fix: use native git CLI for add/commit in worktrees

go-git has issues with worktrees where the object database isn't properly
shared, causing 'invalid object' errors when trying to commit. Switching
to native git CLI for add and commit operations resolves this.

This fixes generate_changelog failing in worktrees with errors like:
- 'cannot create empty commit: clean working tree'
- 'error: invalid object ... Error building trees'

Co-Authored-By: Warp <agent@warp.dev>
This commit is contained in:
Kayvan Sylvan
2026-01-03 14:29:18 -08:00
parent 53bad5b70d
commit d2ebe99e0e

View File

@@ -3,6 +3,7 @@ package git
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
@@ -485,57 +486,53 @@ func (w *Walker) GetStatusDetails() (string, error) {
}
// AddFile adds a file to the git index
// Uses native git CLI instead of go-git to properly handle worktree scenarios
func (w *Walker) AddFile(filename string) error {
worktree, err := w.repo.Worktree()
if err != nil {
return fmt.Errorf("failed to get worktree: %w", err)
}
_, err = worktree.Add(filename)
worktreePath := worktree.Filesystem.Root()
// Use native git add command to avoid go-git worktree issues
cmd := exec.Command("git", "add", filename)
cmd.Dir = worktreePath
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failed to add file %s: %w", filename, err)
return fmt.Errorf("failed to add file %s: %w (output: %s)", filename, err, string(output))
}
return nil
}
// CommitChanges creates a commit with the given message
// Uses native git CLI instead of go-git to properly handle worktree scenarios
func (w *Walker) CommitChanges(message string) (plumbing.Hash, error) {
worktree, err := w.repo.Worktree()
if err != nil {
return plumbing.ZeroHash, fmt.Errorf("failed to get worktree: %w", err)
}
// Get git config for author information
cfg, err := w.repo.Config()
worktreePath := worktree.Filesystem.Root()
// Use native git commit command to avoid go-git worktree issues
cmd := exec.Command("git", "commit", "-m", message)
cmd.Dir = worktreePath
output, err := cmd.CombinedOutput()
if err != nil {
return plumbing.ZeroHash, fmt.Errorf("failed to get git config: %w", err)
return plumbing.ZeroHash, fmt.Errorf("failed to commit: %w (output: %s)", err, string(output))
}
var authorName, authorEmail string
if cfg.User.Name != "" {
authorName = cfg.User.Name
} else {
authorName = "Changelog Bot"
}
if cfg.User.Email != "" {
authorEmail = cfg.User.Email
} else {
authorEmail = "bot@changelog.local"
}
commit, err := worktree.Commit(message, &git.CommitOptions{
Author: &object.Signature{
Name: authorName,
Email: authorEmail,
When: time.Now(),
},
})
// Get the commit hash from HEAD
ref, err := w.repo.Head()
if err != nil {
return plumbing.ZeroHash, fmt.Errorf("failed to commit: %w", err)
return plumbing.ZeroHash, fmt.Errorf("failed to get HEAD after commit: %w", err)
}
return commit, nil
return ref.Hash(), nil
}
// PushToRemote pushes the current branch to the remote repository