diff --git a/pkg/git/gogit/checkout.go b/pkg/git/gogit/checkout.go index 6c5a70642..2f44fbaae 100644 --- a/pkg/git/gogit/checkout.go +++ b/pkg/git/gogit/checkout.go @@ -29,6 +29,7 @@ import ( "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-git/go-git/v5/plumbing/transport" "github.com/go-git/go-git/v5/storage/memory" "github.com/fluxcd/pkg/gitutil" @@ -46,7 +47,7 @@ func CheckoutStrategyForOptions(_ context.Context, opts git.CheckoutOptions) git case opts.SemVer != "": return &CheckoutSemVer{SemVer: opts.SemVer, RecurseSubmodules: opts.RecurseSubmodules} case opts.Tag != "": - return &CheckoutTag{Tag: opts.Tag, RecurseSubmodules: opts.RecurseSubmodules} + return &CheckoutTag{Tag: opts.Tag, RecurseSubmodules: opts.RecurseSubmodules, LastRevision: opts.LastRevision} default: branch := opts.Branch if branch == "" { @@ -71,19 +72,11 @@ func (c *CheckoutBranch) Checkout(ctx context.Context, path, url string, opts *g ref := plumbing.NewBranchReferenceName(c.Branch) // check if previous revision has changed before attempting to clone if c.LastRevision != "" { - config := &config.RemoteConfig{ - Name: git.DefaultOrigin, - URLs: []string{url}, - } - rem := extgogit.NewRemote(memory.NewStorage(), config) - refs, err := rem.List(&extgogit.ListOptions{ - Auth: authMethod, - }) + currentRevision, err := getLastRevision(url, ref, opts, authMethod) if err != nil { - return nil, fmt.Errorf("unable to list remote for '%s': %w", url, err) + return nil, err } - currentRevision := filterRefs(refs, ref) if currentRevision != "" && currentRevision == c.LastRevision { return nil, git.NoChangesError{ Message: "no changes since last reconcilation", @@ -119,9 +112,31 @@ func (c *CheckoutBranch) Checkout(ctx context.Context, path, url string, opts *g return buildCommitWithRef(cc, ref) } +func getLastRevision(url string, ref plumbing.ReferenceName, opts *git.AuthOptions, authMethod transport.AuthMethod) (string, error) { + config := &config.RemoteConfig{ + Name: git.DefaultOrigin, + URLs: []string{url}, + } + rem := extgogit.NewRemote(memory.NewStorage(), config) + listOpts := &extgogit.ListOptions{ + Auth: authMethod, + } + if opts != nil && opts.CAFile != nil { + listOpts.CABundle = opts.CAFile + } + refs, err := rem.List(listOpts) + if err != nil { + return "", fmt.Errorf("unable to list remote for '%s': %w", url, err) + } + + currentRevision := filterRefs(refs, ref) + return currentRevision, nil +} + type CheckoutTag struct { Tag string RecurseSubmodules bool + LastRevision string } func (c *CheckoutTag) Checkout(ctx context.Context, path, url string, opts *git.AuthOptions) (*git.Commit, error) { @@ -130,6 +145,20 @@ func (c *CheckoutTag) Checkout(ctx context.Context, path, url string, opts *git. return nil, fmt.Errorf("failed to construct auth method with options: %w", err) } ref := plumbing.NewTagReferenceName(c.Tag) + // check if previous revision has changed before attempting to clone + if c.LastRevision != "" { + currentRevision, err := getLastRevision(url, ref, opts, authMethod) + if err != nil { + return nil, err + } + + if currentRevision != "" && currentRevision == c.LastRevision { + return nil, git.NoChangesError{ + Message: "no changes since last reconcilation", + ObservedRevision: currentRevision, + } + } + } repo, err := extgogit.PlainCloneContext(ctx, path, false, &extgogit.CloneOptions{ URL: url, Auth: authMethod, diff --git a/pkg/git/gogit/checkout_test.go b/pkg/git/gogit/checkout_test.go index 7c296d665..ba5d28231 100644 --- a/pkg/git/gogit/checkout_test.go +++ b/pkg/git/gogit/checkout_test.go @@ -137,6 +137,8 @@ func TestCheckoutTag_Checkout(t *testing.T) { checkoutTag string expectTag string expectErr string + lastRev string + setLastRev bool }{ { name: "Tag", @@ -144,6 +146,20 @@ func TestCheckoutTag_Checkout(t *testing.T) { checkoutTag: "tag-1", expectTag: "tag-1", }, + { + name: "Skip Tag if last revision hasn't changed", + tag: "tag-2", + checkoutTag: "tag-2", + setLastRev: true, + expectErr: "no changes since last reconcilation", + }, + { + name: "Last revision changed", + tag: "tag-3", + checkoutTag: "tag-3", + expectTag: "tag-3", + lastRev: "tag-3/", + }, { name: "Annotated", tag: "annotated", @@ -168,12 +184,13 @@ func TestCheckoutTag_Checkout(t *testing.T) { } var h plumbing.Hash + var tagHash *plumbing.Reference if tt.tag != "" { h, err = commitFile(repo, "tag", tt.tag, time.Now()) if err != nil { t.Fatal(err) } - _, err = tag(repo, h, !tt.annotated, tt.tag, time.Now()) + tagHash, err = tag(repo, h, !tt.annotated, tt.tag, time.Now()) if err != nil { t.Fatal(err) } @@ -182,10 +199,18 @@ func TestCheckoutTag_Checkout(t *testing.T) { tag := CheckoutTag{ Tag: tt.checkoutTag, } + if tt.setLastRev { + tag.LastRevision = fmt.Sprintf("%s/%s", tt.tag, tagHash.Hash().String()) + } + + if tt.lastRev != "" { + tag.LastRevision = tt.lastRev + } tmpDir := t.TempDir() cc, err := tag.Checkout(context.TODO(), tmpDir, path, nil) if tt.expectErr != "" { + g.Expect(err).ToNot(BeNil()) g.Expect(err.Error()).To(ContainSubstring(tt.expectErr)) g.Expect(cc).To(BeNil()) return