On this page
Package testutil provides shared test helpers for creating temporary git repos, used across internal/*_test.go packages to avoid duplicating boilerplate.
#internal/testutil
#internal/testutil
Package testutil provides shared test helpers for creating temporary git repos, used across internal/*_test.go packages to avoid duplicating boilerplate.
This package intentionally does NOT import internal/repo (or any other internal/* package) to avoid import cycles -- internal/git is at the bottom of the dependency graph and its tests need these helpers too.
#PathologicalCommName
const PathologicalCommName = "sg (weird) name"PathologicalCommName is an executable basename that reproduces the /proc/
#IdentityName
const IdentityName = "Test"The deterministic identity every repo these helpers build commits under.
#IdentityEmail
const IdentityEmail = "test@test.com"#Process
type Process structProcess is a child process spawned by a test, used by suites that need a PID whose liveness they control.
#SubmoduleRepo
type SubmoduleRepo structSubmoduleRepo holds paths for a test repo with a submodule.
#Git
func Git(t *testing.T, dir string, args ...string) stringGit runs git in dir, fails the test on a nonzero exit, and returns stdout and stderr interleaved (combined output) with surrounding whitespace trimmed. Because the result can carry stderr, do not feed it back to git; use GitOut for a value that has to be exact.
#GitRaw
func GitRaw(t *testing.T, dir string, args ...string) stringGitRaw is Git without the trim: stdout and stderr interleaved, exactly as git wrote them. Use it wherever the trailing newline (or its absence) is part of what the test asserts.
#GitOut
func GitOut(t *testing.T, dir string, args ...string) stringGitOut runs git in dir, fails the test on a nonzero exit, and returns stdout verbatim -- stderr never enters the result, so it is the runner to use for a value the test hands back to git. On failure the fatal message carries git's stderr, which is where the explanation lives.
#GitTry
func GitTry(t *testing.T, dir string, args ...string) (string, int)GitTry runs git in dir and returns its combined output (stdout and stderr interleaved, verbatim) and exit code without failing the test: fixtures routinely depend on commands that exit nonzero by design (a conflicting merge, a refused amend), and the refusal itself is on stderr. Only a failure to start the process at all fails the test.
#GitTryEnv
func GitTryEnv(t *testing.T, dir string, extraEnv []string, args ...string) (string, int)GitTryEnv is GitTry with extra "KEY=value" entries appended to the inherited environment -- needed for calls that would otherwise open an editor. Same combined-output contract as GitTry.
#GitStdin
func GitStdin(t *testing.T, dir, stdin string, args ...string) stringGitStdin runs git in dir with stdin data, fails the test on a nonzero exit, and returns trimmed stdout -- stderr is inherited and lands in the test's own output rather than in the result, so the result is safe to feed back to git.
#GitTryOut
func GitTryOut(t *testing.T, dir string, args ...string) (string, bool)GitTryOut runs git in dir and returns its verbatim stdout -- stderr is discarded -- plus whether git exited zero. Use it to probe for something that may legitimately be absent.
#WriteFile
func WriteFile(t *testing.T, dir, rel, content string)WriteFile writes content at a repo-relative path, creating parent directories. It is the one spelling for "put this file in the repo".
#WriteFileAt
func WriteFileAt(t *testing.T, path, content string)WriteFileAt is WriteFile for a path the caller has already joined.
#TreePaths
func TreePaths(t *testing.T, dir, rev string) []stringTreePaths returns every path in a revision's tree, repo-relative and recursive. --full-tree keeps the answer independent of the directory git is invoked from. An empty tree yields nil.
#SplitLines
func SplitLines(s string) []stringSplitLines splits s on newlines, dropping empty lines. An empty string yields nil, so a caller can range over the result without a length check.
#Contains
func Contains(haystack []string, needle string) boolContains reports whether haystack holds needle.
#Rev
func Rev(t *testing.T, dir, rev string) stringRev resolves a revision to its full SHA, failing the test when it does not resolve. Read from stdout only, like RevTry: a SHA a test compares or hands back to git must never have a stderr line mixed into it.
#RevTry
func RevTry(t *testing.T, dir, rev string) stringRevTry resolves a revision to its full SHA, returning "" when the revision does not exist (an unborn branch, a ref another test has yet to create).
#Parents
func Parents(t *testing.T, dir, ref string) []stringParents returns the parent SHAs of a commit, in order, read from stdout only for the same reason as Rev. A commit that cannot be read at all fails the test; a root commit yields an empty slice.
#Show
func Show(t *testing.T, dir, rev, path string) (string, bool)Show returns the verbatim content of a repo-relative path at rev, and whether that path exists in that revision.
#MustShow
func MustShow(t *testing.T, dir, rev, path string) stringMustShow is Show for a path the test requires to be present.
#GitDir
func GitDir(t *testing.T, dir string) stringGitDir is git's own answer to where this checkout's git directory is, rather than the
#MergeStateGone
func MergeStateGone(t *testing.T, dir string) boolMergeStateGone reports whether git considers a merge concluded, i.e. whether MERGE_HEAD is absent from the git directory.
#AssertMergeHead
func AssertMergeHead(t *testing.T, dir, want, context string)AssertMergeHead fails unless the repository is mid-merge with MERGE_HEAD naming want. context names the moment being asserted, so a failure says which step of a fixture or which post-condition broke.
#FileExists
func FileExists(path string) boolFileExists reports whether path exists.
#SpawnSleeper
func SpawnSleeper(t *testing.T) *ProcessSpawnSleeper starts a long-lived child process and returns a handle to it. The process is killed when the test ends unless the test kills it earlier.
#SpawnPathologicalNameSleeper
func SpawnPathologicalNameSleeper(t *testing.T) *ProcessSpawnPathologicalNameSleeper starts a long-lived child process whose executable basename -- and therefore its /proc comm field -- is PathologicalCommName.
#InitRepoWithSubmodule
func InitRepoWithSubmodule(t *testing.T) SubmoduleRepoInitRepoWithSubmodule creates a test repo containing one submodule.
#InitRepoWithTwoSubmodules
func InitRepoWithTwoSubmodules(t *testing.T) (SubmoduleRepo, SubmoduleRepo)InitRepoWithTwoSubmodules creates a test repo containing two submodules.
#InitRepo
func InitRepo(t *testing.T, safegitInit func(ctx context.Context, gitDir string) error) (repoDir, gitDir, safegitDir string)InitRepo creates a temp git repo with a seed file ("seed.txt") and initial commit, runs the provided safegitInit function to set up .git/safegit/, and returns (repoDir, gitDir, safegitDir).
Callers pass repo.Init as safegitInit:
dir, gitDir, sgDir := testutil.InitRepo(t, repo.Init)
#InitUnbornRepo
func InitUnbornRepo(t *testing.T) (repoDir, gitDir string)InitUnbornRepo creates a temp git repo with NO commits at all, so HEAD names a branch that does not exist yet -- the state a repository is in between git init and its first commit. Returns (repoDir, gitDir).
It is the fixture for everything that has to keep working before there is a HEAD to resolve: git rev-parse HEAD fails there, and so does every git command that takes HEAD as a treeish.
#InitBareRepo
func InitBareRepo(t *testing.T) stringInitBareRepo creates a temp git repo with an allow-empty initial commit (no seed file, no safegit init). Returns the repo directory. Suitable for packages like git and index that don't need safegit infrastructure.
#Chdir
func Chdir(t *testing.T, dir string)Chdir changes into dir for the duration of the test, restoring the original working directory on cleanup. A failed restore fails the test: leaving a suite in the wrong directory corrupts every test after it.
#Process.Kill
func (p *Process) Kill(t *testing.T)Kill terminates the process and reaps it, so the PID is genuinely dead (not a zombie) when Kill returns. It is safe to call more than once.