Fetch remote branch before switching to it
For the "push to branch" feature, the controller must either switch to the branch given, or create it starting at the checked-out HEAD. The func `switchBranch` encapsulates this decision -- but it assumes that if the branch exists at the remote, it will have been fetched when cloning, and this is not always true. In particular, cloning with go-git avoids fetching all refs: https://github.com/fluxcd/source-controller/blob/v0.11.0/pkg/git/gogit/checkout.go This commit adds a step to fetch the remote branch to a local branch, before attempting to switch to the local branch. This makes `switchBranch` a little simpler, and doesn't rely on any refs having been fetched ahead of time. Signed-off-by: Michael Bridgen <michael@weave.works>
This commit is contained in:
parent
ddd0a8d8ed
commit
40fb66a217
|
@ -33,6 +33,7 @@ import (
|
|||
libgit2 "github.com/libgit2/git2go/v31"
|
||||
|
||||
securejoin "github.com/cyphar/filepath-securejoin"
|
||||
"github.com/go-git/go-git/v5/config"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/go-logr/logr"
|
||||
|
@ -188,7 +189,11 @@ func (r *ImageUpdateAutomationReconciler) Reconcile(ctx context.Context, req ctr
|
|||
// When there's a push spec, the pushed-to branch is where commits
|
||||
// shall be made
|
||||
if auto.Spec.Push != nil {
|
||||
if err := switchBranch(repo, auto.Spec.Push.Branch); err != nil {
|
||||
pushBranch := auto.Spec.Push.Branch
|
||||
if err := fetch(ctx, tmp, repo, pushBranch, access, origin.Spec.GitImplementation); err != nil && err != errRemoteBranchMissing {
|
||||
return failWithError(err)
|
||||
}
|
||||
if err = switchBranch(repo, pushBranch); err != nil {
|
||||
return failWithError(err)
|
||||
}
|
||||
}
|
||||
|
@ -367,7 +372,6 @@ func (r *ImageUpdateAutomationReconciler) automationsForImagePolicy(obj client.O
|
|||
reqs[i].NamespacedName.Name = autoList.Items[i].GetName()
|
||||
reqs[i].NamespacedName.Namespace = autoList.Items[i].GetNamespace()
|
||||
}
|
||||
println("[DEBUG] enqueuing autos for image policy", obj.GetName(), obj.GetNamespace(), len(reqs))
|
||||
return reqs
|
||||
}
|
||||
|
||||
|
@ -409,6 +413,13 @@ func (r *ImageUpdateAutomationReconciler) getRepoAccess(ctx context.Context, rep
|
|||
return access, nil
|
||||
}
|
||||
|
||||
func (r repoAccess) remoteCallbacks() libgit2.RemoteCallbacks {
|
||||
return libgit2.RemoteCallbacks{
|
||||
CertificateCheckCallback: r.auth.CertCallback,
|
||||
CredentialsCallback: r.auth.CredCallback,
|
||||
}
|
||||
}
|
||||
|
||||
// cloneInto clones the upstream repository at the `branch` given,
|
||||
// using the git library indicated by `impl`. It returns a
|
||||
// `*gogit.Repository` regardless of the git library, since that is
|
||||
|
@ -431,11 +442,10 @@ func cloneInto(ctx context.Context, access repoAccess, branch, path, impl string
|
|||
// branch given. If the branch does not exist, it is created using the
|
||||
// head as the starting point.
|
||||
func switchBranch(repo *gogit.Repository, pushBranch string) error {
|
||||
remoteBranch := plumbing.NewRemoteReferenceName(originRemote, pushBranch)
|
||||
localBranch := plumbing.NewBranchReferenceName(pushBranch)
|
||||
|
||||
// is the remote branch already present?
|
||||
branchHead, err := repo.Reference(remoteBranch, false)
|
||||
// is the branch already present?
|
||||
_, err := repo.Reference(localBranch, false)
|
||||
switch {
|
||||
case err == plumbing.ErrReferenceNotFound:
|
||||
// make a new branch, starting at HEAD
|
||||
|
@ -450,17 +460,15 @@ func switchBranch(repo *gogit.Repository, pushBranch string) error {
|
|||
case err != nil:
|
||||
return err
|
||||
default:
|
||||
// make a local branch that references the remote branch
|
||||
branchRef := plumbing.NewHashReference(localBranch, branchHead.Hash())
|
||||
if err = repo.Storer.SetReference(branchRef); err != nil {
|
||||
return err
|
||||
}
|
||||
// local branch found, great
|
||||
break
|
||||
}
|
||||
|
||||
tree, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tree.Checkout(&gogit.CheckoutOptions{
|
||||
Branch: localBranch,
|
||||
})
|
||||
|
@ -540,6 +548,62 @@ func (r *ImageUpdateAutomationReconciler) getSigningEntity(ctx context.Context,
|
|||
return entities[0], nil
|
||||
}
|
||||
|
||||
var errRemoteBranchMissing = errors.New("remote branch missing")
|
||||
|
||||
// fetch gets the remote branch given and updates the local branch
|
||||
// head of the same name, so it can be switched to. If the fetch
|
||||
// completes, it returns nil; if the remote branch is missing, it
|
||||
// returns errRemoteBranchMissing (this is to work in sympathy with
|
||||
// `switchBranch`, which will create the branch if it doesn't
|
||||
// exist). For any other problem it will return the error.
|
||||
func fetch(ctx context.Context, path string, repo *gogit.Repository, branch string, access repoAccess, impl string) error {
|
||||
refspec := fmt.Sprintf("refs/heads/%s:refs/heads/%s", branch, branch)
|
||||
switch impl {
|
||||
case sourcev1.LibGit2Implementation:
|
||||
lg2repo, err := libgit2.OpenRepository(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fetchLibgit2(lg2repo, refspec, access)
|
||||
case sourcev1.GoGitImplementation:
|
||||
return fetchGoGit(ctx, repo, refspec, access)
|
||||
default:
|
||||
return fmt.Errorf("unknown git implementation %q", impl)
|
||||
}
|
||||
}
|
||||
|
||||
func fetchLibgit2(repo *libgit2.Repository, refspec string, access repoAccess) error {
|
||||
origin, err := repo.Remotes.Lookup(originRemote)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = origin.Fetch(
|
||||
[]string{refspec},
|
||||
&libgit2.FetchOptions{
|
||||
RemoteCallbacks: access.remoteCallbacks(),
|
||||
}, "",
|
||||
)
|
||||
if err != nil && libgit2.IsErrorCode(err, libgit2.ErrorCodeNotFound) {
|
||||
return errRemoteBranchMissing
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func fetchGoGit(ctx context.Context, repo *gogit.Repository, refspec string, access repoAccess) error {
|
||||
err := repo.FetchContext(ctx, &gogit.FetchOptions{
|
||||
RemoteName: originRemote,
|
||||
RefSpecs: []config.RefSpec{config.RefSpec(refspec)},
|
||||
Auth: access.auth.AuthMethod,
|
||||
})
|
||||
if err == gogit.NoErrAlreadyUpToDate {
|
||||
return nil
|
||||
}
|
||||
if _, ok := err.(gogit.NoMatchingRefSpecError); ok {
|
||||
return errRemoteBranchMissing
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// push pushes the branch given to the origin using the git library
|
||||
// indicated by `impl`. It's passed both the path to the repo and a
|
||||
// gogit.Repository value, since the latter may as well be used if the
|
||||
|
@ -553,15 +617,18 @@ func push(ctx context.Context, path string, repo *gogit.Repository, branch strin
|
|||
}
|
||||
return pushLibgit2(lg2repo, access, branch)
|
||||
case sourcev1.GoGitImplementation:
|
||||
return pushGoGit(ctx, repo, access)
|
||||
return pushGoGit(ctx, repo, access, branch)
|
||||
default:
|
||||
return fmt.Errorf("unknown git implementation %q", impl)
|
||||
}
|
||||
}
|
||||
|
||||
func pushGoGit(ctx context.Context, repo *gogit.Repository, access repoAccess) error {
|
||||
func pushGoGit(ctx context.Context, repo *gogit.Repository, access repoAccess, branch string) error {
|
||||
refspec := config.RefSpec(fmt.Sprintf("refs/heads/%s:refs/heads/%s", branch, branch))
|
||||
err := repo.PushContext(ctx, &gogit.PushOptions{
|
||||
Auth: access.auth.AuthMethod,
|
||||
RemoteName: originRemote,
|
||||
Auth: access.auth.AuthMethod,
|
||||
RefSpecs: []config.RefSpec{refspec},
|
||||
})
|
||||
return gogitPushError(err)
|
||||
}
|
||||
|
@ -590,10 +657,7 @@ func pushLibgit2(repo *libgit2.Repository, access repoAccess, branch string) err
|
|||
return err
|
||||
}
|
||||
err = origin.Push([]string{fmt.Sprintf("refs/heads/%s:refs/heads/%s", branch, branch)}, &libgit2.PushOptions{
|
||||
RemoteCallbacks: libgit2.RemoteCallbacks{
|
||||
CertificateCheckCallback: access.auth.CertCallback,
|
||||
CredentialsCallback: access.auth.CredCallback,
|
||||
},
|
||||
RemoteCallbacks: access.remoteCallbacks(),
|
||||
})
|
||||
return libgit2PushError(err)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue