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:
Hidde Beydals 2021-10-24 11:16:52 +02:00 committed by Sunny
parent 5a1fcc213b
commit b7376ce94c
13 changed files with 239 additions and 136 deletions

View File

@ -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"`

View File

@ -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:

View File

@ -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
}

View File

@ -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{

View File

@ -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.

View File

@ -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

View File

@ -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)

23
pkg/git/gogit/gogit.go Normal file
View File

@ -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"
)

View File

@ -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

View File

@ -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)

View File

@ -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"
)

View File

@ -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
}

View File

@ -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)
}
}