package main import ( "context" "encoding/base64" "fmt" "io" "net/url" "os" "os/exec" "os/signal" "strings" "git.geekeey.de/actions/sdk" ) func main() { action := &CheckoutAction{Action: sdk.New()} ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) defer cancel() if err := action.Run(ctx); err != nil { action.Errorf("%s", err) os.Exit(1) } } type CheckoutAction struct { *sdk.Action repository string path string reference string } func (action *CheckoutAction) setup() error { var err error action.repository = action.GetInput("repository") if len(action.repository) == 0 { return fmt.Errorf("input 'repository': is empty") // something like '//.git' } action.path = action.GetInput("path") if len(action.path) == 0 { return fmt.Errorf("input 'path': is empty") // something like '.' } action.reference = action.GetInput("ref") if len(action.reference) == 0 { return fmt.Errorf("input 'ref': is empty") // something like 'HEAD' } return err } func (action *CheckoutAction) Run(ctx context.Context) error { if err := action.setup(); err != nil { return err } env := action.Context() server, err := url.Parse(env.ServerURL) if err != nil { return fmt.Errorf("invalid serevr url: %s", err) } shell := Shell{action: action.Action, args: [][]string{}} shell.Add("git", "config", "--global", "--add", "protocol.version", "2") shell.Add("git", "init", "-q", action.path) shell.Add("git", "-C", action.path, "config", "--local", "gc.auto", "0") if err := shell.Run(ctx, nil, nil); err != nil { return err } action.Noticef("Configuring git repository credentials") shell.Add("git", "-C", action.path, "config", "--local", "--add", fmt.Sprintf("url.%s.insteadof", server.String()), fmt.Sprintf("git@%s", server.Host)) shell.Add("git", "-C", action.path, "config", "--local", "--add", fmt.Sprintf("url.%s.insteadof", server.String()), fmt.Sprintf("ssh://git@%s", server.Host)) shell.Add("git", "-C", action.path, "config", "--local", "--add", fmt.Sprintf("url.%s.insteadof", server.String()), fmt.Sprintf("git://%s", server.Host)) if err := shell.Run(ctx, nil, nil); err != nil { return err } enc := base64.StdEncoding header := fmt.Sprintf("Authorization: Basic %s", enc.EncodeToString(fmt.Appendf(nil, "x-access-token:%s", env.Token))) shell.Add("git", "-C", action.path, "config", "--local", "--add", fmt.Sprintf("http.%s.extraheader", env.ServerURL), header) if err := shell.Run(ctx, nil, nil); err != nil { return err } origin := "origin" shell.Add("git", "-C", action.path, "remote", "add", origin, action.repository) if err := shell.Run(ctx, nil, nil); err != nil { return err } shell.Add("git", "-C", action.path, "ls-remote", origin, action.reference) var stdout strings.Builder = strings.Builder{} if err := shell.Run(ctx, &stdout, nil); err != nil { return err } lines := strings.Split(stdout.String(), "\t") if len(lines) < 2 { return fmt.Errorf("git ls-remore resolved nothing") } sha := strings.TrimSpace(lines[0]) ref := strings.TrimSpace(lines[1]) var name string var ok bool if name, ok = strings.CutPrefix(ref, "refs/heads/"); ok { remote := fmt.Sprintf("refs/remotes/%s/%s", origin, name) branch := name action.Debugf("Checking out branch %s@%s", name, sha) shell.Add("git", "-C", action.path, "fetch", "--no-tags", "--prune", "--no-recurse-submodules", "--depth=1", origin, fmt.Sprintf("+%s:%s", sha, remote)) shell.Add("git", "-C", action.path, "checkout", "--force", "-B", branch, remote) } else if name, ok = strings.CutPrefix(ref, "refs/pull/"); ok { remote := fmt.Sprintf("refs/remotes/pull/%s", name) action.Debugf("Checking out pull-request %s@%s", name, sha) var branch string if len(env.BaseRef) != 0 { branch = env.BaseRef } else if len(env.HeadRef) != 0 { branch = env.HeadRef } else { return fmt.Errorf("pull request can not find base ref for branch") } shell.Add("git", "-C", action.path, "fetch", "--no-tags", "--prune", "--no-recurse-submodules", "--depth=1", origin, fmt.Sprintf("+%s:%s", sha, remote)) shell.Add("git", "-C", action.path, "checkout", "--force", "-B", branch, remote) } else if name, ok = strings.CutPrefix(ref, "refs/tags/"); ok { remote := fmt.Sprintf("refs/tags/%s", name) action.Debugf("Checking out tag %s@%s", name, sha) shell.Add("git", "-C", action.path, "fetch", "--no-tags", "--prune", "--no-recurse-submodules", "--depth=1", origin, fmt.Sprintf("+%s:%s", sha, remote)) shell.Add("git", "-C", action.path, "checkout", "--force", remote) } else { action.Debugf("Checking out detached head %s", ref) shell.Add("git", "-C", action.path, "fetch", "--no-tags", "--prune", "--no-recurse-submodules", "--depth=1", origin, ref) shell.Add("git", "-C", action.path, "checkout", "--force", ref) } if err := shell.Run(ctx, nil, nil); err != nil { return err } return nil } type Shell struct { action *sdk.Action args [][]string } func (sh *Shell) Add(args ...string) { sh.args = append(sh.args, args) } func (sh *Shell) Run(ctx context.Context, stdout io.Writer, stderr io.Writer) error { for _, args := range sh.args { if sh.action != nil { sh.action.Debugf("exec: %s", strings.Join(args, " ")) } cmd := exec.CommandContext(ctx, args[0], args[1:]...) cmd.Stdout = stdout cmd.Stderr = stderr if err := cmd.Run(); err != nil { return err } } sh.args = [][]string{} return nil }