grokkit/internal/git/git.go
Gregory Gauthier 452051f0c1 refactor(git): improve diff handling to tolerate exit code 1
- Introduce git.Diff function that uses CombinedOutput and treats exit code 1 as success (indicating changes exist).
- Update prdescribe command to use git.Diff and trim whitespace when checking for empty diffs.
- Enhance prdescribe output to include base branch in the status message.
2026-03-31 10:40:33 +01:00

85 lines
2.4 KiB
Go

package git
import (
"errors"
"fmt"
"os/exec"
"strings"
"gmgauthier.com/grokkit/internal/logger"
)
func Run(args []string) (string, error) {
cmdStr := "git " + strings.Join(args, " ")
logger.Debug("executing git command", "command", cmdStr, "args", args)
// nolint:gosec // intentional subprocess for git operation
out, err := exec.Command("git", args...).Output()
if err != nil {
logger.Error("git command failed",
"command", cmdStr,
"error", err)
return "", fmt.Errorf("git command failed: %w", err)
}
outputLen := len(out)
logger.Debug("git command completed",
"command", cmdStr,
"output_length", outputLen)
return string(out), nil
}
func IsRepo() bool {
logger.Debug("checking if directory is a git repository")
// nolint:gosec // intentional subprocess for git repository check
_, err := exec.Command("git", "rev-parse", "--is-inside-work-tree").Output()
isRepo := err == nil
logger.Debug("git repository check completed", "is_repo", isRepo)
return isRepo
}
func LatestTag() (string, error) {
out, err := Run([]string{"describe", "--tags", "--abbrev=0"})
if err != nil {
return "", err
}
return strings.TrimSpace(out), nil
}
// PreviousTag returns the tag immediately before the given one.
func PreviousTag(current string) (string, error) {
out, err := Run([]string{"describe", "--tags", "--abbrev=0", current + "^"})
if err != nil {
return "", err
}
return strings.TrimSpace(out), nil
}
// LogSince returns formatted commit log since the given ref (exactly matches the todo spec).
func LogSince(since string) (string, error) {
args := []string{"log", "--pretty=format:%s%n%b%n---", since + "..HEAD"}
return Run(args)
}
// Diff runs `git diff` and tolerates exit code 1 (differences found).
// This is normal git behavior when there *are* changes — unlike .Output().
func Diff(args []string) (string, error) {
cmdStr := "git " + strings.Join(args, " ")
logger.Debug("executing git diff", "command", cmdStr)
// nolint:gosec // intentional git subprocess
cmd := exec.Command("git", args...)
out, err := cmd.CombinedOutput()
if err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) && exitErr.ExitCode() == 1 {
// legitimate diff (changes exist)
return string(out), nil
}
logger.Error("git diff failed", "command", cmdStr, "error", err)
return "", fmt.Errorf("git diff failed: %w", err)
}
return string(out), nil
}