//go:build !gogit package git import ( "bytes" "context" "fmt" "io" "os/exec" "strings" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" ) type Repository interface { Branches() ([]*plumbing.Reference, error) // Auth(username string, password string) error FetchContext(ctx context.Context, opts *git.FetchOptions) error PushContext(ctx context.Context, opts *git.PushOptions) error Head() (*plumbing.Reference, error) Worktree() (Worktree, error) } type Worktree interface { Checkout(*git.CheckoutOptions) error PullContext(ctx context.Context, opts *git.PullOptions) error Status() (git.Status, error) AddWithOptions(opts *git.AddOptions) error Commit(msg string, opts *git.CommitOptions) (plumbing.Hash, error) Reset(opts *git.ResetOptions) error } type repository struct { gocmd string path string // authUsername string // authPassword string } func PlainOpenWithOptions(path string, opts *git.PlainOpenOptions) (Repository, error) { gopath, err := exec.LookPath("git") if err != nil { return nil, err } return &repository{path: path, gocmd: gopath}, nil } /* func (r *repository) Auth(username string, password string) error { r.authUsername = username r.authPassword = password return nil } */ func (r *repository) Branches() ([]*plumbing.Reference, error) { var branches []*plumbing.Reference cmd := exec.Command(r.gocmd, "show-ref", "--branches") buf, err := cmd.CombinedOutput() if err != nil { return nil, fmt.Errorf("output %s error %w", buf, err) } br := bytes.NewBuffer(buf) for { line, err := br.ReadString('\n') if err != nil { if err == io.EOF && line == "" { break } else if err != io.EOF && line == "" { return nil, err } } fields := strings.Fields(line) if len(fields) != 2 { return nil, fmt.Errorf("invalid fields %s", line) } branches = append(branches, plumbing.NewReferenceFromStrings(fields[1], fields[0])) } return branches, nil } func (r *repository) FetchContext(ctx context.Context, opts *git.FetchOptions) error { args := []string{"fetch"} if opts.Force { args = append(args, "-f") } cmd := exec.CommandContext(ctx, r.gocmd, args...) buf, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("output %s error %w", buf, err) } return nil } func (r *repository) PushContext(ctx context.Context, opts *git.PushOptions) error { args := []string{"push"} if opts.Force { args = append(args, "-f") } /* TODO var refs []string for _, ref := range opts.RefSpecs { refs = append(refs, ref.String()) } args = append(args, strings.Join(refs, " ")) */ cmd := exec.CommandContext(ctx, r.gocmd, args...) buf, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("output %s error %w", buf, err) } return nil } func (r *repository) Head() (*plumbing.Reference, error) { var head *plumbing.Reference cmd := exec.Command(r.gocmd, "symbolic-ref", "--short", "HEAD") buf, err := cmd.CombinedOutput() if err != nil { return nil, fmt.Errorf("output %s error %w", buf, err) } br := bytes.NewBuffer(buf) for { line, err := br.ReadString('\n') if err != nil { if err == io.EOF && line == "" { break } else if err != io.EOF && line == "" { return nil, err } } fields := strings.Fields(line) if len(fields) != 2 { return nil, fmt.Errorf("invalid fields %s", line) } head = plumbing.NewReferenceFromStrings("HEAD", fields[0]) } return head, nil } type worktree struct { gocmd string } func (r *repository) Worktree() (Worktree, error) { return &worktree{gocmd: r.gocmd}, nil } func (w *worktree) Checkout(opts *git.CheckoutOptions) error { args := []string{"checkout"} if opts.Create { args = append(args, "-b", opts.Branch.Short()) } if opts.Force { args = append(args, "-f") } if opts.Hash.IsZero() { args = append(args, opts.Branch.Short()) } else { args = append(args, opts.Hash.String()) } cmd := exec.Command(w.gocmd, args...) buf, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("output %s error %w", buf, err) } return nil } func (w *worktree) Status() (git.Status, error) { return git.Status{}, nil } func (w *worktree) Reset(opts *git.ResetOptions) error { args := []string{"reset"} if opts.Mode == git.HardReset { args = append(args, "--hard") } args = append(args, opts.Commit.String()) cmd := exec.Command(w.gocmd, args...) buf, err := cmd.CombinedOutput() if err != nil { return err } _ = buf return nil } func (w *worktree) Commit(msg string, opts *git.CommitOptions) (plumbing.Hash, error) { cmd := exec.Command(w.gocmd, `commit`, fmt.Sprintf(`--author="%s <%s>"`, opts.Author.Name, opts.Author.Email), "-m", msg, fmt.Sprintf(`--date="%s"`, opts.Author.When.Format(`Mon Jan _2 15:04:05 2006 -0700`)), ) buf, err := cmd.CombinedOutput() if err != nil { return plumbing.ZeroHash, fmt.Errorf("output %s error %w", buf, err) } var head *plumbing.Reference cmd = exec.Command(w.gocmd, "show-ref", "HEAD") buf, err = cmd.CombinedOutput() if err != nil { return plumbing.ZeroHash, err } br := bytes.NewBuffer(buf) for { line, err := br.ReadString('\n') if err != nil { if err == io.EOF && line == "" { break } else if err != io.EOF && line == "" { return plumbing.ZeroHash, err } } fields := strings.Fields(line) if len(fields) != 2 { return plumbing.ZeroHash, fmt.Errorf("invalid fields %s", line) } head = plumbing.NewReferenceFromStrings("HEAD", fields[0]) } return head.Hash(), nil } func (w *worktree) PullContext(ctx context.Context, opts *git.PullOptions) error { args := []string{"pull"} if opts.Force { args = append(args, "-f") } if opts.Depth != 0 { args = append(args, fmt.Sprintf("--depth=%d", opts.Depth)) } cmd := exec.CommandContext(ctx, w.gocmd, args...) buf, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("output %s error %w", buf, err) } return nil } func (w *worktree) AddWithOptions(opts *git.AddOptions) error { cmd := exec.Command(w.gocmd, "add", opts.Path) buf, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("output %s error %w", buf, err) } return nil }