package git import ( "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) 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") _, 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) }