Skip to content

Conversation

@pjbgf
Copy link
Member

@pjbgf pjbgf commented Dec 19, 2025

Adds a new worktree command with sub command add, remove and list.

The output of list was aligned with upstream for the supported cases (e.g. we currently don't support prune):

go-git/cli

$ ./build/gogit worktree list
/src/git/go-git/cli      a411c9b [worktrees]
/tmp/abc                       a411c9b (detached HEAD)
/tmp/abc1                      a411c9b [abc1]
/tmp/abc3                      f4f6863 (detached HEAD)
/tmp/abc4                      f4f6863 [abc4]
/tmp/abc5                      f4f6863 (detached HEAD)
/tmp/abc6                      a411c9b (detached HEAD)

upstream git

$ git worktree list
/src/git/go-git/cli  a411c9b [worktrees]
/tmp/abc                   a411c9b (detached HEAD)
/tmp/abc1                  a411c9b [abc1]
/tmp/abc3                  f4f6863 (detached HEAD)
/tmp/abc4                  f4f6863 [abc4]
/tmp/abc5                  f4f6863 (detached HEAD)
/tmp/abc6                  a411c9b (detached HEAD)

This command was largely made to test the go-git linked worktree implementation.

Signed-off-by: Paulo Gomes <pjbgf@linux.com>
Copilot AI review requested due to automatic review settings December 19, 2025 09:21
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new worktree command to the go-git CLI that enables management of Git worktrees with three subcommands: add, list, and remove. The implementation aims to align output formatting with upstream Git for the list command and supports testing the go-git linked worktree functionality.

  • Adds comprehensive worktree management capabilities (add, list, remove)
  • Updates go-git and related dependencies to versions with worktree support
  • Aligns list command output format with upstream Git

Reviewed changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 7 comments.

File Description
cmd/gogit/worktree.go New file implementing worktree subcommands with add, list, and remove functionality
go.mod Updates go-git, go-billy, and go-git-fixtures dependencies to versions supporting worktree features
go.sum Corresponding checksum updates for the new dependency versions

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +65 to +69
if worktreeAddCommit != "" {
hash := plumbing.NewHash(worktreeAddCommit)
if !hash.IsZero() {
opts = append(opts, worktree.WithCommit(hash))
}
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No validation is performed on the commit hash provided by the user. If the user provides an invalid hash string, plumbing.NewHash() will silently return a zero hash, and the WithCommit option won't be added. This could lead to unexpected behavior where the user thinks they're checking out a specific commit but they're not.

Copilot uses AI. Check for mistakes.
Comment on lines +157 to +160
return fmt.Sprint("[", name.Short(), "]")
}

return fmt.Sprint("(detached ", string(ref.Name()), ")")
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fmt.Sprint call is unnecessarily verbose. Using fmt.Sprintf with proper formatting would be more idiomatic and clearer.

Suggested change
return fmt.Sprint("[", name.Short(), "]")
}
return fmt.Sprint("(detached ", string(ref.Name()), ")")
return fmt.Sprintf("[%s]", name.Short())
}
return fmt.Sprintf("(detached %s)", string(ref.Name()))

Copilot uses AI. Check for mistakes.
Comment on lines +157 to +160
return fmt.Sprint("[", name.Short(), "]")
}

return fmt.Sprint("(detached ", string(ref.Name()), ")")
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fmt.Sprint call is unnecessarily verbose. Using fmt.Sprintf with proper formatting would be more idiomatic and clearer.

Suggested change
return fmt.Sprint("[", name.Short(), "]")
}
return fmt.Sprint("(detached ", string(ref.Name()), ")")
return fmt.Sprintf("[%s]", name.Short())
}
return fmt.Sprintf("(detached %s)", ref.Name())

Copilot uses AI. Check for mistakes.
continue
}

wtPath := filepath.Dir(string(gitdirData[:len(gitdirData)-1]))
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential index out of bounds error when gitdirData is empty. The code subtracts 1 from the length without checking if the data has at least one byte. This could panic if the gitdir file is empty or only contains a newline character.

Copilot uses AI. Check for mistakes.
Comment on lines +136 to +141
r, err := w.Open(wt)
if err != nil {
continue
}

ref, err := r.Head()
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable shadowing issue: the variable 'r' is being redeclared here, shadowing the repository variable 'r' from line 89. This makes the outer 'r' variable inaccessible within this scope and could lead to confusion about which repository is being used.

Suggested change
r, err := w.Open(wt)
if err != nil {
continue
}
ref, err := r.Head()
wtRepo, err := w.Open(wt)
if err != nil {
continue
}
ref, err := wtRepo.Head()

Copilot uses AI. Check for mistakes.
Comment on lines +141 to +146
ref, err := r.Head()
if err != nil {
return fmt.Errorf("failed to get HEAD: %w", err)
}

fmt.Fprintf(cmd.OutOrStdout(), "%-30s %s %s\n", wtPath, ref.Hash().String()[:7], refName(ref))
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable shadowing issue: the variable 'ref' is being redeclared here, shadowing the HEAD reference 'ref' from line 104. This makes the outer 'ref' variable inaccessible within this scope and could lead to confusion.

Suggested change
ref, err := r.Head()
if err != nil {
return fmt.Errorf("failed to get HEAD: %w", err)
}
fmt.Fprintf(cmd.OutOrStdout(), "%-30s %s %s\n", wtPath, ref.Hash().String()[:7], refName(ref))
wtRef, err := r.Head()
if err != nil {
return fmt.Errorf("failed to get HEAD: %w", err)
}
fmt.Fprintf(cmd.OutOrStdout(), "%-30s %s %s\n", wtPath, wtRef.Hash().String()[:7], refName(wtRef))

Copilot uses AI. Check for mistakes.

ref, err := r.Head()
if err != nil {
return fmt.Errorf("failed to get HEAD: %w", err)
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent error handling in the loop. While errors from os.ReadFile, w.Init, and w.Open are silently ignored with 'continue', an error from r.Head() returns immediately with an error. This inconsistency could make the command fail unexpectedly instead of just skipping problematic worktrees like the earlier error cases.

Suggested change
return fmt.Errorf("failed to get HEAD: %w", err)
continue

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants