package main import ( "context" "encoding/base64" "fmt" "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") action.path = action.GetInput("path") action.reference = action.GetInput("ref") return err } func (action *CheckoutAction) run(ctx context.Context) error { if err := action.setup(); err != nil { return err } env := action.Context() var sh *Shell sh = &Shell{} sh.Add("git", "config", "--global", "--add", "protocol.version", "2") sh.Add("git", "init", "-q", action.path) sh.Add("git", "-C", action.path, "config", "--local", "gc.auto", "0") if err := sh.Run(ctx); err != nil { return err } action.Noticef("Configuring git repository credentials") server, err := url.Parse(env.ServerURL) if err != nil { return err } sh = &Shell{} sh.Add("git", "-C", action.path, "config", "--local", "--add", fmt.Sprintf("url.%s.insteadof", env.ServerURL), fmt.Sprintf("git@%s", server.Host)) sh.Add("git", "-C", action.path, "config", "--local", "--add", fmt.Sprintf("url.%s.insteadof", env.ServerURL), fmt.Sprintf("ssh://git@%s", server.Host)) sh.Add("git", "-C", action.path, "config", "--local", "--add", fmt.Sprintf("url.%s.insteadof", env.ServerURL), fmt.Sprintf("git://%s", server.Host)) if err := sh.Run(ctx); err != nil { return err } enc := base64.StdEncoding header := fmt.Sprintf("Authorization: Basic %s", enc.EncodeToString([]byte(fmt.Sprintf("x-access-token:%s", env.Token)))) sh = &Shell{} sh.Add("git", "-C", action.path, "config", "--local", "--add", fmt.Sprintf("http.%s.extraheader", env.ServerURL), header) if err := sh.Run(ctx); err != nil { return err } origin := "origin" sh = &Shell{} sh.Add("git", "-C", action.path, "remote", "add", origin, action.repository) if err := sh.Run(ctx); err != nil { return err } stdout, _, err := execx(ctx, "git", "-C", action.path, "ls-remote", origin, action.reference) if err != nil { return err } lines := strings.Split(stdout, "\t") if len(lines) < 2 { return fmt.Errorf("git ls-remore resolved nothing") } sha := strings.TrimSpace(lines[0]) ref := strings.TrimSpace(lines[1]) sh = &Shell{} if strings.HasPrefix(ref, "refs/heads/") { name := strings.TrimPrefix(ref, "refs/heads/") remote := fmt.Sprintf("refs/remotes/%s/%s", origin, name) branch := name action.Debugf("Checking out branch %s@%s", name, sha) sh.Add("git", "-C", action.path, "fetch", "--no-tags", "--prune", "--no-recurse-submodules", "--depth=1", origin, fmt.Sprintf("+%s:%s", sha, remote)) sh.Add("git", "-C", action.path, "checkout", "--force", "-B", branch, remote) } else if strings.HasPrefix(ref, "refs/pull/") { name := strings.TrimPrefix(ref, "refs/pull/") 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") } sh.Add("git", "-C", action.path, "fetch", "--no-tags", "--prune", "--no-recurse-submodules", "--depth=1", origin, fmt.Sprintf("+%s:%s", sha, remote)) sh.Add("git", "-C", action.path, "checkout", "--force", "-B", branch, remote) } else if strings.HasPrefix(ref, "refs/tags/") { name := strings.TrimPrefix(ref, "refs/tags/") remote := fmt.Sprintf("refs/tags/%s", name) action.Debugf("Checking out ptag %s@%s", name, sha) sh.Add("git", "-C", action.path, "fetch", "--no-tags", "--prune", "--no-recurse-submodules", "--depth=1", origin, fmt.Sprintf("+%s:%s", sha, remote)) sh.Add("git", "-C", action.path, "checkout", "--force", remote) } else { action.Debugf("Checking out detached head %s", ref) sh.Add("git", "-C", action.path, "fetch", "--no-tags", "--prune", "--no-recurse-submodules", "--depth=1", origin, ref) sh.Add("git", "-C", action.path, "checkout", "--force", ref) } if err := sh.Run(ctx); err != nil { return err } return nil } type Shell [][]string func (sh *Shell) Add(line ...string) { *sh = append([][]string(*sh), line) } func (sh *Shell) Run(ctx context.Context) error { for _, line := range [][]string(*sh) { if _, _, err := execx(ctx, line...); err != nil { return err } } return nil } func execx(ctx context.Context, args ...string) (string, string, error) { cmd := exec.CommandContext(ctx, args[0], args[1:]...) var outbuf, errbuf strings.Builder cmd.Stdout = &outbuf cmd.Stderr = &errbuf err := cmd.Run() return outbuf.String(), errbuf.String(), err }