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(),
|
||||
branch: "master",
|
||||
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",
|
||||
},
|
||||
}
|
||||
tmpDir, _ := os.MkdirTemp("", "git2go")
|
||||
defer os.RemoveAll(tmpDir)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
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: tt.commit,
|
||||
Branch: tt.branch,
|
||||
}
|
||||
|
||||
commit = CheckoutCommit{
|
||||
commit: "4dc3185c5fc94eb75048376edeb44571cece25f4",
|
||||
branch: "master",
|
||||
tmpDir, err := os.MkdirTemp("", "git2go")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
cc, err := commit.Checkout(context.TODO(), tmpDir, path, nil)
|
||||
if tt.expectError != "" {
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
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))
|
||||
})
|
||||
}
|
||||
tmpDir2, _ := os.MkdirTemp("", "git2go")
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
cc, err = commit.Checkout(context.TODO(), tmpDir2, path, nil)
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(err.Error()).To(ContainSubstring("object not found"))
|
||||
g.Expect(cc).To(BeNil())
|
||||
}
|
||||
|
||||
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 {
|
||||
// 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)
|
||||
}
|
||||
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
|
||||
}
|
||||
return strategy
|
||||
case ref.Branch != "":
|
||||
return &CheckoutBranch{branch: ref.Branch}
|
||||
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