Optimize libgit2 checkout tag strategy through condition no-ops
Signed-off-by: Sanskar Jaiswal <jaiswalsanskar078@gmail.com>
This commit is contained in:
parent
860d7051da
commit
6a793a55f6
|
|
@ -359,7 +359,7 @@ func TestGitRepositoryReconciler_reconcileSource_authStrategy(t *testing.T) {
|
|||
},
|
||||
wantErr: true,
|
||||
assertConditions: []metav1.Condition{
|
||||
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.GitOperationFailedReason, "failed to checkout and determine revision: unable to clone '<url>': PEM CA bundle could not be appended to x509 certificate pool"),
|
||||
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.GitOperationFailedReason, "failed to checkout and determine revision: unable to fetch-connect to remote '<url>': PEM CA bundle could not be appended to x509 certificate pool"),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -65,23 +65,13 @@ type CheckoutBranch struct {
|
|||
}
|
||||
|
||||
func (c *CheckoutBranch) Checkout(ctx context.Context, path, url string, opts *git.AuthOptions) (*git.Commit, error) {
|
||||
repo, err := git2go.InitRepository(path, false)
|
||||
repo, remote, err := getBlankRepoAndRemote(ctx, path, url, opts)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to init repository for '%s': %w", managed.EffectiveURL(url), gitutil.LibGit2Error(err))
|
||||
return nil, err
|
||||
}
|
||||
defer repo.Free()
|
||||
|
||||
remote, err := repo.Remotes.Create("origin", url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create remote for '%s': %w", managed.EffectiveURL(url), gitutil.LibGit2Error(err))
|
||||
}
|
||||
defer remote.Free()
|
||||
|
||||
callBacks := RemoteCallbacks(ctx, opts)
|
||||
err = remote.ConnectFetch(&callBacks, &git2go.ProxyOptions{Type: git2go.ProxyTypeAuto}, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to fetch-connect to remote '%s': %w", managed.EffectiveURL(url), gitutil.LibGit2Error(err))
|
||||
}
|
||||
defer remote.Disconnect()
|
||||
|
||||
// When the last observed revision is set, check whether it is still
|
||||
|
|
@ -95,7 +85,7 @@ func (c *CheckoutBranch) Checkout(ctx context.Context, path, url string, opts *g
|
|||
currentRevision := fmt.Sprintf("%s/%s", c.Branch, heads[0].Id.String())
|
||||
if currentRevision == c.LastRevision {
|
||||
return nil, git.NoChangesError{
|
||||
Message: "no changes since last reconcilation",
|
||||
Message: "no changes since last reconciliation",
|
||||
ObservedRevision: currentRevision,
|
||||
}
|
||||
}
|
||||
|
|
@ -155,21 +145,59 @@ func (c *CheckoutBranch) Checkout(ctx context.Context, path, url string, opts *g
|
|||
}
|
||||
|
||||
type CheckoutTag struct {
|
||||
Tag string
|
||||
Tag string
|
||||
LastRevision string
|
||||
}
|
||||
|
||||
func (c *CheckoutTag) Checkout(ctx context.Context, path, url string, opts *git.AuthOptions) (*git.Commit, error) {
|
||||
repo, err := safeClone(url, path, &git2go.CloneOptions{
|
||||
FetchOptions: git2go.FetchOptions{
|
||||
DownloadTags: git2go.DownloadTagsAll,
|
||||
repo, remote, err := getBlankRepoAndRemote(ctx, path, url, opts)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer repo.Free()
|
||||
defer remote.Free()
|
||||
defer remote.Disconnect()
|
||||
|
||||
if c.LastRevision != "" {
|
||||
heads, err := remote.Ls(c.Tag)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to remote ls for '%s': %w", managed.EffectiveURL(url), gitutil.LibGit2Error(err))
|
||||
}
|
||||
if len(heads) > 0 {
|
||||
currentRevision := fmt.Sprintf("%s/%s", c.Tag, heads[0].Id.String())
|
||||
var same bool
|
||||
if currentRevision == c.LastRevision {
|
||||
same = true
|
||||
} else if len(heads) > 1 {
|
||||
currentAnnotatedRevision := fmt.Sprintf("%s/%s", c.Tag, heads[1].Id.String())
|
||||
if currentAnnotatedRevision == c.LastRevision {
|
||||
same = true
|
||||
}
|
||||
}
|
||||
if same {
|
||||
return nil, git.NoChangesError{
|
||||
Message: "no changes since last reconciliation",
|
||||
ObservedRevision: currentRevision,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = remote.Fetch([]string{c.Tag},
|
||||
&git2go.FetchOptions{
|
||||
DownloadTags: git2go.DownloadTagsAuto,
|
||||
RemoteCallbacks: RemoteCallbacks(ctx, opts),
|
||||
ProxyOptions: git2go.ProxyOptions{Type: git2go.ProxyTypeAuto},
|
||||
},
|
||||
})
|
||||
"")
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to clone '%s': %w", managed.EffectiveURL(url), gitutil.LibGit2Error(err))
|
||||
return nil, fmt.Errorf("unable to fetch remote '%s': %w",
|
||||
managed.EffectiveURL(url), gitutil.LibGit2Error(err))
|
||||
}
|
||||
defer repo.Free()
|
||||
|
||||
cc, err := checkoutDetachedDwim(repo, c.Tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -391,3 +419,27 @@ func buildSignature(s *git2go.Signature) git.Signature {
|
|||
When: s.When,
|
||||
}
|
||||
}
|
||||
|
||||
// getBlankRepoAndRemote returns a newly initialized repository, and a remote connected to the provided url.
|
||||
// Callers must make sure to call the below defer statements:
|
||||
// defer repo.Free()
|
||||
// defer remote.Free()
|
||||
// defer remote.Disconnect()
|
||||
func getBlankRepoAndRemote(ctx context.Context, path, url string, opts *git.AuthOptions) (*git2go.Repository, *git2go.Remote, error) {
|
||||
repo, err := git2go.InitRepository(path, false)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to init repository for '%s': %w", managed.EffectiveURL(url), gitutil.LibGit2Error(err))
|
||||
}
|
||||
|
||||
remote, err := repo.Remotes.Create("origin", url)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to create remote for '%s': %w", managed.EffectiveURL(url), gitutil.LibGit2Error(err))
|
||||
}
|
||||
|
||||
callBacks := RemoteCallbacks(ctx, opts)
|
||||
err = remote.ConnectFetch(&callBacks, &git2go.ProxyOptions{Type: git2go.ProxyTypeAuto}, nil)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to fetch-connect to remote '%s': %w", managed.EffectiveURL(url), gitutil.LibGit2Error(err))
|
||||
}
|
||||
return repo, remote, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,8 +51,19 @@ func TestCheckoutBranch_Checkout(t *testing.T) {
|
|||
// ignores the error here because it can be defaulted
|
||||
// https://github.blog/2020-07-27-highlights-from-git-2-28/#introducing-init-defaultbranch
|
||||
defaultBranch := "master"
|
||||
if v, err := cfg.LookupString("init.defaultBranch"); err != nil && v != "" {
|
||||
defaultBranch = v
|
||||
iter, err := cfg.NewIterator()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for {
|
||||
val, e := iter.Next()
|
||||
if e != nil {
|
||||
break
|
||||
}
|
||||
if val.Name == "init.defaultbranch" {
|
||||
defaultBranch = val.Value
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
firstCommit, err := commitFile(repo, "branch", "init", time.Now())
|
||||
|
|
@ -102,7 +113,7 @@ func TestCheckoutBranch_Checkout(t *testing.T) {
|
|||
filesCreated: map[string]string{"branch": "second"},
|
||||
expectedCommit: secondCommit.String(),
|
||||
lastRevision: fmt.Sprintf("%s/%s", defaultBranch, secondCommit.String()),
|
||||
expectedErr: fmt.Sprintf("no changes since last reconcilation: observed revision '%s/%s'", defaultBranch, secondCommit.String()),
|
||||
expectedErr: fmt.Sprintf("no changes since last reconciliation: observed revision '%s/%s'", defaultBranch, secondCommit.String()),
|
||||
},
|
||||
{
|
||||
name: "lastRevision is different",
|
||||
|
|
@ -143,12 +154,13 @@ func TestCheckoutBranch_Checkout(t *testing.T) {
|
|||
|
||||
func TestCheckoutTag_Checkout(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
tag string
|
||||
annotated bool
|
||||
checkoutTag string
|
||||
expectTag string
|
||||
expectErr string
|
||||
name string
|
||||
tag string
|
||||
annotated bool
|
||||
checkoutTag string
|
||||
expectTag string
|
||||
expectErr string
|
||||
lastRevision bool
|
||||
}{
|
||||
{
|
||||
name: "Tag",
|
||||
|
|
@ -168,6 +180,21 @@ func TestCheckoutTag_Checkout(t *testing.T) {
|
|||
checkoutTag: "invalid",
|
||||
expectErr: "unable to find 'invalid': no reference found for shorthand 'invalid'",
|
||||
},
|
||||
{
|
||||
name: "skip clone - last revision is unchanged",
|
||||
tag: "tag-1",
|
||||
checkoutTag: "tag-1",
|
||||
expectTag: "tag-1",
|
||||
lastRevision: true,
|
||||
expectErr: "no changes since last reconciliation",
|
||||
},
|
||||
{
|
||||
name: "last revision changed",
|
||||
tag: "tag-1",
|
||||
checkoutTag: "tag-1",
|
||||
expectTag: "tag-2",
|
||||
lastRevision: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
@ -188,29 +215,60 @@ func TestCheckoutTag_Checkout(t *testing.T) {
|
|||
if commit, err = repo.LookupCommit(c); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = tag(repo, c, !tt.annotated, tt.tag, time.Now())
|
||||
_, err = tag(repo, commit.Id(), !tt.annotated, tt.tag, time.Now())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
tag := CheckoutTag{
|
||||
checkoutTag := CheckoutTag{
|
||||
Tag: tt.checkoutTag,
|
||||
}
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
cc, err := tag.Checkout(context.TODO(), tmpDir, repo.Path(), nil)
|
||||
cc, err := checkoutTag.Checkout(context.TODO(), tmpDir, repo.Path(), nil)
|
||||
|
||||
if tt.expectErr != "" {
|
||||
if tt.lastRevision {
|
||||
tmpDir, _ = os.MkdirTemp("", "test")
|
||||
defer os.RemoveAll(tmpDir)
|
||||
checkoutTag.LastRevision = cc.String()
|
||||
cc, err = checkoutTag.Checkout(context.TODO(), tmpDir, repo.Path(), nil)
|
||||
}
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(err.Error()).To(ContainSubstring(tt.expectErr))
|
||||
g.Expect(cc).To(BeNil())
|
||||
return
|
||||
}
|
||||
if tt.lastRevision {
|
||||
checkoutTag.LastRevision = fmt.Sprintf("%s/%s", tt.tag, commit.Id().String())
|
||||
checkoutTag.Tag = tt.expectTag
|
||||
if tt.tag != "" {
|
||||
c, err := commitFile(repo, "tag", "changed tag", time.Now())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if commit, err = repo.LookupCommit(c); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = tag(repo, commit.Id(), !tt.annotated, tt.expectTag, time.Now())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tmpDir, _ = os.MkdirTemp("", "test")
|
||||
defer os.RemoveAll(tmpDir)
|
||||
cc, err = checkoutTag.Checkout(context.TODO(), tmpDir, repo.Path(), nil)
|
||||
}
|
||||
}
|
||||
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(cc.String()).To(Equal(tt.expectTag + "/" + commit.Id().String()))
|
||||
g.Expect(filepath.Join(tmpDir, "tag")).To(BeARegularFile())
|
||||
g.Expect(os.ReadFile(filepath.Join(tmpDir, "tag"))).To(BeEquivalentTo(tt.tag))
|
||||
if tt.lastRevision {
|
||||
g.Expect(os.ReadFile(filepath.Join(tmpDir, "tag"))).To(BeEquivalentTo("changed tag"))
|
||||
} else {
|
||||
g.Expect(os.ReadFile(filepath.Join(tmpDir, "tag"))).To(BeEquivalentTo(tt.tag))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue