Errors reference¶
Every error this module produces, what causes it, and what to do about it. Messages below are quoted as the module emits them.
Errors are created and wrapped with
cockroachdb/errors, so match with errors.Is
and errors.As, never by comparing strings. Three errors carry a hint, retrievable
with errors.FlattenHints(err).
Sentinel errors¶
Nine exported sentinels. All four are returned wrapped, so err == repo.ErrNoWorktree
will not match; errors.Is(err, repo.ErrNoWorktree) will.
| Sentinel | Message |
|---|---|
repo.ErrNoRepository |
repository not initialised; call Open, Clone, or SetRepo first |
repo.ErrNoWorktree |
worktree not initialised; call Open, Clone, or SetTree first |
repo.ErrAlreadyRepository |
path is already inside a git repository |
repo.ErrMissingGitDir |
git directory is missing or unusable |
repo.ErrTagAlreadyExists |
tag already exists |
repo.ErrInvalidCommit |
commit must be a full 40-character SHA |
repo.ErrMissingTagger |
an explicit tagger name and email are required |
repo.ErrMissingTagMessage |
an annotated tag requires a message |
repo.ErrMissingTagField |
missing required field |
Which method returns which sentinel¶
Every method has a defined behaviour when called before a repository is open. None of them panics. A mis-sequenced call is an error value you can handle.
| Method | Before Open* / Clone / InitLocal |
|---|---|
Commit, Checkout, CheckoutCommit, AddAll, WorkFS, WithWorkFS, WithTree |
ErrNoWorktree |
Push, WalkTree, FileExists, DirectoryExists, GetFile, CreateRemote, Remote, WithRepo |
ErrNoRepository |
CreateBranch |
ErrNoRepository, because it checks the repository first, then the worktree |
CreateTag |
ErrNoRepository. It needs no worktree: a tag names a commit, not a checkout |
AddToFS |
No sentinel. It reads no repository state; it copies the *object.File you hand it into the afero.Fs you hand it. |
SourceIs, SetSource, GetAuth, SetKey, SetBasicAuth, SetRepo, SetTree |
No error return; safe at any time |
SourceIs(repo.SourceUnknown) is true on a freshly constructed repository and stays true
until an Open*/Clone/InitLocal call sets SourceLocal or SourceMemory.
path is already inside a git repository¶
Returned by InitLocal, wrapped with the location:
InitLocal refuses to act on an existing repository so a scaffolding caller can tell
"newly initialised" from "already tracked". The probe walks upward, matching git's own
discovery semantics, so a subdirectory of a repository is also refused.
Match with errors.Is(err, repo.ErrAlreadyRepository). To ask the question without
attempting anything, call repo.DiscoverRepository(path), a read-only probe that
returns (true, nil) for the same input and creates nothing.
OpenLocal deliberately does not return this error: it opens an existing repository or
initialises a new one, conflating the two.
no <FORGE> token available for private repository¶
Returned by NewRepo when Private: true, token authentication is being attempted, and
no token resolves. The forge name is upper-cased into the message, and a hint is attached:
no GITLAB token available for private repository
HINT: Set GITLAB_TOKEN or configure gitlab.auth.env in your config to enable git operations
Fix it by supplying a working Token, or by dropping Private if the repository is in
fact public. Note that Private: true on its own produces no error at all. See
Private.
The hint names a config layout this module does not define
gitlab.auth.env is the shape phpboyscout tools use in their own config files. This
module reads no configuration, so treat the hint as a suggestion aimed at the host
application's users, not a key you can set here.
failed to get SSH key¶
Returned by NewRepo when SSH.Configured and SSH.HasKey are set, SSH.Path names
something, and the key cannot be loaded. The wrapped cause names the real problem:
| Wrapped cause | Meaning |
|---|---|
failed to get SSH key: stat /path/to/key: no such file or directory |
Nothing at SSH.Path. Check for an unexpanded ~, which neither this module nor KeyPath expands. The inner text is whatever Settings.FS reports, so an in-memory FS phrases it differently. |
could not open SSH key at '/path/to/key': path is a directory |
SSH.Path points at a directory. |
failed to get SSH key: ssh: no key found |
The file exists but is not a private key. |
SSH key '/path/to/key' is passphrase-protected |
See below. |
Keys are read through Settings.FS, so a test can supply them from
afero.NewMemMapFs().
SSH key '<path>' is passphrase-protected¶
Carries a hint:
HINT: Prompt for the passphrase and retry with GetSSHKeyWithPassphrase, or load the key into ssh-agent
This module never blocks on a terminal prompt, so an encrypted key is reported rather
than read. Detect it with errors.As against *ssh.PassphraseMissingError from
golang.org/x/crypto/ssh, prompt, and retry:
key, err := repo.GetSSHKeyWithPassphrase(path, fs, passphrase)
if err != nil {
return err
}
r.SetKey(key)
failed to create SSH auth¶
Returned by NewRepo when the ssh-agent fallback cannot reach an agent. With
SSH_AUTH_SOCK unset:
failed to create SSH auth: error creating SSH agent: "SSH agent requested but SSH_AUTH_SOCK not-specified"
The agent fallback is taken when SSH.Configured is true and any of the following holds:
SSH.HasKey is false, SSH.Type is exactly agent, or SSH.Path is empty. Each of the
first and third also emits a Warn on Settings.Logger.
unknown repo type: <value>¶
Returned by Open when repoType is neither repo.LocalRepo ("local") nor
repo.InMemoryRepo ("inmemory"). The comparison lower-cases the value, so "LOCAL"
and "InMemory" are accepted; the message echoes what you passed, uncased.
failed to open git repository at <path>¶
Returned by OpenLocal when go-git could not open the path and the reason was not
git.ErrRepositoryNotExists. Only that one cause triggers the init fallback; every
other open failure is returned wrapped.
That distinction is the point, and it is worth stating at its real size rather than
larger. What is guaranteed is that a .git redirect whose target has gone is
reported and never initialised over. See
git directory is missing or unusable below.
A .git directory is a different matter. If go-git reports it as no repository
(because HEAD is missing, say) the fallback initialises into it, which can complete a
partially-present repository rather than refusing it. That is not an oversight: it is
what git init does in the same directory, refs and all, and diverging from git there
would cost more than the guarantee is worth.
Two causes worth naming. A linked worktree
whose common directory has gone missing wraps go-git's ErrRepositoryIncomplete
(repository's commondir path does not exist). A .git symlink wraps
chroot boundary crossed whatever it points at, including a target inside the same
working tree, which is a go-git limitation rather than anything wrong with the
repository.
Both are reported here rather than triggering the fallback.
git directory is missing or unusable¶
Returned by OpenLocal, DiscoverRepository and InitLocal when the location holds a
.git file naming a git directory that is not there, or that holds no repository.
Match it with errors.Is(err, repo.ErrMissingGitDir).
/home/u/wt: .git names /home/u/project/.git/worktrees/wt: git directory is missing or unusable
HINT: The repository this worktree belongs to has moved or been pruned
The message names the git directory rather than only the location, because that is the part the caller did not supply and cannot see from where they are standing.
Three features put a .git file at a working directory and all three reach this error
identically: a linked worktree,
a --separate-git-dir checkout, and a submodule. The state arises when the repository
behind one is moved, pruned or re-cloned while the working directory survives:
git worktree removeorgit worktree prune, with the directory later restored from a backup or a sync client- the parent repository re-cloned, moved or deleted
- a worktree directory copied to another machine on its own
Nothing is written. The working directory is left exactly as it was, which is the whole point of reporting rather than initialising: it holds real work whose repository has gone missing.
DiscoverRepository returns this error rather than (false, nil). A directory whose
repository cannot be reached is not the same answer as "no repository here", and the
probe is documented as the safe way to ask.
Two neighbouring shapes do not produce it, and keep their own errors:
| Shape | Error |
|---|---|
.git file with no gitdir: prefix |
.git file has no gitdir: prefix, straight from go-git |
linked worktree whose commondir target is missing |
ErrRepositoryIncomplete, see above |
failed to initialise git repository / failed to initialize Git repository¶
Two spellings, from two different methods:
| Message | Method |
|---|---|
failed to initialise git repository |
InitLocal |
failed to initialize Git repository |
OpenLocal, on the init-if-absent fallback |
Both wrap go-git's PlainInitWithOptions failure, typically an unwritable parent
directory.
failed to create target directory¶
Returned by Clone when it cannot create targetPath. Clone creates the directory with
os.MkdirAll at mode 0755 before contacting the remote, on the operating-system
filesystem regardless of Settings.FS.
That example is a path whose parent component is a regular file.
failed to clone repository¶
Returned by Clone when go-git's clone fails. The wrapped cause is go-git's:
| Wrapped cause | Meaning |
|---|---|
repository not found |
Bad URL, or a private repository reached without usable credentials |
repository already exists |
targetPath already contains a git repository |
authentication required |
The remote wants credentials and none were configured |
Get "<url>/info/refs?service=git-upload-pack": context canceled |
The ctx you passed ended before the fetch finished |
A failed clone leaves the target directory behind, because Clone creates it first.
Remove it yourself before retrying, or the retry fails with repository already exists
once a partial repository is on disk.
failed to get HEAD reference: reference not found¶
Returned by every TreeReader method (WalkTree, FileExists, DirectoryExists and
GetFile) when the repository has no commits yet.
This is the usual surprise after OpenLocal or InitLocal on an empty directory: HEAD
is a symbolic reference to an unborn refs/heads/main, so there is no commit to resolve
a tree from. It is not a corruption and not a missing-repository error; the sentinel
checks passed.
Make a commit first, or check with DiscoverRepository and branch on it. The tree
readers read the HEAD commit's tree, never the staging area or the working directory,
so a file you have written but not committed also reports as absent.
The sibling messages failed to get HEAD commit and failed to get commit tree come
from the same resolution path, further along.
failed to get file from git: file not found¶
Returned by GetFile for a path not present in the HEAD tree.
FileExists does not return this: it treats object.ErrFileNotFound as
(false, nil). Use FileExists to ask, GetFile to fetch. DirectoryExists likewise
returns (false, nil) for an absent directory, and (true, nil) for "" or "/",
which name the tree root.
Errors that come straight from go-git¶
These are not wrapped by this module, so they arrive with go-git's own wording.
| Message | From | Cause |
|---|---|---|
author field is required |
Commit |
No commit identity. See below. |
reference not found |
Checkout, CheckoutCommit |
The branch or commit does not exist in this repository |
remote not found |
Push, CreateBranch |
No remote is configured. See below. |
already up-to-date |
CreateBranch |
Not returned. CreateBranch swallows git.NoErrAlreadyUpToDate |
Get "<url>/info/refs?service=git-upload-pack": context canceled |
Clone, OpenInMemory |
The context was cancelled mid-fetch |
author field is required¶
Commit(ctx, msg, nil) passes empty *git.CommitOptions to go-git, which fills the
author from git configuration: user.name and user.email, resolved across the system,
global and repository scopes. With none set anywhere, go-git refuses.
This bites hardest in containers and CI runners, where $HOME holds no .gitconfig. It
has nothing to do with Settings; this module supplies no identity of its own. Set one
explicitly instead of relying on ambient configuration:
hash, err := r.Commit(ctx, "docs: add readme", &git.CommitOptions{
Author: &object.Signature{
Name: "Release Bot",
Email: "bot@example.com",
When: time.Now(),
},
})
remote not found¶
Push needs a remote, and so does CreateBranch, but only in one case.
CreateBranch(ctx, name) checks whether the branch already exists. For a branch that is
new, it creates and checks it out, and touches no remote. For a branch that
already exists, on a non-in-memory repository, it then pulls to bring the branch up
to date, and that pull fails with remote not found if the repository has no remote.
So calling CreateBranch twice with the same name on a purely local repository succeeds
the first time and fails the second. Add a remote with CreateRemote, or use
Checkout(plumbing.NewBranchReferenceName(name)) to move to a branch that already
exists.
The pull is skipped entirely when SourceIs(repo.SourceMemory) is true.
Related¶
- Settings reference: what produces the construction-time errors
- Clone and commit: the ordering these errors enforce
- What this module does not do
Tagging¶
CreateTag refuses more than it accepts, and each refusal removes a way to
publish a tag that means something other than what was asked for.
commit must be a full 40-character SHA¶
ErrInvalidCommit. The commit is validated, never resolved: a caller reaching
CreateTag has already established which commit to tag, and re-resolving a
name would reopen the race that check closed.
An abbreviation is refused rather than expanded, and the reason is sharper than
it looks. plumbing.NewHash reports nothing for a bad value, and for the
likeliest mistake its result is the dangerous one:
| you pass | NewHash gives |
IsZero() |
|---|---|---|
main |
0000…0000 |
true |
0173e42 |
0173e40000000000000000000000000000000000 |
false |
An abbreviated SHA — what a person pastes, and what every forge UI shows — is
silently zero-padded into a different, valid-looking hash pointing at nothing.
A guard written as if hash.IsZero() lets it through.
an explicit tagger name and email are required¶
ErrMissingTagger. go-git reads the name and email from the machine's git
configuration when the tagger is nil, so the same release tagged from two
machines is attributed to two different people, and in CI to whatever identity
the runner image carries.
an annotated tag requires a message¶
ErrMissingTagMessage. This module creates annotated tags only. A lightweight
tag is a bare reference that records neither who made it nor when, which is the
wrong shape for the one artefact of a release that is permanent and public.
tag already exists¶
ErrTagAlreadyExists. A published tag has been fetched by consumers, and
moving it means two artefacts answer to one version. There is no force option;
a caller that genuinely needs one can reach WithRepo.