package main import ( "bytes" "fmt" "os/exec" "strings" ) // gitRun executes a git command in repoPath, returning a descriptive error // that includes the combined stdout+stderr on failure. func gitRun(repoPath string, args ...string) error { cmd := exec.Command("git", append([]string{"-C", repoPath}, args...)...) out, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("git %s: %w\n%s", strings.Join(args, " "), err, bytes.TrimSpace(out)) } return nil } // gitOutput executes a git command and returns its stdout as a string. func gitOutput(repoPath string, args ...string) (string, error) { cmd := exec.Command("git", append([]string{"-C", repoPath}, args...)...) out, err := cmd.Output() if err != nil { var stderr []byte if ee, ok := err.(*exec.ExitError); ok { stderr = ee.Stderr } return "", fmt.Errorf("git %s: %w\n%s", strings.Join(args, " "), err, bytes.TrimSpace(stderr)) } return string(out), nil } // GitPull fetches from the remote and rebases the local branch. // Using --autostash ensures any uncommitted changes are preserved across the rebase // (should not normally happen but guards against manual edits). func GitPull(cfg *Config) error { if err := gitRun(cfg.RepoPath, "fetch", "--prune", cfg.RepoRemote); err != nil { return fmt.Errorf("git fetch: %w", err) } if err := gitRun(cfg.RepoPath, "pull", "--rebase", "--autostash", cfg.RepoRemote, cfg.RepoBranch, ); err != nil { return fmt.Errorf("git pull --rebase: %w", err) } return nil } // GitStatusChanged reports true when the working tree has uncommitted changes. func GitStatusChanged(cfg *Config) (bool, error) { out, err := gitOutput(cfg.RepoPath, "status", "--porcelain") if err != nil { return false, err } return strings.TrimSpace(out) != "", nil } // GitCommitAndPush stages the dynamic directory, commits with the given message, // and pushes to the configured remote branch. // Author identity is passed via git -c flags to avoid requiring global git config. func GitCommitAndPush(cfg *Config, message string) error { if err := gitRun(cfg.RepoPath, "add", cfg.DynamicDir); err != nil { return fmt.Errorf("git add: %w", err) } if err := gitRun(cfg.RepoPath, "-c", "user.name="+cfg.AuthorName, "-c", "user.email="+cfg.AuthorEmail, "commit", "-m", message, ); err != nil { return fmt.Errorf("git commit: %w", err) } if err := gitRun(cfg.RepoPath, "push", cfg.RepoRemote, cfg.RepoBranch); err != nil { return fmt.Errorf("git push: %w", err) } return nil }