package git import ( "fmt" "os/exec" "strings" ) // Run executes a git command in the given directory and returns combined output. func Run(dir string, args ...string) (string, error) { cmd := exec.Command("git", args...) cmd.Dir = dir out, err := cmd.CombinedOutput() if err != nil { return "", fmt.Errorf("git %s: %w\n%s", strings.Join(args, " "), err, out) } return strings.TrimSpace(string(out)), nil } // RepoRoot returns the root of the git repository. func RepoRoot(dir string) (string, error) { return Run(dir, "rev-parse", "--show-toplevel") } // Add stages the given paths. func Add(dir string, paths ...string) error { args := append([]string{"add", "--"}, paths...) _, err := Run(dir, args...) return err } // Commit creates a commit with the given message. func Commit(dir, message string) error { _, err := Run(dir, "commit", "-m", message) return err } // Tag creates an annotated tag. func Tag(dir, name, message string) error { _, err := Run(dir, "tag", "-a", name, "-m", message) return err } // TagLightweight creates a lightweight tag. func TagLightweight(dir, name string) error { _, err := Run(dir, "tag", name) return err } // Push pushes the current branch and all tags to origin. func Push(dir string) error { _, err := Run(dir, "push", "--follow-tags") return err } // PushTag pushes a specific tag to origin. func PushTag(dir, tag string) error { _, err := Run(dir, "push", "origin", tag) return err } // CurrentBranch returns the current branch name. func CurrentBranch(dir string) (string, error) { return Run(dir, "rev-parse", "--abbrev-ref", "HEAD") } // TagName returns the conventional tag name for a project version. // For a single-project repo (projectName == "") it returns "v". // For monorepos it returns "@v". func TagName(projectName, version string) string { if projectName == "" { return "v" + version } return projectName + "@v" + version } // TagExists returns true if the tag already exists locally. func TagExists(dir, tag string) (bool, error) { out, err := Run(dir, "tag", "-l", tag) if err != nil { return false, err } return strings.TrimSpace(out) == tag, nil } // BranchExists returns true if the given local branch exists. func BranchExists(dir, branch string) (bool, error) { out, err := Run(dir, "branch", "--list", branch) if err != nil { return false, err } return strings.TrimSpace(out) != "", nil } // CheckoutBranch checks out an existing branch. func CheckoutBranch(dir, branch string) error { _, err := Run(dir, "checkout", branch) return err } // CheckoutNewBranch creates and checks out a new branch from the given base. func CheckoutNewBranch(dir, branch, base string) error { _, err := Run(dir, "checkout", "-B", branch, base) return err } // PushBranch force-pushes a branch to origin. func PushBranch(dir, branch string) error { _, err := Run(dir, "push", "--force-with-lease", "origin", branch) return err } // FetchOrigin fetches from origin. func FetchOrigin(dir string) error { _, err := Run(dir, "fetch", "origin") return err }