gitutil

package
v0.88.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 17, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

README

gitutil

The single git-execution package for the monorepo. All git operations shell out to the git CLI through Run; the go-git library must not be used here (it has repeatedly caused issues: it wipes git-ignored files on checkout, cannot fetch or push private repos without explicit auth, and does not resolve linked-worktree configs).

Invariants

  • All git commands go through Run, which forces LC_ALL=C (so stderr substring checks are stable), sets GIT_TERMINAL_PROMPT=0 (no interactive credential prompts), and redacts URL-embedded credentials in error messages.
  • Credentials must never be persisted to .git/config. SetRemote and CloneWithConfig store clean URLs only; credential-embedded URLs from Config.FullyQualifiedRemote() are passed as command-line arguments per invocation.
  • Minimum supported git version is 2.11 (required by status --porcelain=v2). Do not introduce flags that raise the floor (e.g. git init -b, which requires 2.28; use git symbolic-ref HEAD instead, see EnsureInit).

API guidance

Common operations get named functions (Clone, Fetch, Status, Pull, Push, CommitAll, SetRemote, ...). One-off commands should call Run directly at the call site instead of adding single-use helpers.

File map

  • run.go — the Run exec primitive and credential redaction
  • config.goConfig (remote + ephemeral credentials) and Signature (commit authorship)
  • repo.go — repo detection, init, clone, repo-root/subpath inference
  • commit.go — branch and identity lookup, checkout, commit, commit-and-push
  • remote.go — remote listing and management
  • sync.go — status, fetch, pull, push, merge
  • github.go — GitHub remote URL parsing and normalization
  • gitignore.go.gitignore management via a drivers.RepoStore

Documentation

Overview

Package gitutil provides utilities for working with git repositories. All git operations shell out to the git CLI via Run; the go-git library must not be used here. Credential-embedded remote URLs may be passed as command-line arguments, but must never be persisted to .git/config; Run redacts URL credentials in error messages.

Index

Constants

This section is empty.

Variables

View Source
var ErrDetachedHead = errors.New("detached HEAD state detected, please checkout a branch")

ErrDetachedHead is returned when an operation requires HEAD to point to a branch.

View Source
var ErrEmptyCommit = errors.New("nothing to commit")

ErrEmptyCommit is returned by CommitAll when there are no changes to commit.

View Source
var ErrGitRemoteNotFound = errors.New("no git remotes found")
View Source
var ErrNotAGitRepository = errors.New("not a git repository")
View Source
var ErrRefNotFound = errors.New("git reference not found")

Functions

func Checkout added in v0.87.0

func Checkout(repoDir, branch string, force, create bool, startPoint string) error

Checkout checks out a branch using the git command. If create is true, it creates the branch (using -B) at the given startPoint. go-git wipes out git-ignored changes during checkout so must use the git command.

func Clone added in v0.87.0

func Clone(ctx context.Context, path, remote, checkoutBranch string, singleBranch, shallow bool) error

func CloneRepo added in v0.88.0

func CloneRepo(ctx context.Context, repoURL string) (string, error)

CloneRepo clones the repository at repoURL into the current working directory and returns the repository name.

func CloneWithConfig added in v0.88.0

func CloneWithConfig(ctx context.Context, path string, config *Config) error

CloneWithConfig clones the remote described by config into path, naming the remote config.RemoteName() and checking out config.DefaultBranch. Only the clean config.Remote URL is persisted in the repo's config; credentials are passed exclusively on the command line.

func CommitAll added in v0.87.0

func CommitAll(ctx context.Context, path, pathspec, message string, author Signature) (string, error)

CommitAll stages all changes in the working tree and creates a commit with the given message. If pathspec is non-empty, staging and the empty-commit check are scoped to that pathspec. The commit's author and committer are set to the provided author. Returns the new commit hash, or ErrEmptyCommit if there are no changes to commit.

func CommitAndForcePush added in v0.88.0

func CommitAndForcePush(ctx context.Context, path string, config *Config, commitMsg string, author Signature) error

CommitAndForcePush is similar to CommitAndPush but force pushes the local changes to the remote. Unlike CommitAndPush, the current local branch need not match config.DefaultBranch, and HEAD may be detached.

func CommitAndPush added in v0.88.0

func CommitAndPush(ctx context.Context, path string, config *Config, commitMsg string, author Signature) error

CommitAndPush stages and commits all changes at path (scoped to config.Subpath if set) and pushes the current branch to the remote described by config. It initializes a git repository at path if one does not exist. If there is nothing new to commit, it still attempts the push. Only the clean config.Remote URL is persisted in the repo's config; credentials are passed exclusively on the command line.

func CurrentBranch added in v0.88.0

func CurrentBranch(ctx context.Context, path string) (string, error)

CurrentBranch returns the name of the branch HEAD points to. It also succeeds on unborn branches in repositories without commits. Returns ErrDetachedHead if HEAD is detached.

func Diff added in v0.88.0

func Diff(ctx context.Context, path, subpath, remoteName, remoteBranch string) (string, error)

Diff returns the combined unified patch between the working tree at path and the comparison ref, i.e. the changes that would land on the ref if merged. It mirrors ChangedFiles: committed, staged, and untracked changes are all included.

The result is git's standard unified diff format, ready for rendering. Per-file sections larger than maxFileDiffBytes are replaced with a "diff too large" placeholder. (Genuinely binary files stay small because git emits a "Binary files … differ" line for them rather than their content.)

func EnsureGitignoreHas added in v0.87.0

func EnsureGitignoreHas(ctx context.Context, repo drivers.RepoStore, path string) (bool, error)

EnsureGitignoreHas ensures the given path is listed in .gitignore. Returns true if .gitignore was modified.

func EnsureInit added in v0.88.0

func EnsureInit(ctx context.Context, path, defaultBranch string) error

EnsureInit initializes a git repository at path if one does not already exist. If defaultBranch is non-empty, the initial (unborn) branch is named after it. It is a no-op if path already contains a .git entry (a directory in a regular checkout, or a file in a linked worktree).

func Fetch added in v0.88.0

func Fetch(ctx context.Context, path string, config *Config) error

Fetch fetches the latest changes from the remote described by config, updating the remote-tracking refs under `refs/remotes/<remote-name>/`. If config is nil or carries no credentials, it fetches from origin relying on git's own configuration and credential helpers.

func FetchBranches added in v0.87.0

func FetchBranches(ctx context.Context, path string, branches ...string) error

FetchBranches fetches the specified branches from the remote repository. If a branch doesn't exist on the remote, it will be skipped without returning an error.

func ForcePush added in v0.88.0

func ForcePush(ctx context.Context, path, remote, branch string) error

ForcePush force-pushes HEAD to branch on remote, overwriting the remote ref unconditionally. It uses the refspec HEAD:<branch> so it works even when the local HEAD is detached or points to a different branch. remote may be a remote name or a URL with embedded credentials.

func Hash added in v0.87.0

func Hash(ctx context.Context, path, ref string) (string, error)

Hash returns the commit hash for the given ref. Returns ErrRefNotFound if the ref does not resolve.

func InferRepoRoot added in v0.88.0

func InferRepoRoot(path string) (string, error)

InferRepoRoot returns the root of the git working tree containing path. Returns ErrNotAGitRepository if path is not inside a git working tree.

func InferRepoRootAndSubpath added in v0.88.0

func InferRepoRootAndSubpath(path string) (string, string, error)

InferRepoRootAndSubpath infers the root of the Git repository and the subpath from the given path. Since the extraction stops at first .git directory it means that if a subpath in a github monorepo is deployed as a rill managed project it will prevent the subpath from being inferred. This means : - user will need to explicitly set the subpath if they want to connect this to Github. - When finding matching projects it will only list the rill managed projects for that subpath.

func IsCommitHash added in v0.87.0

func IsCommitHash(s string) bool

IsCommitHash reports whether s is a full hex commit hash (SHA-1 or SHA-256). Use it to validate untrusted hashes before passing them as git CLI arguments: it rules out strings that git would interpret as flags or other revision syntax.

func IsGitRepo added in v0.87.0

func IsGitRepo(path string) bool

IsGitRepo reports whether path is inside a git working tree. Returns true for the repo root as well as any subdirectory of it.

func MergeWithBailOnConflict

func MergeWithBailOnConflict(path, branch string) (bool, error)

MergeWithBailOnConflict attempts to merge a branch into the current branch and aborts if there are conflicts. Returns true if merge was successful, false if there were conflicts (but abort succeeded). Returns an error if the merge failed for a reason other than conflicts, or if both merge and abort fail.

func MergeWithStrategy added in v0.87.0

func MergeWithStrategy(path, branch, strategy string) error

MergeWithStrategy merge a branch into the current branch using the specified strategy.

func NormalizeGithubRemote added in v0.88.0

func NormalizeGithubRemote(remote string) (string, error)

NormalizeGithubRemote validates and converts a Git remote to a normalized HTTPS Github URL ending in .git. Any credentials embedded in the remote are stripped.

func Pull added in v0.88.0

func Pull(ctx context.Context, path string, discardLocal bool, remote, remoteName string) (string, error)

Pull pulls the latest changes from the remote into the current branch. If discardLocal is true, local changes and local commits are stashed away first. If git rejects the pull (e.g. on divergent branches), the (credential-redacted) git error message is returned as the output with a nil error, so callers can surface it to users.

func Push added in v0.88.0

func Push(ctx context.Context, path, remote, refspec string) error

Push pushes the given refspec (commonly just a branch name) to the remote. remote may be a remote name or a URL, optionally with embedded credentials (which are passed as an argument only and redacted from any error).

func RemoveRemote added in v0.88.0

func RemoveRemote(path, remoteName string) error

RemoveRemote removes the named remote from the repository at path. It is a no-op if the remote does not exist.

func Revert added in v0.88.0

func Revert(ctx context.Context, path, subpath, remoteName, remoteBranch string, paths []string) ([]string, error)

Revert discards local changes for the given files, resetting them to the target-branch state, i.e. the merge base of HEAD and `<remoteName>/<remoteBranch>` (the same comparison ref used by ChangedFiles and Diff). It mirrors ChangedFiles: a file reverts to the state it has on the target branch, undoing committed, staged, and untracked local changes to it.

paths are subpath-relative, matching the paths returned by ChangedFiles. Paths that are not actually changed relative to the target branch are silently skipped. If paths is empty, all changed files are reverted. The subpath-relative paths that were reverted are returned.

The revert is applied per file according to its change status:

  • Modified/Deleted: restored from the merge base (content and index).
  • Added: removed from the index (if staged) and deleted from the working tree.
  • Renamed: the old path is restored from the merge base and the new path is removed.

func Run added in v0.87.0

func Run(ctx context.Context, path string, args ...string) (string, error)

Run executes a git command with the specified arguments in the given path and returns its output or an error. If path is empty, the command runs without -C (use for commands like `clone` that take an explicit destination). Use it to run one-off git commands that don't fit into the other helper functions in this package.

func SetRemote added in v0.88.0

func SetRemote(path string, config *Config) error

SetRemote sets the remote named config.RemoteName() for the repository at path to config.Remote. It is a no-op if the remote already has the wanted URL, or if it is not a Rill-managed remote (a user's own remote must never be overwritten).

func SplitGithubRemote added in v0.88.0

func SplitGithubRemote(remote string) (account, repo string, ok bool)

SplitGithubRemote splits a GitHub remote URL into a Github account and repository name.

func UpstreamMerge added in v0.88.0

func UpstreamMerge(ctx context.Context, path, remoteName, branch string, favourLocal bool) error

UpstreamMerge merges the remote tracking branch `<remoteName>/<branch>` into the current branch. If favourLocal is true, merge conflicts are resolved in favour of local changes.

Types

type ChangedFile added in v0.88.0

type ChangedFile struct {
	Path string
	// OldPath is the previous path; only set when Status is ChangedFileStatusRenamed.
	OldPath string
	Status  ChangedFileStatus
}

ChangedFile is a single file that differs from a comparison ref.

func ChangedFiles added in v0.88.0

func ChangedFiles(ctx context.Context, path, subpath, remoteName, remoteBranch string) ([]ChangedFile, error)

ChangedFiles returns the files that differ between the working tree at path and the comparison ref, i.e. the changes that would land on the ref if merged.

The diff is computed from the merge base of HEAD and the remote ref, not directly against the remote ref. This ensures that commits on the remote that have not been pulled locally do not appear as spurious inverse changes in the result.

Paths are returned relative to subpath (with the subpath prefix stripped). Uncommitted and untracked changes are included, since those are committed before a merge. Renames are reported with status Renamed and the old path, but only when git can detect them (a committed or staged rename); a rename that is still uncommitted in the working tree appears as a deleted old path plus an added new path, because git cannot pair an untracked new file with a deleted old one.

type ChangedFileStatus added in v0.88.0

type ChangedFileStatus int

ChangedFileStatus describes how a file changed relative to a comparison ref.

const (
	ChangedFileStatusUnspecified ChangedFileStatus = iota
	ChangedFileStatusAdded
	ChangedFileStatusModified
	ChangedFileStatusDeleted
	ChangedFileStatusRenamed
)

type Config added in v0.88.0

type Config struct {
	Remote            string
	Username          string
	Password          string
	PasswordExpiresAt time.Time
	DefaultBranch     string
	Subpath           string
	ManagedRepo       bool
}

Config describes a git remote and the (usually ephemeral) credentials to access it.

func (*Config) FullyQualifiedRemote added in v0.88.0

func (g *Config) FullyQualifiedRemote() (string, error)

FullyQualifiedRemote returns the remote URL with the credentials embedded in it. The result may be passed to git commands as an argument but must never be persisted to .git/config.

func (*Config) IsExpired added in v0.88.0

func (g *Config) IsExpired() bool

func (*Config) RemoteName added in v0.88.0

func (g *Config) RemoteName() string

type GitStatus added in v0.88.0

type GitStatus struct {
	Branch        string
	RemoteURL     string
	LocalChanges  bool // true if there are local changes (staged, unstaged, or untracked)
	LocalCommits  int32
	RemoteCommits int32
}

func Status added in v0.88.0

func Status(ctx context.Context, path, subpath, remoteName, remoteBranch string) (GitStatus, error)

Status returns the status of the git repo at path. If subpath is non-empty, local changes and ahead/behind counts are scoped to it. If remoteBranch is non-empty, ahead/behind counts compare the local branch against `<remoteName>/<remoteBranch>` instead of `<remoteName>/<localBranch>`.

type Remote added in v0.88.0

type Remote struct {
	Name string
	URL  string
}

Remote represents a Git remote with its name and URL.

func ExtractGitRemote added in v0.88.0

func ExtractGitRemote(projectPath, remoteName string, detectDotGit bool) (Remote, error)

ExtractGitRemote extracts the first Git remote from the Git repository at projectPath. If remoteName is provided, it will return the remote with that name. If detectDotGit is true, it will look for a .git directory in parent directories.

func ExtractRemotes added in v0.88.0

func ExtractRemotes(projectPath string, detectDotGit bool) ([]Remote, error)

ExtractRemotes extracts all Git remotes from the Git repository at projectPath. If detectDotGit is true, it will look for a .git directory in parent directories.

func (Remote) Github added in v0.88.0

func (r Remote) Github() (string, error)

Github returns a normalized HTTPS Github URL ending in .git for the remote.

type Signature added in v0.88.0

type Signature struct {
	Name  string
	Email string
}

Signature identifies the author of a git commit.

func UserSignature added in v0.88.0

func UserSignature(ctx context.Context, path string) (Signature, error)

UserSignature returns the git author configured for the repository at path, resolved from the combined local, global, and system git config. Returns an error if user.name or user.email is not configured.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL