Skip to content

Commit 5ee6446

Browse files
author
Sanskar Jaiswal
committed
fix regression in switchToBranch
Fixes regression in which we fail to push to a branch after switching to a branch, if origin is ahead of local. Fixed by setting the upstream commit as the local branch target. Regression introduced in #330, and partially addressed in #369. Signed-off-by: Sanskar Jaiswal <[email protected]>
1 parent 74420bd commit 5ee6446

File tree

3 files changed

+123
-18
lines changed

3 files changed

+123
-18
lines changed

controllers/git_test.go

Lines changed: 96 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import (
1010

1111
"github.com/go-logr/logr"
1212
libgit2 "github.com/libgit2/git2go/v33"
13+
"k8s.io/apimachinery/pkg/types"
14+
15+
. "github.com/onsi/gomega"
1316

1417
"github.com/fluxcd/pkg/gittestserver"
1518
"github.com/fluxcd/source-controller/pkg/git/libgit2/managed"
@@ -129,12 +132,12 @@ func TestPushRejected(t *testing.T) {
129132
}
130133

131134
// this is currently defined in update_test.go, but handy right here ..
132-
if err = initGitRepo(gitServer, "testdata/appconfig", "master", "/appconfig.git"); err != nil {
135+
if err = initGitRepo(gitServer, "testdata/appconfig", "test", "/appconfig.git"); err != nil {
133136
t.Fatal(err)
134137
}
135138

136139
repoURL := gitServer.HTTPAddressWithCredentials() + "/appconfig.git"
137-
repo, err := clone(repoURL, "master")
140+
repo, err := clone(repoURL, "test")
138141
if err != nil {
139142
t.Fatal(err)
140143
}
@@ -148,7 +151,7 @@ func TestPushRejected(t *testing.T) {
148151
repo.Remotes.SetUrl("origin", transportOptsURL)
149152

150153
// This is here to guard against push in general being broken
151-
err = push(context.TODO(), repo.Workdir(), "master", repoAccess{})
154+
err = push(context.TODO(), repo.Workdir(), "test", repoAccess{})
152155
if err != nil {
153156
t.Fatal(err)
154157
}
@@ -165,3 +168,93 @@ func TestPushRejected(t *testing.T) {
165168
t.Error("push to a forbidden branch is expected to fail, but succeeded")
166169
}
167170
}
171+
172+
func Test_switchToBranch(t *testing.T) {
173+
g := NewWithT(t)
174+
gitServer, err := gittestserver.NewTempGitServer()
175+
g.Expect(err).ToNot(HaveOccurred())
176+
gitServer.AutoCreate()
177+
g.Expect(gitServer.StartHTTP()).To(Succeed())
178+
179+
branch := "test"
180+
g.Expect(initGitRepo(gitServer, "testdata/appconfig", branch, "/appconfig.git")).To(Succeed())
181+
182+
repoURL := gitServer.HTTPAddressWithCredentials() + "/appconfig.git"
183+
repo, err := clone(repoURL, branch)
184+
g.Expect(err).ToNot(HaveOccurred())
185+
defer repo.Free()
186+
187+
head, err := repo.Head()
188+
g.Expect(err).ToNot(HaveOccurred())
189+
defer head.Free()
190+
target := head.Target()
191+
192+
// register transport options and update remote to transport url
193+
transportOptsURL := "http://" + randStringRunes(5)
194+
managed.AddTransportOptions(transportOptsURL, managed.TransportOptions{
195+
TargetURL: repoURL,
196+
})
197+
defer managed.RemoveTransportOptions(transportOptsURL)
198+
repo.Remotes.SetUrl("origin", transportOptsURL)
199+
200+
// calling switchToBranch with a branch that doesn't exist on origin
201+
// should result in the branch being created and switched to.
202+
branch = "not-on-origin"
203+
switchToBranch(repo, context.TODO(), branch, repoAccess{})
204+
205+
head, err = repo.Head()
206+
g.Expect(err).ToNot(HaveOccurred())
207+
name, err := head.Branch().Name()
208+
g.Expect(err).ToNot(HaveOccurred())
209+
g.Expect(name).To(Equal(branch))
210+
211+
cc, err := repo.LookupCommit(head.Target())
212+
g.Expect(err).ToNot(HaveOccurred())
213+
defer cc.Free()
214+
g.Expect(cc.Id().String()).To(Equal(target.String()))
215+
216+
// create a branch with the HEAD commit and push it to origin
217+
branch = "exists-on-origin"
218+
_, err = repo.CreateBranch(branch, cc, false)
219+
g.Expect(err).ToNot(HaveOccurred())
220+
origin, err := repo.Remotes.Lookup("origin")
221+
g.Expect(err).ToNot(HaveOccurred())
222+
defer origin.Free()
223+
224+
g.Expect(origin.Push(
225+
[]string{fmt.Sprintf("refs/heads/%s:refs/heads/%s", branch, branch)}, &libgit2.PushOptions{},
226+
)).To(Succeed())
227+
228+
// push a new commit to the branch. this is done to test whether we properly
229+
// sync our local branch with the remote branch, before switching.
230+
policyKey := types.NamespacedName{
231+
Name: "policy",
232+
Namespace: "ns",
233+
}
234+
commitID := commitInRepo(g, repoURL, branch, "Install setter marker", func(tmp string) {
235+
g.Expect(replaceMarker(tmp, policyKey)).To(Succeed())
236+
})
237+
238+
// calling switchToBranch with a branch that exists should make sure to fetch latest
239+
// for that branch from origin, and then switch to it.
240+
switchToBranch(repo, context.TODO(), branch, repoAccess{})
241+
head, err = repo.Head()
242+
g.Expect(err).ToNot(HaveOccurred())
243+
name, err = head.Branch().Name()
244+
245+
g.Expect(err).ToNot(HaveOccurred())
246+
g.Expect(name).To(Equal(branch))
247+
g.Expect(head.Target().String()).To(Equal(commitID.String()))
248+
249+
// push a commit after switching to the branch, to check if the local
250+
// branch is synced with origin.
251+
replaceMarker(repo.Workdir(), policyKey)
252+
sig := &libgit2.Signature{
253+
Name: "Testbot",
254+
255+
When: time.Now(),
256+
}
257+
_, err = commitWorkDir(repo, branch, "update policy", sig)
258+
g.Expect(err).ToNot(HaveOccurred())
259+
g.Expect(push(context.TODO(), repo.Workdir(), branch, repoAccess{})).To(Succeed())
260+
}

controllers/imageupdateautomation_controller.go

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -749,7 +749,6 @@ func switchToBranch(repo *libgit2.Repository, ctx context.Context, branch string
749749
callbacks = managed.RemoteCallbacks()
750750
}
751751

752-
branchRef := fmt.Sprintf("origin/%s", branch)
753752
// Force the fetching of the remote branch.
754753
err = origin.Fetch([]string{branch}, &libgit2.FetchOptions{
755754
RemoteCallbacks: callbacks,
@@ -758,7 +757,7 @@ func switchToBranch(repo *libgit2.Repository, ctx context.Context, branch string
758757
return fmt.Errorf("cannot fetch remote branch: %w", err)
759758
}
760759

761-
remoteBranch, err := repo.LookupBranch(branchRef, libgit2.BranchRemote)
760+
remoteBranch, err := repo.References.Lookup(fmt.Sprintf("refs/remotes/origin/%s", branch))
762761
if err != nil && !libgit2.IsErrorCode(err, libgit2.ErrorCodeNotFound) {
763762
return err
764763
}
@@ -785,15 +784,25 @@ func switchToBranch(repo *libgit2.Repository, ctx context.Context, branch string
785784
}
786785
defer commit.Free()
787786

788-
localBranch, err := repo.LookupBranch(branch, libgit2.BranchLocal)
787+
localBranch, err := repo.References.Lookup(fmt.Sprintf("refs/heads/%s", branch))
789788
if err != nil && !libgit2.IsErrorCode(err, libgit2.ErrorCodeNotFound) {
790789
return fmt.Errorf("cannot lookup branch '%s': %w", branch, err)
791790
}
792791
if localBranch == nil {
793-
localBranch, err = repo.CreateBranch(branch, commit, false)
794-
}
795-
if localBranch == nil {
796-
return fmt.Errorf("cannot create local branch '%s': %w", branch, err)
792+
lb, err := repo.CreateBranch(branch, commit, false)
793+
if err != nil {
794+
return fmt.Errorf("cannot create branch '%s': %w", branch, err)
795+
}
796+
defer lb.Free()
797+
// We could've done something like:
798+
// localBranch = lb.Reference
799+
// But for some reason, calling `lb.Free()` AND using it, causes a really
800+
// nasty crash. Since, we can't avoid calling `lb.Free()`, in order to prevent
801+
// memory leaks, we don't use `lb` and instead manually lookup the ref.
802+
localBranch, err = repo.References.Lookup(fmt.Sprintf("refs/heads/%s", branch))
803+
if err != nil {
804+
return fmt.Errorf("cannot lookup branch '%s': %w", branch, err)
805+
}
797806
}
798807
defer localBranch.Free()
799808

@@ -811,6 +820,12 @@ func switchToBranch(repo *libgit2.Repository, ctx context.Context, branch string
811820
return fmt.Errorf("cannot checkout tree for branch '%s': %w", branch, err)
812821
}
813822

823+
ref, err := localBranch.SetTarget(commit.Id(), "")
824+
if err != nil {
825+
return fmt.Errorf("cannot update branch '%s' to be at target commit: %w", branch, err)
826+
}
827+
ref.Free()
828+
814829
return repo.SetHead("refs/heads/" + branch)
815830
}
816831

controllers/update_test.go

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -904,7 +904,7 @@ func configureManagedTransportOptions(repo *libgit2.Repository, repoURL string)
904904
}, nil
905905
}
906906

907-
func commitInRepo(g *WithT, repoURL, branch, msg string, changeFiles func(path string)) {
907+
func commitInRepo(g *WithT, repoURL, branch, msg string, changeFiles func(path string)) *libgit2.Oid {
908908
repo, err := clone(repoURL, branch)
909909
g.Expect(err).ToNot(HaveOccurred())
910910
defer repo.Free()
@@ -916,21 +916,18 @@ func commitInRepo(g *WithT, repoURL, branch, msg string, changeFiles func(path s
916916
917917
When: time.Now(),
918918
}
919-
_, err = commitWorkDir(repo, branch, msg, sig)
919+
id, err := commitWorkDir(repo, branch, msg, sig)
920920
g.Expect(err).ToNot(HaveOccurred())
921921

922922
cleanup, err := configureManagedTransportOptions(repo, repoURL)
923-
if err != nil {
924-
panic(err)
925-
}
923+
g.Expect(err).ToNot(HaveOccurred())
926924
defer cleanup()
927925
origin, err := repo.Remotes.Lookup(originRemote)
928-
if err != nil {
929-
panic(fmt.Errorf("cannot find origin: %v", err))
930-
}
926+
g.Expect(err).ToNot(HaveOccurred())
931927
defer origin.Free()
932928

933929
g.Expect(origin.Push([]string{branchRefName(branch)}, &libgit2.PushOptions{})).To(Succeed())
930+
return id
934931
}
935932

936933
// Initialise a git server with a repo including the files in dir.

0 commit comments

Comments
 (0)