Pull an existing clone¶
Clone once, then call Pull to bring the clone up to date. Treat
git.NoErrAlreadyUpToDate as success, and re-clone on any other error:
err := r.Pull(ctx, nil)
switch {
case err == nil:
// the branch fast-forwarded
case errors.Is(err, git.NoErrAlreadyUpToDate):
// nothing new: the common case
default:
// refused, or failed: discard the directory and clone again
}
Pull makes one network round trip, uses the credentials the repository already holds
(SSH included), and only ever fast-forwards. Every refusal leaves HEAD, the branch and the
working tree as they were, so a refused clone is safe to inspect before you replace it.
Throughout, ctx is a context.Context you supply. See
context and cancellation.
Clone once, pull after that¶
A job that refreshes many repositories on a schedule should not re-clone each one every time. Re-cloning costs a full transfer per repository per run, and on a forge with request limits it is what gets throttled first.
r, err := repo.NewRepo(repo.Settings{
Forge: repo.ForgeGitLab,
SSH: repo.SSHSettings{Configured: true, HasKey: true, Type: "agent"},
})
if err != nil {
return err
}
if _, err := os.Stat(filepath.Join(dir, ".git")); err != nil {
_, _, err = r.Clone(ctx, url, dir)
return err
}
if _, _, err := r.OpenLocal(ctx, dir); err != nil {
return err
}
err = r.Pull(ctx, nil)
if errors.Is(err, git.NoErrAlreadyUpToDate) {
return nil
}
return err
OpenLocal initialises an empty repository where there is none, so check for the clone
before opening it. Pulling a freshly initialised repository fails with
reference not found, because it has no commits.
Re-clone on any refusal¶
Each refusal is a sentinel. For a clone that nothing edits between pulls, every one of them means the same thing, which is that the clone no longer matches what you expect, and the answer is a fresh clone.
| Return | When | What changed |
|---|---|---|
| nil | the branch fast-forwarded | HEAD, the branch, the tree, the tracking ref |
git.NoErrAlreadyUpToDate |
nothing new, or the clone is ahead of its remote | the tracking ref, if the fetch moved it |
ErrUnsupportedPullOption |
an option Pull does not honour |
nothing |
ErrNoRepository, ErrNoWorktree |
not opened, or no worktree | nothing |
ErrDetachedHead |
HEAD is not a branch | nothing |
ErrWorktreeNotClean |
any local change, untracked files included | nothing, or the tracking ref if found after the fetch |
ErrRemoteBranchMissing |
the remote has no such branch | nothing |
ErrNotFastForward |
diverged, or ancestry unprovable on a shallow clone | the tracking ref only |
wraps plumbing.ErrReferenceNotFound |
unborn HEAD | nothing |
wraps git.ErrRemoteNotFound |
no such remote | nothing |
| a wrapped transport error | the fetch failed, including a cancelled context | nothing, or the tracking ref |
| the context's error | cancelled after the fetch, before the reset | the tracking ref only |
a wrapped error from Reset |
writing the index or a file failed | HEAD and the branch; the tree is not trustworthy |
The last row is the one exception to "nothing moved". It is a disk failure, not a
refusal: read Head() to see where HEAD is, and re-clone. The
errors reference explains each sentinel.
A local change refuses the pull¶
Any modified, staged, deleted or untracked file refuses the pull with
ErrWorktreeNotClean, whichever paths upstream changed. Ignored files do not count. That
is stricter than git pull --ff-only, deliberately, so the same local state always gets
the same answer. Pull is stricter than git
has the comparison.
If your program writes into the clone, write build output somewhere .gitignore covers,
or outside the clone.
Pull from another remote or branch¶
RemoteName picks the remote, and ReferenceName the remote branch, given in full:
err := r.Pull(ctx, &git.PullOptions{
RemoteName: "upstream",
ReferenceName: plumbing.NewBranchReferenceName("release"),
})
That is git pull --ff-only upstream release: the checked-out branch fast-forwards to
upstream/release, and refs/remotes/upstream/release is written. An empty
ReferenceName means the remote branch with the same name as the checked-out branch,
which is not go-git's default (the remote's HEAD).
Pull refuses Force, a non-zero Depth, a RemoteURL, RecurseSubmodules, and a
ReferenceName outside refs/heads/, rather than ignoring them. To pull from a URL, add a
remote for it with CreateRemote on *Repo, or through WithRepo on a ThreadSafeRepo.
Pull does not fetch tags. A clone made with WithNoTags stays without them.
Fetch without pulling¶
Fetch updates the remote-tracking refs and touches nothing else:
It is a thin wrapper over go-git's fetch: it fills Auth and Progress from the
repository when unset, refuses nothing go-git accepts, and fetches tags as go-git does by
default. Nothing new is git.NoErrAlreadyUpToDate.
Both Fetch and Pull work on a copy of the options you pass, so one options value is
safe to reuse across repositories.
Related¶
- Clone and commit
- Authenticate to a forge
- Errors reference
- Spec 0009:
why
Pullbehaves as it does