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"
|
libgit2 "github.com/libgit2/git2go/v31"
|
||||||
|
|
||||||
securejoin "github.com/cyphar/filepath-securejoin"
|
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"
|
||||||
"github.com/go-git/go-git/v5/plumbing/object"
|
"github.com/go-git/go-git/v5/plumbing/object"
|
||||||
"github.com/go-logr/logr"
|
"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
|
// When there's a push spec, the pushed-to branch is where commits
|
||||||
// shall be made
|
// shall be made
|
||||||
if auto.Spec.Push != nil {
|
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)
|
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.Name = autoList.Items[i].GetName()
|
||||||
reqs[i].NamespacedName.Namespace = autoList.Items[i].GetNamespace()
|
reqs[i].NamespacedName.Namespace = autoList.Items[i].GetNamespace()
|
||||||
}
|
}
|
||||||
println("[DEBUG] enqueuing autos for image policy", obj.GetName(), obj.GetNamespace(), len(reqs))
|
|
||||||
return reqs
|
return reqs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -409,6 +413,13 @@ func (r *ImageUpdateAutomationReconciler) getRepoAccess(ctx context.Context, rep
|
||||||
return access, nil
|
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,
|
// cloneInto clones the upstream repository at the `branch` given,
|
||||||
// using the git library indicated by `impl`. It returns a
|
// using the git library indicated by `impl`. It returns a
|
||||||
// `*gogit.Repository` regardless of the git library, since that is
|
// `*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
|
// branch given. If the branch does not exist, it is created using the
|
||||||
// head as the starting point.
|
// head as the starting point.
|
||||||
func switchBranch(repo *gogit.Repository, pushBranch string) error {
|
func switchBranch(repo *gogit.Repository, pushBranch string) error {
|
||||||
remoteBranch := plumbing.NewRemoteReferenceName(originRemote, pushBranch)
|
|
||||||
localBranch := plumbing.NewBranchReferenceName(pushBranch)
|
localBranch := plumbing.NewBranchReferenceName(pushBranch)
|
||||||
|
|
||||||
// is the remote branch already present?
|
// is the branch already present?
|
||||||
branchHead, err := repo.Reference(remoteBranch, false)
|
_, err := repo.Reference(localBranch, false)
|
||||||
switch {
|
switch {
|
||||||
case err == plumbing.ErrReferenceNotFound:
|
case err == plumbing.ErrReferenceNotFound:
|
||||||
// make a new branch, starting at HEAD
|
// make a new branch, starting at HEAD
|
||||||
|
@ -450,17 +460,15 @@ func switchBranch(repo *gogit.Repository, pushBranch string) error {
|
||||||
case err != nil:
|
case err != nil:
|
||||||
return err
|
return err
|
||||||
default:
|
default:
|
||||||
// make a local branch that references the remote branch
|
// local branch found, great
|
||||||
branchRef := plumbing.NewHashReference(localBranch, branchHead.Hash())
|
break
|
||||||
if err = repo.Storer.SetReference(branchRef); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tree, err := repo.Worktree()
|
tree, err := repo.Worktree()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return tree.Checkout(&gogit.CheckoutOptions{
|
return tree.Checkout(&gogit.CheckoutOptions{
|
||||||
Branch: localBranch,
|
Branch: localBranch,
|
||||||
})
|
})
|
||||||
|
@ -540,6 +548,62 @@ func (r *ImageUpdateAutomationReconciler) getSigningEntity(ctx context.Context,
|
||||||
return entities[0], nil
|
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
|
// 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
|
// 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
|
// 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)
|
return pushLibgit2(lg2repo, access, branch)
|
||||||
case sourcev1.GoGitImplementation:
|
case sourcev1.GoGitImplementation:
|
||||||
return pushGoGit(ctx, repo, access)
|
return pushGoGit(ctx, repo, access, branch)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown git implementation %q", impl)
|
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{
|
err := repo.PushContext(ctx, &gogit.PushOptions{
|
||||||
Auth: access.auth.AuthMethod,
|
RemoteName: originRemote,
|
||||||
|
Auth: access.auth.AuthMethod,
|
||||||
|
RefSpecs: []config.RefSpec{refspec},
|
||||||
})
|
})
|
||||||
return gogitPushError(err)
|
return gogitPushError(err)
|
||||||
}
|
}
|
||||||
|
@ -590,10 +657,7 @@ func pushLibgit2(repo *libgit2.Repository, access repoAccess, branch string) err
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = origin.Push([]string{fmt.Sprintf("refs/heads/%s:refs/heads/%s", branch, branch)}, &libgit2.PushOptions{
|
err = origin.Push([]string{fmt.Sprintf("refs/heads/%s:refs/heads/%s", branch, branch)}, &libgit2.PushOptions{
|
||||||
RemoteCallbacks: libgit2.RemoteCallbacks{
|
RemoteCallbacks: access.remoteCallbacks(),
|
||||||
CertificateCheckCallback: access.auth.CertCallback,
|
|
||||||
CredentialsCallback: access.auth.CredCallback,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
return libgit2PushError(err)
|
return libgit2PushError(err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue