gogit: allow checkout of commit without branch
This commit changes the `gogit` behavior for commit checkouts, now allowing one to reference to just a commit while omitting any branch reference. Doing this creates an Artifact with a `HEAD/<commit>` revision. If both a `branch` and `commit` are defined, the commit is expected to exist within the branch. This results in a more efficient clone of just the target branch, and also makes this change backwards compatible. Fixes #407 Fixes #315 Signed-off-by: Hidde Beydals <hello@hidde.co>
This commit is contained in:
		
							parent
							
								
									5a1fcc213b
								
							
						
					
					
						commit
						b7376ce94c
					
				|  | @ -120,7 +120,6 @@ type GitRepositoryInclude struct { | |||
| // GitRepositoryRef defines the Git ref used for pull and checkout operations.
 | ||||
| type GitRepositoryRef struct { | ||||
| 	// The Git branch to checkout, defaults to master.
 | ||||
| 	// +kubebuilder:default:=master
 | ||||
| 	// +optional
 | ||||
| 	Branch string `json:"branch,omitempty"` | ||||
| 
 | ||||
|  |  | |||
|  | @ -91,7 +91,6 @@ spec: | |||
|                 description: The Git reference to checkout and monitor for changes, defaults to master branch. | ||||
|                 properties: | ||||
|                   branch: | ||||
|                     default: master | ||||
|                     description: The Git branch to checkout, defaults to master. | ||||
|                     type: string | ||||
|                   commit: | ||||
|  |  | |||
|  | @ -249,14 +249,15 @@ func (r *GitRepositoryReconciler) reconcile(ctx context.Context, repository sour | |||
| 			return sourcev1.GitRepositoryNotReady(repository, sourcev1.AuthenticationFailedReason, err.Error()), err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	checkoutStrategy, err := strategy.CheckoutStrategyForRef( | ||||
| 		repository.Spec.Reference, | ||||
| 		git.CheckoutOptions{ | ||||
| 			GitImplementation: repository.Spec.GitImplementation, | ||||
| 			RecurseSubmodules: repository.Spec.RecurseSubmodules, | ||||
| 		}, | ||||
| 	) | ||||
| 	checkoutOpts := git.CheckoutOptions{RecurseSubmodules: repository.Spec.RecurseSubmodules} | ||||
| 	if ref := repository.Spec.Reference; ref != nil { | ||||
| 		checkoutOpts.Branch = ref.Branch | ||||
| 		checkoutOpts.Commit = ref.Commit | ||||
| 		checkoutOpts.Tag = ref.Tag | ||||
| 		checkoutOpts.SemVer = ref.SemVer | ||||
| 	} | ||||
| 	checkoutStrategy, err := strategy.CheckoutStrategyForImplementation(ctx, | ||||
| 		git.Implementation(repository.Spec.GitImplementation), checkoutOpts) | ||||
| 	if err != nil { | ||||
| 		return sourcev1.GitRepositoryNotReady(repository, sourcev1.GitOperationFailedReason, err.Error()), err | ||||
| 	} | ||||
|  |  | |||
|  | @ -263,7 +263,7 @@ var _ = Describe("GitRepositoryReconciler", func() { | |||
| 				}, | ||||
| 				waitForReason:  sourcev1.GitOperationSucceedReason, | ||||
| 				expectStatus:   metav1.ConditionTrue, | ||||
| 				expectRevision: "master", | ||||
| 				expectRevision: "HEAD", | ||||
| 			}), | ||||
| 			Entry("commit in branch", refTestCase{ | ||||
| 				reference: &sourcev1.GitRepositoryRef{ | ||||
|  |  | |||
|  | @ -26,6 +26,8 @@ import ( | |||
| 	"github.com/ProtonMail/go-crypto/openpgp" | ||||
| ) | ||||
| 
 | ||||
| type Implementation string | ||||
| 
 | ||||
| type Hash []byte | ||||
| 
 | ||||
| // String returns the SHA1 Hash as a string.
 | ||||
|  |  | |||
|  | @ -25,40 +25,38 @@ import ( | |||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/Masterminds/semver/v3" | ||||
| 	"github.com/fluxcd/pkg/gitutil" | ||||
| 	"github.com/fluxcd/pkg/version" | ||||
| 	extgogit "github.com/go-git/go-git/v5" | ||||
| 	"github.com/go-git/go-git/v5/plumbing" | ||||
| 	"github.com/go-git/go-git/v5/plumbing/object" | ||||
| 
 | ||||
| 	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1" | ||||
| 	"github.com/fluxcd/pkg/gitutil" | ||||
| 	"github.com/fluxcd/pkg/version" | ||||
| 
 | ||||
| 	"github.com/fluxcd/source-controller/pkg/git" | ||||
| ) | ||||
| 
 | ||||
| func CheckoutStrategyForRef(ref *sourcev1.GitRepositoryRef, opt git.CheckoutOptions) git.CheckoutStrategy { | ||||
| // CheckoutStrategyForOptions returns the git.CheckoutStrategy for the given
 | ||||
| // git.CheckoutOptions.
 | ||||
| func CheckoutStrategyForOptions(_ context.Context, opts git.CheckoutOptions) git.CheckoutStrategy { | ||||
| 	switch { | ||||
| 	case ref == nil: | ||||
| 		return &CheckoutBranch{branch: git.DefaultBranch} | ||||
| 	case ref.SemVer != "": | ||||
| 		return &CheckoutSemVer{semVer: ref.SemVer, recurseSubmodules: opt.RecurseSubmodules} | ||||
| 	case ref.Tag != "": | ||||
| 		return &CheckoutTag{tag: ref.Tag, recurseSubmodules: opt.RecurseSubmodules} | ||||
| 	case ref.Commit != "": | ||||
| 		strategy := &CheckoutCommit{branch: ref.Branch, commit: ref.Commit, recurseSubmodules: opt.RecurseSubmodules} | ||||
| 		if strategy.branch == "" { | ||||
| 			strategy.branch = git.DefaultBranch | ||||
| 		} | ||||
| 		return strategy | ||||
| 	case ref.Branch != "": | ||||
| 		return &CheckoutBranch{branch: ref.Branch, recurseSubmodules: opt.RecurseSubmodules} | ||||
| 	case opts.Commit != "": | ||||
| 		return &CheckoutCommit{Branch: opts.Branch, Commit: opts.Commit, RecurseSubmodules: opts.RecurseSubmodules} | ||||
| 	case opts.SemVer != "": | ||||
| 		return &CheckoutSemVer{SemVer: opts.SemVer, RecurseSubmodules: opts.RecurseSubmodules} | ||||
| 	case opts.Tag != "": | ||||
| 		return &CheckoutTag{Tag: opts.Tag, RecurseSubmodules: opts.RecurseSubmodules} | ||||
| 	default: | ||||
| 		return &CheckoutBranch{branch: git.DefaultBranch} | ||||
| 		branch := opts.Branch | ||||
| 		if branch == "" { | ||||
| 			branch = git.DefaultBranch | ||||
| 		} | ||||
| 		return &CheckoutBranch{Branch: branch, RecurseSubmodules: opts.RecurseSubmodules} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| type CheckoutBranch struct { | ||||
| 	branch            string | ||||
| 	recurseSubmodules bool | ||||
| 	Branch            string | ||||
| 	RecurseSubmodules bool | ||||
| } | ||||
| 
 | ||||
| func (c *CheckoutBranch) Checkout(ctx context.Context, path, url string, opts *git.AuthOptions) (*git.Commit, error) { | ||||
|  | @ -66,16 +64,16 @@ func (c *CheckoutBranch) Checkout(ctx context.Context, path, url string, opts *g | |||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to construct auth method with options: %w", err) | ||||
| 	} | ||||
| 	ref := plumbing.NewBranchReferenceName(c.branch) | ||||
| 	ref := plumbing.NewBranchReferenceName(c.Branch) | ||||
| 	repo, err := extgogit.PlainCloneContext(ctx, path, false, &extgogit.CloneOptions{ | ||||
| 		URL:               url, | ||||
| 		Auth:              authMethod, | ||||
| 		RemoteName:        git.DefaultOrigin, | ||||
| 		ReferenceName:     plumbing.NewBranchReferenceName(c.branch), | ||||
| 		ReferenceName:     plumbing.NewBranchReferenceName(c.Branch), | ||||
| 		SingleBranch:      true, | ||||
| 		NoCheckout:        false, | ||||
| 		Depth:             1, | ||||
| 		RecurseSubmodules: recurseSubmodules(c.recurseSubmodules), | ||||
| 		RecurseSubmodules: recurseSubmodules(c.RecurseSubmodules), | ||||
| 		Progress:          nil, | ||||
| 		Tags:              extgogit.NoTags, | ||||
| 		CABundle:          caBundle(opts), | ||||
|  | @ -85,7 +83,7 @@ func (c *CheckoutBranch) Checkout(ctx context.Context, path, url string, opts *g | |||
| 	} | ||||
| 	head, err := repo.Head() | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to resolve HEAD of branch '%s': %w", c.branch, err) | ||||
| 		return nil, fmt.Errorf("failed to resolve HEAD of branch '%s': %w", c.Branch, err) | ||||
| 	} | ||||
| 	cc, err := repo.CommitObject(head.Hash()) | ||||
| 	if err != nil { | ||||
|  | @ -95,8 +93,8 @@ func (c *CheckoutBranch) Checkout(ctx context.Context, path, url string, opts *g | |||
| } | ||||
| 
 | ||||
| type CheckoutTag struct { | ||||
| 	tag               string | ||||
| 	recurseSubmodules bool | ||||
| 	Tag               string | ||||
| 	RecurseSubmodules bool | ||||
| } | ||||
| 
 | ||||
| func (c *CheckoutTag) Checkout(ctx context.Context, path, url string, opts *git.AuthOptions) (*git.Commit, error) { | ||||
|  | @ -104,16 +102,16 @@ func (c *CheckoutTag) Checkout(ctx context.Context, path, url string, opts *git. | |||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to construct auth method with options: %w", err) | ||||
| 	} | ||||
| 	ref := plumbing.NewTagReferenceName(c.tag) | ||||
| 	ref := plumbing.NewTagReferenceName(c.Tag) | ||||
| 	repo, err := extgogit.PlainCloneContext(ctx, path, false, &extgogit.CloneOptions{ | ||||
| 		URL:               url, | ||||
| 		Auth:              authMethod, | ||||
| 		RemoteName:        git.DefaultOrigin, | ||||
| 		ReferenceName:     plumbing.NewTagReferenceName(c.tag), | ||||
| 		ReferenceName:     plumbing.NewTagReferenceName(c.Tag), | ||||
| 		SingleBranch:      true, | ||||
| 		NoCheckout:        false, | ||||
| 		Depth:             1, | ||||
| 		RecurseSubmodules: recurseSubmodules(c.recurseSubmodules), | ||||
| 		RecurseSubmodules: recurseSubmodules(c.RecurseSubmodules), | ||||
| 		Progress:          nil, | ||||
| 		Tags:              extgogit.NoTags, | ||||
| 		CABundle:          caBundle(opts), | ||||
|  | @ -123,7 +121,7 @@ func (c *CheckoutTag) Checkout(ctx context.Context, path, url string, opts *git. | |||
| 	} | ||||
| 	head, err := repo.Head() | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to resolve HEAD of tag '%s': %w", c.tag, err) | ||||
| 		return nil, fmt.Errorf("failed to resolve HEAD of tag '%s': %w", c.Tag, err) | ||||
| 	} | ||||
| 	cc, err := repo.CommitObject(head.Hash()) | ||||
| 	if err != nil { | ||||
|  | @ -133,9 +131,9 @@ func (c *CheckoutTag) Checkout(ctx context.Context, path, url string, opts *git. | |||
| } | ||||
| 
 | ||||
| type CheckoutCommit struct { | ||||
| 	branch            string | ||||
| 	commit            string | ||||
| 	recurseSubmodules bool | ||||
| 	Branch            string | ||||
| 	Commit            string | ||||
| 	RecurseSubmodules bool | ||||
| } | ||||
| 
 | ||||
| func (c *CheckoutCommit) Checkout(ctx context.Context, path, url string, opts *git.AuthOptions) (*git.Commit, error) { | ||||
|  | @ -143,19 +141,22 @@ func (c *CheckoutCommit) Checkout(ctx context.Context, path, url string, opts *g | |||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to construct auth method with options: %w", err) | ||||
| 	} | ||||
| 	ref := plumbing.NewBranchReferenceName(c.branch) | ||||
| 	repo, err := extgogit.PlainCloneContext(ctx, path, false, &extgogit.CloneOptions{ | ||||
| 	cloneOpts := &extgogit.CloneOptions{ | ||||
| 		URL:               url, | ||||
| 		Auth:              authMethod, | ||||
| 		RemoteName:        git.DefaultOrigin, | ||||
| 		ReferenceName:     ref, | ||||
| 		SingleBranch:      true, | ||||
| 		NoCheckout:        false, | ||||
| 		RecurseSubmodules: recurseSubmodules(c.recurseSubmodules), | ||||
| 		SingleBranch:      false, | ||||
| 		NoCheckout:        true, | ||||
| 		RecurseSubmodules: recurseSubmodules(c.RecurseSubmodules), | ||||
| 		Progress:          nil, | ||||
| 		Tags:              extgogit.NoTags, | ||||
| 		CABundle:          caBundle(opts), | ||||
| 	}) | ||||
| 	} | ||||
| 	if c.Branch != "" { | ||||
| 		cloneOpts.SingleBranch = true | ||||
| 		cloneOpts.ReferenceName = plumbing.NewBranchReferenceName(c.Branch) | ||||
| 	} | ||||
| 	repo, err := extgogit.PlainCloneContext(ctx, path, false, cloneOpts) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("unable to clone '%s', error: %w", url, gitutil.GoGitError(err)) | ||||
| 	} | ||||
|  | @ -163,29 +164,27 @@ func (c *CheckoutCommit) Checkout(ctx context.Context, path, url string, opts *g | |||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to open Git worktree: %w", err) | ||||
| 	} | ||||
| 	f, _ := repo.Head() | ||||
| 	f.String() | ||||
| 	cc, err := repo.CommitObject(plumbing.NewHash(c.commit)) | ||||
| 	cc, err := repo.CommitObject(plumbing.NewHash(c.Commit)) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to resolve commit object for '%s': %w", c.commit, err) | ||||
| 		return nil, fmt.Errorf("failed to resolve commit object for '%s': %w", c.Commit, err) | ||||
| 	} | ||||
| 	err = w.Checkout(&extgogit.CheckoutOptions{ | ||||
| 		Hash:  cc.Hash, | ||||
| 		Force: true, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to checkout commit '%s': %w", c.commit, err) | ||||
| 		return nil, fmt.Errorf("failed to checkout commit '%s': %w", c.Commit, err) | ||||
| 	} | ||||
| 	return commitWithRef(cc, ref) | ||||
| 	return commitWithRef(cc, cloneOpts.ReferenceName) | ||||
| } | ||||
| 
 | ||||
| type CheckoutSemVer struct { | ||||
| 	semVer            string | ||||
| 	recurseSubmodules bool | ||||
| 	SemVer            string | ||||
| 	RecurseSubmodules bool | ||||
| } | ||||
| 
 | ||||
| func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, opts *git.AuthOptions) (*git.Commit, error) { | ||||
| 	verConstraint, err := semver.NewConstraint(c.semVer) | ||||
| 	verConstraint, err := semver.NewConstraint(c.SemVer) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("semver parse error: %w", err) | ||||
| 	} | ||||
|  | @ -201,7 +200,7 @@ func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, opts *g | |||
| 		RemoteName:        git.DefaultOrigin, | ||||
| 		NoCheckout:        false, | ||||
| 		Depth:             1, | ||||
| 		RecurseSubmodules: recurseSubmodules(c.recurseSubmodules), | ||||
| 		RecurseSubmodules: recurseSubmodules(c.RecurseSubmodules), | ||||
| 		Progress:          nil, | ||||
| 		Tags:              extgogit.AllTags, | ||||
| 		CABundle:          caBundle(opts), | ||||
|  | @ -247,7 +246,7 @@ func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, opts *g | |||
| 		matchedVersions = append(matchedVersions, v) | ||||
| 	} | ||||
| 	if len(matchedVersions) == 0 { | ||||
| 		return nil, fmt.Errorf("no match found for semver: %s", c.semVer) | ||||
| 		return nil, fmt.Errorf("no match found for semver: %s", c.SemVer) | ||||
| 	} | ||||
| 
 | ||||
| 	// Sort versions
 | ||||
|  |  | |||
|  | @ -83,7 +83,7 @@ func TestCheckoutBranch_Checkout(t *testing.T) { | |||
| 			g := NewWithT(t) | ||||
| 
 | ||||
| 			branch := CheckoutBranch{ | ||||
| 				branch: tt.branch, | ||||
| 				Branch: tt.branch, | ||||
| 			} | ||||
| 			tmpDir, _ := os.MkdirTemp("", "test") | ||||
| 			defer os.RemoveAll(tmpDir) | ||||
|  | @ -152,7 +152,7 @@ func TestCheckoutTag_Checkout(t *testing.T) { | |||
| 			} | ||||
| 
 | ||||
| 			tag := CheckoutTag{ | ||||
| 				tag: tt.checkoutTag, | ||||
| 				Tag: tt.checkoutTag, | ||||
| 			} | ||||
| 			tmpDir, _ := os.MkdirTemp("", "test") | ||||
| 			defer os.RemoveAll(tmpDir) | ||||
|  | @ -173,46 +173,87 @@ func TestCheckoutTag_Checkout(t *testing.T) { | |||
| } | ||||
| 
 | ||||
| func TestCheckoutCommit_Checkout(t *testing.T) { | ||||
| 	g := NewWithT(t) | ||||
| 
 | ||||
| 	repo, path, err := initRepo() | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	defer os.RemoveAll(path) | ||||
| 
 | ||||
| 	c, err := commitFile(repo, "commit", "init", time.Now()) | ||||
| 	firstCommit, err := commitFile(repo, "commit", "init", time.Now()) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if _, err = commitFile(repo, "commit", "second", time.Now()); err != nil { | ||||
| 	if err = createBranch(repo, "other-branch"); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	secondCommit, err := commitFile(repo, "commit", "second", time.Now()) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	commit := CheckoutCommit{ | ||||
| 		commit: c.String(), | ||||
| 	tests := []struct { | ||||
| 		name         string | ||||
| 		commit       string | ||||
| 		branch       string | ||||
| 		expectCommit string | ||||
| 		expectFile   string | ||||
| 		expectError  string | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name:         "Commit", | ||||
| 			commit:       firstCommit.String(), | ||||
| 			expectCommit: "HEAD/" + firstCommit.String(), | ||||
| 			expectFile:   "init", | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "Commit in specific branch", | ||||
| 			commit:       secondCommit.String(), | ||||
| 			branch:       "other-branch", | ||||
| 			expectCommit: "other-branch/" + secondCommit.String(), | ||||
| 			expectFile:   "second", | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:        "Non existing commit", | ||||
| 			commit:      "a-random-invalid-commit", | ||||
| 			expectError: "failed to resolve commit object for 'a-random-invalid-commit': object not found", | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:        "Non existing commit in specific branch", | ||||
| 			commit:      secondCommit.String(), | ||||
| 			branch:      "master", | ||||
| 			expectError: "object not found", | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, tt := range tests { | ||||
| 		t.Run(tt.name, func(t *testing.T) { | ||||
| 			g := NewWithT(t) | ||||
| 
 | ||||
| 			commit := CheckoutCommit{ | ||||
| 				Commit: tt.commit, | ||||
| 				Branch: tt.branch, | ||||
| 			} | ||||
| 
 | ||||
| 			tmpDir, err := os.MkdirTemp("", "git2go") | ||||
| 			if err != nil { | ||||
| 				t.Fatal(err) | ||||
| 			} | ||||
| 	tmpDir, _ := os.MkdirTemp("", "git2go") | ||||
| 			defer os.RemoveAll(tmpDir) | ||||
| 
 | ||||
| 			cc, err := commit.Checkout(context.TODO(), tmpDir, path, nil) | ||||
| 	g.Expect(err).ToNot(HaveOccurred()) | ||||
| 	g.Expect(cc.String()).To(Equal("master" + "/" + c.String())) | ||||
| 	g.Expect(filepath.Join(tmpDir, "commit")).To(BeARegularFile()) | ||||
| 	g.Expect(os.ReadFile(filepath.Join(tmpDir, "commit"))).To(BeEquivalentTo("init")) | ||||
| 
 | ||||
| 	commit = CheckoutCommit{ | ||||
| 		commit: "4dc3185c5fc94eb75048376edeb44571cece25f4", | ||||
| 		branch: "master", | ||||
| 	} | ||||
| 	tmpDir2, _ := os.MkdirTemp("", "git2go") | ||||
| 	defer os.RemoveAll(tmpDir) | ||||
| 
 | ||||
| 	cc, err = commit.Checkout(context.TODO(), tmpDir2, path, nil) | ||||
| 			if tt.expectError != "" { | ||||
| 				g.Expect(err).To(HaveOccurred()) | ||||
| 	g.Expect(err.Error()).To(ContainSubstring("object not found")) | ||||
| 				g.Expect(err.Error()).To(ContainSubstring(tt.expectError)) | ||||
| 				g.Expect(cc).To(BeNil()) | ||||
| 				return | ||||
| 			} | ||||
| 
 | ||||
| 			g.Expect(err).ToNot(HaveOccurred()) | ||||
| 			g.Expect(cc).ToNot(BeNil()) | ||||
| 			g.Expect(cc.String()).To(Equal(tt.expectCommit)) | ||||
| 			g.Expect(filepath.Join(tmpDir, "commit")).To(BeARegularFile()) | ||||
| 			g.Expect(os.ReadFile(filepath.Join(tmpDir, "commit"))).To(BeEquivalentTo(tt.expectFile)) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestCheckoutTagSemVer_Checkout(t *testing.T) { | ||||
|  | @ -300,7 +341,7 @@ func TestCheckoutTagSemVer_Checkout(t *testing.T) { | |||
| 			g := NewWithT(t) | ||||
| 
 | ||||
| 			semVer := CheckoutSemVer{ | ||||
| 				semVer: tt.constraint, | ||||
| 				SemVer: tt.constraint, | ||||
| 			} | ||||
| 			tmpDir, _ := os.MkdirTemp("", "test") | ||||
| 			defer os.RemoveAll(tmpDir) | ||||
|  |  | |||
|  | @ -0,0 +1,23 @@ | |||
| /* | ||||
| Copyright 2021 The Flux authors | ||||
| 
 | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
| 
 | ||||
|     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| 
 | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| package gogit | ||||
| 
 | ||||
| import "github.com/fluxcd/source-controller/pkg/git" | ||||
| 
 | ||||
| const ( | ||||
| 	Implementation git.Implementation = "go-git" | ||||
| ) | ||||
|  | @ -24,38 +24,39 @@ import ( | |||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/Masterminds/semver/v3" | ||||
| 	"github.com/go-logr/logr" | ||||
| 	git2go "github.com/libgit2/git2go/v31" | ||||
| 
 | ||||
| 	"github.com/fluxcd/pkg/gitutil" | ||||
| 	"github.com/fluxcd/pkg/version" | ||||
| 
 | ||||
| 	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1" | ||||
| 	"github.com/fluxcd/source-controller/pkg/git" | ||||
| ) | ||||
| 
 | ||||
| func CheckoutStrategyForRef(ref *sourcev1.GitRepositoryRef, opt git.CheckoutOptions) git.CheckoutStrategy { | ||||
| 	switch { | ||||
| 	case ref == nil: | ||||
| 		return &CheckoutBranch{branch: git.DefaultBranch} | ||||
| 	case ref.SemVer != "": | ||||
| 		return &CheckoutSemVer{semVer: ref.SemVer} | ||||
| 	case ref.Tag != "": | ||||
| 		return &CheckoutTag{tag: ref.Tag} | ||||
| 	case ref.Commit != "": | ||||
| 		strategy := &CheckoutCommit{branch: ref.Branch, commit: ref.Commit} | ||||
| 		if strategy.branch == "" { | ||||
| 			strategy.branch = git.DefaultBranch | ||||
| // CheckoutStrategyForOptions returns the git.CheckoutStrategy for the given
 | ||||
| // git.CheckoutOptions.
 | ||||
| func CheckoutStrategyForOptions(ctx context.Context, opt git.CheckoutOptions) git.CheckoutStrategy { | ||||
| 	if opt.RecurseSubmodules { | ||||
| 		logr.FromContextOrDiscard(ctx).Info("git submodule recursion not supported by '%s'", Implementation) | ||||
| 	} | ||||
| 		return strategy | ||||
| 	case ref.Branch != "": | ||||
| 		return &CheckoutBranch{branch: ref.Branch} | ||||
| 	switch { | ||||
| 	case opt.Commit != "": | ||||
| 		return &CheckoutCommit{Commit: opt.Commit} | ||||
| 	case opt.SemVer != "": | ||||
| 		return &CheckoutSemVer{SemVer: opt.SemVer} | ||||
| 	case opt.Tag != "": | ||||
| 		return &CheckoutTag{Tag: opt.Tag} | ||||
| 	default: | ||||
| 		return &CheckoutBranch{branch: git.DefaultBranch} | ||||
| 		branch := opt.Branch | ||||
| 		if branch == "" { | ||||
| 			branch = git.DefaultBranch | ||||
| 		} | ||||
| 		return &CheckoutBranch{Branch: branch} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| type CheckoutBranch struct { | ||||
| 	branch string | ||||
| 	Branch string | ||||
| } | ||||
| 
 | ||||
| func (c *CheckoutBranch) Checkout(ctx context.Context, path, url string, opts *git.AuthOptions) (*git.Commit, error) { | ||||
|  | @ -64,7 +65,7 @@ func (c *CheckoutBranch) Checkout(ctx context.Context, path, url string, opts *g | |||
| 			DownloadTags:    git2go.DownloadTagsNone, | ||||
| 			RemoteCallbacks: remoteCallbacks(opts), | ||||
| 		}, | ||||
| 		CheckoutBranch: c.branch, | ||||
| 		CheckoutBranch: c.Branch, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("unable to clone '%s', error: %w", url, gitutil.LibGit2Error(err)) | ||||
|  | @ -77,14 +78,14 @@ func (c *CheckoutBranch) Checkout(ctx context.Context, path, url string, opts *g | |||
| 	defer head.Free() | ||||
| 	cc, err := repo.LookupCommit(head.Target()) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("could not find commit '%s' in branch '%s': %w", head.Target(), c.branch, err) | ||||
| 		return nil, fmt.Errorf("could not find commit '%s' in branch '%s': %w", head.Target(), c.Branch, err) | ||||
| 	} | ||||
| 	defer cc.Free() | ||||
| 	return commit(cc, "refs/heads/"+c.branch), nil | ||||
| 	return commit(cc, "refs/heads/"+c.Branch), nil | ||||
| } | ||||
| 
 | ||||
| type CheckoutTag struct { | ||||
| 	tag string | ||||
| 	Tag string | ||||
| } | ||||
| 
 | ||||
| func (c *CheckoutTag) Checkout(ctx context.Context, path, url string, opts *git.AuthOptions) (*git.Commit, error) { | ||||
|  | @ -98,17 +99,16 @@ func (c *CheckoutTag) Checkout(ctx context.Context, path, url string, opts *git. | |||
| 		return nil, fmt.Errorf("unable to clone '%s', error: %w", url, gitutil.LibGit2Error(err)) | ||||
| 	} | ||||
| 	defer repo.Free() | ||||
| 	cc, err := checkoutDetachedDwim(repo, c.tag) | ||||
| 	cc, err := checkoutDetachedDwim(repo, c.Tag) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer cc.Free() | ||||
| 	return commit(cc, "refs/tags/"+c.tag), nil | ||||
| 	return commit(cc, "refs/tags/"+c.Tag), nil | ||||
| } | ||||
| 
 | ||||
| type CheckoutCommit struct { | ||||
| 	branch string | ||||
| 	commit string | ||||
| 	Commit string | ||||
| } | ||||
| 
 | ||||
| func (c *CheckoutCommit) Checkout(ctx context.Context, path, url string, opts *git.AuthOptions) (*git.Commit, error) { | ||||
|  | @ -122,9 +122,9 @@ func (c *CheckoutCommit) Checkout(ctx context.Context, path, url string, opts *g | |||
| 		return nil, fmt.Errorf("unable to clone '%s', error: %w", url, gitutil.LibGit2Error(err)) | ||||
| 	} | ||||
| 	defer repo.Free() | ||||
| 	oid, err := git2go.NewOid(c.commit) | ||||
| 	oid, err := git2go.NewOid(c.Commit) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("could not create oid for '%s': %w", c.commit, err) | ||||
| 		return nil, fmt.Errorf("could not create oid for '%s': %w", c.Commit, err) | ||||
| 	} | ||||
| 	cc, err := checkoutDetachedHEAD(repo, oid) | ||||
| 	if err != nil { | ||||
|  | @ -134,11 +134,11 @@ func (c *CheckoutCommit) Checkout(ctx context.Context, path, url string, opts *g | |||
| } | ||||
| 
 | ||||
| type CheckoutSemVer struct { | ||||
| 	semVer string | ||||
| 	SemVer string | ||||
| } | ||||
| 
 | ||||
| func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, opts *git.AuthOptions) (*git.Commit, error) { | ||||
| 	verConstraint, err := semver.NewConstraint(c.semVer) | ||||
| 	verConstraint, err := semver.NewConstraint(c.SemVer) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("semver parse error: %w", err) | ||||
| 	} | ||||
|  | @ -202,7 +202,7 @@ func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, opts *g | |||
| 		matchedVersions = append(matchedVersions, v) | ||||
| 	} | ||||
| 	if len(matchedVersions) == 0 { | ||||
| 		return nil, fmt.Errorf("no match found for semver: %s", c.semVer) | ||||
| 		return nil, fmt.Errorf("no match found for semver: %s", c.SemVer) | ||||
| 	} | ||||
| 
 | ||||
| 	// Sort versions
 | ||||
|  |  | |||
|  | @ -77,7 +77,7 @@ func TestCheckoutBranch_Checkout(t *testing.T) { | |||
| 			g := NewWithT(t) | ||||
| 
 | ||||
| 			branch := CheckoutBranch{ | ||||
| 				branch: tt.branch, | ||||
| 				Branch: tt.branch, | ||||
| 			} | ||||
| 			tmpDir, _ := os.MkdirTemp("", "test") | ||||
| 			defer os.RemoveAll(tmpDir) | ||||
|  | @ -148,7 +148,7 @@ func TestCheckoutTag_Checkout(t *testing.T) { | |||
| 			} | ||||
| 
 | ||||
| 			tag := CheckoutTag{ | ||||
| 				tag: tt.checkoutTag, | ||||
| 				Tag: tt.checkoutTag, | ||||
| 			} | ||||
| 			tmpDir, _ := os.MkdirTemp("", "test") | ||||
| 			defer os.RemoveAll(tmpDir) | ||||
|  | @ -188,8 +188,7 @@ func TestCheckoutCommit_Checkout(t *testing.T) { | |||
| 	} | ||||
| 
 | ||||
| 	commit := CheckoutCommit{ | ||||
| 		commit: c.String(), | ||||
| 		branch: "main", | ||||
| 		Commit: c.String(), | ||||
| 	} | ||||
| 	tmpDir, _ := os.MkdirTemp("", "git2go") | ||||
| 	defer os.RemoveAll(tmpDir) | ||||
|  | @ -202,7 +201,7 @@ func TestCheckoutCommit_Checkout(t *testing.T) { | |||
| 	g.Expect(os.ReadFile(filepath.Join(tmpDir, "commit"))).To(BeEquivalentTo("init")) | ||||
| 
 | ||||
| 	commit = CheckoutCommit{ | ||||
| 		commit: "4dc3185c5fc94eb75048376edeb44571cece25f4", | ||||
| 		Commit: "4dc3185c5fc94eb75048376edeb44571cece25f4", | ||||
| 	} | ||||
| 	tmpDir2, _ := os.MkdirTemp("", "git2go") | ||||
| 	defer os.RemoveAll(tmpDir) | ||||
|  | @ -309,7 +308,7 @@ func TestCheckoutTagSemVer_Checkout(t *testing.T) { | |||
| 			g := NewWithT(t) | ||||
| 
 | ||||
| 			semVer := CheckoutSemVer{ | ||||
| 				semVer: tt.constraint, | ||||
| 				SemVer: tt.constraint, | ||||
| 			} | ||||
| 			tmpDir, _ := os.MkdirTemp("", "test") | ||||
| 			defer os.RemoveAll(tmpDir) | ||||
|  |  | |||
|  | @ -0,0 +1,23 @@ | |||
| /* | ||||
| Copyright 2021 The Flux authors | ||||
| 
 | ||||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| you may not use this file except in compliance with the License. | ||||
| You may obtain a copy of the License at | ||||
| 
 | ||||
|     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| 
 | ||||
| Unless required by applicable law or agreed to in writing, software | ||||
| distributed under the License is distributed on an "AS IS" BASIS, | ||||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| See the License for the specific language governing permissions and | ||||
| limitations under the License. | ||||
| */ | ||||
| 
 | ||||
| package libgit2 | ||||
| 
 | ||||
| import "github.com/fluxcd/source-controller/pkg/git" | ||||
| 
 | ||||
| const ( | ||||
| 	Implementation git.Implementation = "libgit2" | ||||
| ) | ||||
|  | @ -32,7 +32,22 @@ const ( | |||
| 
 | ||||
| // CheckoutOptions are the options used for a Git checkout.
 | ||||
| type CheckoutOptions struct { | ||||
| 	GitImplementation string | ||||
| 	// Branch to checkout, can be combined with Branch with some
 | ||||
| 	// Implementations.
 | ||||
| 	Branch string | ||||
| 
 | ||||
| 	// Tag to checkout, takes precedence over Branch.
 | ||||
| 	Tag string | ||||
| 
 | ||||
| 	// SemVer tag expression to checkout, takes precedence over Tag.
 | ||||
| 	SemVer string `json:"semver,omitempty"` | ||||
| 
 | ||||
| 	// Commit SHA1 to checkout, takes precedence over Tag and SemVer,
 | ||||
| 	// can be combined with Branch with some Implementations.
 | ||||
| 	Commit string | ||||
| 
 | ||||
| 	// RecurseSubmodules defines if submodules should be checked out,
 | ||||
| 	// not supported by all Implementations.
 | ||||
| 	RecurseSubmodules bool | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -17,21 +17,23 @@ limitations under the License. | |||
| package strategy | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	sourcev1 "github.com/fluxcd/source-controller/api/v1beta1" | ||||
| 	"github.com/fluxcd/source-controller/pkg/git" | ||||
| 	"github.com/fluxcd/source-controller/pkg/git/gogit" | ||||
| 	"github.com/fluxcd/source-controller/pkg/git/libgit2" | ||||
| ) | ||||
| 
 | ||||
| func CheckoutStrategyForRef(ref *sourcev1.GitRepositoryRef, opt git.CheckoutOptions) (git.CheckoutStrategy, error) { | ||||
| 	switch opt.GitImplementation { | ||||
| 	case sourcev1.GoGitImplementation: | ||||
| 		return gogit.CheckoutStrategyForRef(ref, opt), nil | ||||
| 	case sourcev1.LibGit2Implementation: | ||||
| 		return libgit2.CheckoutStrategyForRef(ref, opt), nil | ||||
| // CheckoutStrategyForImplementation returns the CheckoutStrategy for the given
 | ||||
| // git.Implementation and git.CheckoutOptions.
 | ||||
| func CheckoutStrategyForImplementation(ctx context.Context, impl git.Implementation, opts git.CheckoutOptions) (git.CheckoutStrategy, error) { | ||||
| 	switch impl { | ||||
| 	case gogit.Implementation: | ||||
| 		return gogit.CheckoutStrategyForOptions(ctx, opts), nil | ||||
| 	case libgit2.Implementation: | ||||
| 		return libgit2.CheckoutStrategyForOptions(ctx, opts), nil | ||||
| 	default: | ||||
| 		return nil, fmt.Errorf("unsupported Git implementation %s", opt.GitImplementation) | ||||
| 		return nil, fmt.Errorf("unsupported Git implementation '%s'", impl) | ||||
| 	} | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue