Skip to content

Conversation

@pjbgf
Copy link
Member

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 <[email protected]>
CopilotAI review requested due to automatic review settings December 19, 2025 09:21
Copy link
Contributor

CopilotAI 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.

FileDescription
cmd/gogit/worktree.goNew file implementing worktree subcommands with add, list, and remove functionality
go.modUpdates go-git, go-billy, and go-git-fixtures dependencies to versions supporting worktree features
go.sumCorresponding 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

CopilotAIDec 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

CopilotAIDec 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
returnfmt.Sprint("[", name.Short(), "]")
}
returnfmt.Sprint("(detached ", string(ref.Name()), ")")
returnfmt.Sprintf("[%s]", name.Short())
}
returnfmt.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

CopilotAIDec 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
returnfmt.Sprint("[", name.Short(), "]")
}
returnfmt.Sprint("(detached ", string(ref.Name()), ")")
returnfmt.Sprintf("[%s]", name.Short())
}
returnfmt.Sprintf("(detached %s)", ref.Name())

Copilot uses AI. Check for mistakes.
continue
}

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

CopilotAIDec 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

CopilotAIDec 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)
iferr!=nil{
continue
}
ref, err:=r.Head()
wtRepo, err:=w.Open(wt)
iferr!=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

CopilotAIDec 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()
iferr!=nil{
returnfmt.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()
iferr!=nil{
returnfmt.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

CopilotAIDec 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
returnfmt.Errorf("failed to get HEAD: %w", err)
continue

Copilot uses AI. Check for mistakes.
Sign up for freeto 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

@pjbgf