From 11e9e16078d3d3b9bfbbe15f47923a7f16736759 Mon Sep 17 00:00:00 2001 From: Kayvan Sylvan Date: Sat, 3 Jan 2026 14:07:50 -0800 Subject: [PATCH 1/3] fix: improve git worktree status detection to ignore staged-only files - Add worktree-specific check for actual working directory changes - Filter out files that are only staged but not in worktree - Check worktree status codes instead of using IsClean method - Update GetStatusDetails to only include worktree-modified files - Ignore unmodified and untracked files in clean check --- cmd/generate_changelog/internal/git/walker.go | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/cmd/generate_changelog/internal/git/walker.go b/cmd/generate_changelog/internal/git/walker.go index 93e9347d..b98c96f8 100644 --- a/cmd/generate_changelog/internal/git/walker.go +++ b/cmd/generate_changelog/internal/git/walker.go @@ -433,7 +433,17 @@ func (w *Walker) IsWorkingDirectoryClean() (bool, error) { return false, fmt.Errorf("failed to get git status: %w", err) } - return status.IsClean(), nil + // In worktrees, we need to check if files are actually modified in the working directory + // Ignore files that are only staged (Added) but not present in the worktree filesystem + for _, fileStatus := range status { + // Check if there are any changes in the working directory (not just staging) + // Worktree status codes: ' ' = unmodified, 'M' = modified, 'D' = deleted, '?' = untracked + if fileStatus.Worktree != git.Unmodified && fileStatus.Worktree != git.Untracked { + return false, nil + } + } + + return true, nil } // GetStatusDetails returns a detailed status of the working directory @@ -448,13 +458,12 @@ func (w *Walker) GetStatusDetails() (string, error) { return "", fmt.Errorf("failed to get git status: %w", err) } - if status.IsClean() { - return "", nil - } - var details strings.Builder for file, fileStatus := range status { - details.WriteString(fmt.Sprintf(" %c%c %s\n", fileStatus.Staging, fileStatus.Worktree, file)) + // Only include files with actual working directory changes + if fileStatus.Worktree != git.Unmodified && fileStatus.Worktree != git.Untracked { + details.WriteString(fmt.Sprintf(" %c%c %s\n", fileStatus.Staging, fileStatus.Worktree, file)) + } } return details.String(), nil From 53bad5b70d77c5c2df1ff974bbd8f3a809378fd8 Mon Sep 17 00:00:00 2001 From: Kayvan Sylvan Date: Sat, 3 Jan 2026 14:16:09 -0800 Subject: [PATCH 2/3] fix: IsWorkingDirectoryClean to work correctly in worktrees - Check filesystem existence of staged files to handle worktree scenarios - Ignore files staged in main repo that don't exist in worktree - Allow staged files that exist in worktree to be committed normally Co-Authored-By: Warp --- cmd/generate_changelog/internal/git/walker.go | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/cmd/generate_changelog/internal/git/walker.go b/cmd/generate_changelog/internal/git/walker.go index b98c96f8..b5ce43f9 100644 --- a/cmd/generate_changelog/internal/git/walker.go +++ b/cmd/generate_changelog/internal/git/walker.go @@ -2,6 +2,8 @@ package git import ( "fmt" + "os" + "path/filepath" "regexp" "strconv" "strings" @@ -433,14 +435,27 @@ func (w *Walker) IsWorkingDirectoryClean() (bool, error) { return false, fmt.Errorf("failed to get git status: %w", err) } - // In worktrees, we need to check if files are actually modified in the working directory - // Ignore files that are only staged (Added) but not present in the worktree filesystem - for _, fileStatus := range status { - // Check if there are any changes in the working directory (not just staging) - // Worktree status codes: ' ' = unmodified, 'M' = modified, 'D' = deleted, '?' = untracked + worktreePath := worktree.Filesystem.Root() + + // In worktrees, files staged in the main repo may appear in status but not exist in the worktree + // We need to check both the working directory status AND filesystem existence + for file, fileStatus := range status { + // Check if there are any changes in the working directory if fileStatus.Worktree != git.Unmodified && fileStatus.Worktree != git.Untracked { return false, nil } + + // For staged files (Added, Modified in index), verify they exist in this worktree's filesystem + // This handles the worktree case where the main repo has staged files that don't exist here + if fileStatus.Staging != git.Unmodified && fileStatus.Staging != git.Untracked { + filePath := filepath.Join(worktreePath, file) + if _, err := os.Stat(filePath); os.IsNotExist(err) { + // File is staged but doesn't exist in this worktree - ignore it + continue + } + // File is staged AND exists in this worktree - not clean + return false, nil + } } return true, nil From d2ebe99e0ed11bc3865f0f425d19a11da3dbf372 Mon Sep 17 00:00:00 2001 From: Kayvan Sylvan Date: Sat, 3 Jan 2026 14:29:18 -0800 Subject: [PATCH 3/3] 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 --- cmd/generate_changelog/internal/git/walker.go | 49 +++++++++---------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/cmd/generate_changelog/internal/git/walker.go b/cmd/generate_changelog/internal/git/walker.go index b5ce43f9..a8c03c8d 100644 --- a/cmd/generate_changelog/internal/git/walker.go +++ b/cmd/generate_changelog/internal/git/walker.go @@ -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