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.
|
// GitRepositoryRef defines the Git ref used for pull and checkout operations.
|
||||||
type GitRepositoryRef struct {
|
type GitRepositoryRef struct {
|
||||||
// The Git branch to checkout, defaults to master.
|
// The Git branch to checkout, defaults to master.
|
||||||
// +kubebuilder:default:=master
|
|
||||||
// +optional
|
// +optional
|
||||||
Branch string `json:"branch,omitempty"`
|
Branch string `json:"branch,omitempty"`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,6 @@ spec:
|
||||||
description: The Git reference to checkout and monitor for changes, defaults to master branch.
|
description: The Git reference to checkout and monitor for changes, defaults to master branch.
|
||||||
properties:
|
properties:
|
||||||
branch:
|
branch:
|
||||||
default: master
|
|
||||||
description: The Git branch to checkout, defaults to master.
|
description: The Git branch to checkout, defaults to master.
|
||||||
type: string
|
type: string
|
||||||
commit:
|
commit:
|
||||||
|
|
|
||||||
|
|
@ -249,14 +249,15 @@ func (r *GitRepositoryReconciler) reconcile(ctx context.Context, repository sour
|
||||||
return sourcev1.GitRepositoryNotReady(repository, sourcev1.AuthenticationFailedReason, err.Error()), err
|
return sourcev1.GitRepositoryNotReady(repository, sourcev1.AuthenticationFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
checkoutOpts := git.CheckoutOptions{RecurseSubmodules: repository.Spec.RecurseSubmodules}
|
||||||
checkoutStrategy, err := strategy.CheckoutStrategyForRef(
|
if ref := repository.Spec.Reference; ref != nil {
|
||||||
repository.Spec.Reference,
|
checkoutOpts.Branch = ref.Branch
|
||||||
git.CheckoutOptions{
|
checkoutOpts.Commit = ref.Commit
|
||||||
GitImplementation: repository.Spec.GitImplementation,
|
checkoutOpts.Tag = ref.Tag
|
||||||
RecurseSubmodules: repository.Spec.RecurseSubmodules,
|
checkoutOpts.SemVer = ref.SemVer
|
||||||
},
|
}
|
||||||
)
|
checkoutStrategy, err := strategy.CheckoutStrategyForImplementation(ctx,
|
||||||
|
git.Implementation(repository.Spec.GitImplementation), checkoutOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sourcev1.GitRepositoryNotReady(repository, sourcev1.GitOperationFailedReason, err.Error()), err
|
return sourcev1.GitRepositoryNotReady(repository, sourcev1.GitOperationFailedReason, err.Error()), err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -263,7 +263,7 @@ var _ = Describe("GitRepositoryReconciler", func() {
|
||||||
},
|
},
|
||||||
waitForReason: sourcev1.GitOperationSucceedReason,
|
waitForReason: sourcev1.GitOperationSucceedReason,
|
||||||
expectStatus: metav1.ConditionTrue,
|
expectStatus: metav1.ConditionTrue,
|
||||||
expectRevision: "master",
|
expectRevision: "HEAD",
|
||||||
}),
|
}),
|
||||||
Entry("commit in branch", refTestCase{
|
Entry("commit in branch", refTestCase{
|
||||||
reference: &sourcev1.GitRepositoryRef{
|
reference: &sourcev1.GitRepositoryRef{
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ import (
|
||||||
"github.com/ProtonMail/go-crypto/openpgp"
|
"github.com/ProtonMail/go-crypto/openpgp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Implementation string
|
||||||
|
|
||||||
type Hash []byte
|
type Hash []byte
|
||||||
|
|
||||||
// String returns the SHA1 Hash as a string.
|
// String returns the SHA1 Hash as a string.
|
||||||
|
|
|
||||||
|
|
@ -25,40 +25,38 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
"github.com/Masterminds/semver/v3"
|
||||||
"github.com/fluxcd/pkg/gitutil"
|
|
||||||
"github.com/fluxcd/pkg/version"
|
|
||||||
extgogit "github.com/go-git/go-git/v5"
|
extgogit "github.com/go-git/go-git/v5"
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
"github.com/go-git/go-git/v5/plumbing/object"
|
"github.com/go-git/go-git/v5/plumbing/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"
|
"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 {
|
switch {
|
||||||
case ref == nil:
|
case opts.Commit != "":
|
||||||
return &CheckoutBranch{branch: git.DefaultBranch}
|
return &CheckoutCommit{Branch: opts.Branch, Commit: opts.Commit, RecurseSubmodules: opts.RecurseSubmodules}
|
||||||
case ref.SemVer != "":
|
case opts.SemVer != "":
|
||||||
return &CheckoutSemVer{semVer: ref.SemVer, recurseSubmodules: opt.RecurseSubmodules}
|
return &CheckoutSemVer{SemVer: opts.SemVer, RecurseSubmodules: opts.RecurseSubmodules}
|
||||||
case ref.Tag != "":
|
case opts.Tag != "":
|
||||||
return &CheckoutTag{tag: ref.Tag, recurseSubmodules: opt.RecurseSubmodules}
|
return &CheckoutTag{Tag: opts.Tag, RecurseSubmodules: opts.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}
|
|
||||||
default:
|
default:
|
||||||
return &CheckoutBranch{branch: git.DefaultBranch}
|
branch := opts.Branch
|
||||||
|
if branch == "" {
|
||||||
|
branch = git.DefaultBranch
|
||||||
|
}
|
||||||
|
return &CheckoutBranch{Branch: branch, RecurseSubmodules: opts.RecurseSubmodules}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type CheckoutBranch struct {
|
type CheckoutBranch struct {
|
||||||
branch string
|
Branch string
|
||||||
recurseSubmodules bool
|
RecurseSubmodules bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CheckoutBranch) Checkout(ctx context.Context, path, url string, opts *git.AuthOptions) (*git.Commit, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to construct auth method with options: %w", err)
|
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{
|
repo, err := extgogit.PlainCloneContext(ctx, path, false, &extgogit.CloneOptions{
|
||||||
URL: url,
|
URL: url,
|
||||||
Auth: authMethod,
|
Auth: authMethod,
|
||||||
RemoteName: git.DefaultOrigin,
|
RemoteName: git.DefaultOrigin,
|
||||||
ReferenceName: plumbing.NewBranchReferenceName(c.branch),
|
ReferenceName: plumbing.NewBranchReferenceName(c.Branch),
|
||||||
SingleBranch: true,
|
SingleBranch: true,
|
||||||
NoCheckout: false,
|
NoCheckout: false,
|
||||||
Depth: 1,
|
Depth: 1,
|
||||||
RecurseSubmodules: recurseSubmodules(c.recurseSubmodules),
|
RecurseSubmodules: recurseSubmodules(c.RecurseSubmodules),
|
||||||
Progress: nil,
|
Progress: nil,
|
||||||
Tags: extgogit.NoTags,
|
Tags: extgogit.NoTags,
|
||||||
CABundle: caBundle(opts),
|
CABundle: caBundle(opts),
|
||||||
|
|
@ -85,7 +83,7 @@ func (c *CheckoutBranch) Checkout(ctx context.Context, path, url string, opts *g
|
||||||
}
|
}
|
||||||
head, err := repo.Head()
|
head, err := repo.Head()
|
||||||
if err != nil {
|
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())
|
cc, err := repo.CommitObject(head.Hash())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -95,8 +93,8 @@ func (c *CheckoutBranch) Checkout(ctx context.Context, path, url string, opts *g
|
||||||
}
|
}
|
||||||
|
|
||||||
type CheckoutTag struct {
|
type CheckoutTag struct {
|
||||||
tag string
|
Tag string
|
||||||
recurseSubmodules bool
|
RecurseSubmodules bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CheckoutTag) Checkout(ctx context.Context, path, url string, opts *git.AuthOptions) (*git.Commit, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to construct auth method with options: %w", err)
|
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{
|
repo, err := extgogit.PlainCloneContext(ctx, path, false, &extgogit.CloneOptions{
|
||||||
URL: url,
|
URL: url,
|
||||||
Auth: authMethod,
|
Auth: authMethod,
|
||||||
RemoteName: git.DefaultOrigin,
|
RemoteName: git.DefaultOrigin,
|
||||||
ReferenceName: plumbing.NewTagReferenceName(c.tag),
|
ReferenceName: plumbing.NewTagReferenceName(c.Tag),
|
||||||
SingleBranch: true,
|
SingleBranch: true,
|
||||||
NoCheckout: false,
|
NoCheckout: false,
|
||||||
Depth: 1,
|
Depth: 1,
|
||||||
RecurseSubmodules: recurseSubmodules(c.recurseSubmodules),
|
RecurseSubmodules: recurseSubmodules(c.RecurseSubmodules),
|
||||||
Progress: nil,
|
Progress: nil,
|
||||||
Tags: extgogit.NoTags,
|
Tags: extgogit.NoTags,
|
||||||
CABundle: caBundle(opts),
|
CABundle: caBundle(opts),
|
||||||
|
|
@ -123,7 +121,7 @@ func (c *CheckoutTag) Checkout(ctx context.Context, path, url string, opts *git.
|
||||||
}
|
}
|
||||||
head, err := repo.Head()
|
head, err := repo.Head()
|
||||||
if err != nil {
|
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())
|
cc, err := repo.CommitObject(head.Hash())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -133,9 +131,9 @@ func (c *CheckoutTag) Checkout(ctx context.Context, path, url string, opts *git.
|
||||||
}
|
}
|
||||||
|
|
||||||
type CheckoutCommit struct {
|
type CheckoutCommit struct {
|
||||||
branch string
|
Branch string
|
||||||
commit string
|
Commit string
|
||||||
recurseSubmodules bool
|
RecurseSubmodules bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CheckoutCommit) Checkout(ctx context.Context, path, url string, opts *git.AuthOptions) (*git.Commit, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to construct auth method with options: %w", err)
|
return nil, fmt.Errorf("failed to construct auth method with options: %w", err)
|
||||||
}
|
}
|
||||||
ref := plumbing.NewBranchReferenceName(c.branch)
|
cloneOpts := &extgogit.CloneOptions{
|
||||||
repo, err := extgogit.PlainCloneContext(ctx, path, false, &extgogit.CloneOptions{
|
|
||||||
URL: url,
|
URL: url,
|
||||||
Auth: authMethod,
|
Auth: authMethod,
|
||||||
RemoteName: git.DefaultOrigin,
|
RemoteName: git.DefaultOrigin,
|
||||||
ReferenceName: ref,
|
SingleBranch: false,
|
||||||
SingleBranch: true,
|
NoCheckout: true,
|
||||||
NoCheckout: false,
|
RecurseSubmodules: recurseSubmodules(c.RecurseSubmodules),
|
||||||
RecurseSubmodules: recurseSubmodules(c.recurseSubmodules),
|
|
||||||
Progress: nil,
|
Progress: nil,
|
||||||
Tags: extgogit.NoTags,
|
Tags: extgogit.NoTags,
|
||||||
CABundle: caBundle(opts),
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to clone '%s', error: %w", url, gitutil.GoGitError(err))
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to open Git worktree: %w", err)
|
return nil, fmt.Errorf("failed to open Git worktree: %w", err)
|
||||||
}
|
}
|
||||||
f, _ := repo.Head()
|
cc, err := repo.CommitObject(plumbing.NewHash(c.Commit))
|
||||||
f.String()
|
|
||||||
cc, err := repo.CommitObject(plumbing.NewHash(c.commit))
|
|
||||||
if err != nil {
|
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{
|
err = w.Checkout(&extgogit.CheckoutOptions{
|
||||||
Hash: cc.Hash,
|
Hash: cc.Hash,
|
||||||
Force: true,
|
Force: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
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 {
|
type CheckoutSemVer struct {
|
||||||
semVer string
|
SemVer string
|
||||||
recurseSubmodules bool
|
RecurseSubmodules bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, opts *git.AuthOptions) (*git.Commit, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("semver parse error: %w", err)
|
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,
|
RemoteName: git.DefaultOrigin,
|
||||||
NoCheckout: false,
|
NoCheckout: false,
|
||||||
Depth: 1,
|
Depth: 1,
|
||||||
RecurseSubmodules: recurseSubmodules(c.recurseSubmodules),
|
RecurseSubmodules: recurseSubmodules(c.RecurseSubmodules),
|
||||||
Progress: nil,
|
Progress: nil,
|
||||||
Tags: extgogit.AllTags,
|
Tags: extgogit.AllTags,
|
||||||
CABundle: caBundle(opts),
|
CABundle: caBundle(opts),
|
||||||
|
|
@ -247,7 +246,7 @@ func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, opts *g
|
||||||
matchedVersions = append(matchedVersions, v)
|
matchedVersions = append(matchedVersions, v)
|
||||||
}
|
}
|
||||||
if len(matchedVersions) == 0 {
|
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
|
// Sort versions
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ func TestCheckoutBranch_Checkout(t *testing.T) {
|
||||||
g := NewWithT(t)
|
g := NewWithT(t)
|
||||||
|
|
||||||
branch := CheckoutBranch{
|
branch := CheckoutBranch{
|
||||||
branch: tt.branch,
|
Branch: tt.branch,
|
||||||
}
|
}
|
||||||
tmpDir, _ := os.MkdirTemp("", "test")
|
tmpDir, _ := os.MkdirTemp("", "test")
|
||||||
defer os.RemoveAll(tmpDir)
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
@ -152,7 +152,7 @@ func TestCheckoutTag_Checkout(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
tag := CheckoutTag{
|
tag := CheckoutTag{
|
||||||
tag: tt.checkoutTag,
|
Tag: tt.checkoutTag,
|
||||||
}
|
}
|
||||||
tmpDir, _ := os.MkdirTemp("", "test")
|
tmpDir, _ := os.MkdirTemp("", "test")
|
||||||
defer os.RemoveAll(tmpDir)
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
@ -173,46 +173,87 @@ func TestCheckoutTag_Checkout(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckoutCommit_Checkout(t *testing.T) {
|
func TestCheckoutCommit_Checkout(t *testing.T) {
|
||||||
g := NewWithT(t)
|
|
||||||
|
|
||||||
repo, path, err := initRepo()
|
repo, path, err := initRepo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(path)
|
defer os.RemoveAll(path)
|
||||||
|
|
||||||
c, err := commitFile(repo, "commit", "init", time.Now())
|
firstCommit, err := commitFile(repo, "commit", "init", time.Now())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
commit := CheckoutCommit{
|
tests := []struct {
|
||||||
commit: c.String(),
|
name string
|
||||||
branch: "master",
|
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")
|
for _, tt := range tests {
|
||||||
defer os.RemoveAll(tmpDir)
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
g := NewWithT(t)
|
||||||
|
|
||||||
cc, err := commit.Checkout(context.TODO(), tmpDir, path, nil)
|
commit := CheckoutCommit{
|
||||||
g.Expect(err).ToNot(HaveOccurred())
|
Commit: tt.commit,
|
||||||
g.Expect(cc.String()).To(Equal("master" + "/" + c.String()))
|
Branch: tt.branch,
|
||||||
g.Expect(filepath.Join(tmpDir, "commit")).To(BeARegularFile())
|
}
|
||||||
g.Expect(os.ReadFile(filepath.Join(tmpDir, "commit"))).To(BeEquivalentTo("init"))
|
|
||||||
|
|
||||||
commit = CheckoutCommit{
|
tmpDir, err := os.MkdirTemp("", "git2go")
|
||||||
commit: "4dc3185c5fc94eb75048376edeb44571cece25f4",
|
if err != nil {
|
||||||
branch: "master",
|
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) {
|
func TestCheckoutTagSemVer_Checkout(t *testing.T) {
|
||||||
|
|
@ -300,7 +341,7 @@ func TestCheckoutTagSemVer_Checkout(t *testing.T) {
|
||||||
g := NewWithT(t)
|
g := NewWithT(t)
|
||||||
|
|
||||||
semVer := CheckoutSemVer{
|
semVer := CheckoutSemVer{
|
||||||
semVer: tt.constraint,
|
SemVer: tt.constraint,
|
||||||
}
|
}
|
||||||
tmpDir, _ := os.MkdirTemp("", "test")
|
tmpDir, _ := os.MkdirTemp("", "test")
|
||||||
defer os.RemoveAll(tmpDir)
|
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"
|
"time"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
"github.com/Masterminds/semver/v3"
|
||||||
|
"github.com/go-logr/logr"
|
||||||
git2go "github.com/libgit2/git2go/v31"
|
git2go "github.com/libgit2/git2go/v31"
|
||||||
|
|
||||||
"github.com/fluxcd/pkg/gitutil"
|
"github.com/fluxcd/pkg/gitutil"
|
||||||
"github.com/fluxcd/pkg/version"
|
"github.com/fluxcd/pkg/version"
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
|
||||||
"github.com/fluxcd/source-controller/pkg/git"
|
"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 {
|
switch {
|
||||||
case ref == nil:
|
case opt.Commit != "":
|
||||||
return &CheckoutBranch{branch: git.DefaultBranch}
|
return &CheckoutCommit{Commit: opt.Commit}
|
||||||
case ref.SemVer != "":
|
case opt.SemVer != "":
|
||||||
return &CheckoutSemVer{semVer: ref.SemVer}
|
return &CheckoutSemVer{SemVer: opt.SemVer}
|
||||||
case ref.Tag != "":
|
case opt.Tag != "":
|
||||||
return &CheckoutTag{tag: ref.Tag}
|
return &CheckoutTag{Tag: opt.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}
|
|
||||||
default:
|
default:
|
||||||
return &CheckoutBranch{branch: git.DefaultBranch}
|
branch := opt.Branch
|
||||||
|
if branch == "" {
|
||||||
|
branch = git.DefaultBranch
|
||||||
|
}
|
||||||
|
return &CheckoutBranch{Branch: branch}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type CheckoutBranch struct {
|
type CheckoutBranch struct {
|
||||||
branch string
|
Branch string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CheckoutBranch) Checkout(ctx context.Context, path, url string, opts *git.AuthOptions) (*git.Commit, error) {
|
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,
|
DownloadTags: git2go.DownloadTagsNone,
|
||||||
RemoteCallbacks: remoteCallbacks(opts),
|
RemoteCallbacks: remoteCallbacks(opts),
|
||||||
},
|
},
|
||||||
CheckoutBranch: c.branch,
|
CheckoutBranch: c.Branch,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to clone '%s', error: %w", url, gitutil.LibGit2Error(err))
|
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()
|
defer head.Free()
|
||||||
cc, err := repo.LookupCommit(head.Target())
|
cc, err := repo.LookupCommit(head.Target())
|
||||||
if err != nil {
|
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()
|
defer cc.Free()
|
||||||
return commit(cc, "refs/heads/"+c.branch), nil
|
return commit(cc, "refs/heads/"+c.Branch), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type CheckoutTag struct {
|
type CheckoutTag struct {
|
||||||
tag string
|
Tag string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CheckoutTag) Checkout(ctx context.Context, path, url string, opts *git.AuthOptions) (*git.Commit, error) {
|
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))
|
return nil, fmt.Errorf("unable to clone '%s', error: %w", url, gitutil.LibGit2Error(err))
|
||||||
}
|
}
|
||||||
defer repo.Free()
|
defer repo.Free()
|
||||||
cc, err := checkoutDetachedDwim(repo, c.tag)
|
cc, err := checkoutDetachedDwim(repo, c.Tag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer cc.Free()
|
defer cc.Free()
|
||||||
return commit(cc, "refs/tags/"+c.tag), nil
|
return commit(cc, "refs/tags/"+c.Tag), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type CheckoutCommit struct {
|
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) {
|
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))
|
return nil, fmt.Errorf("unable to clone '%s', error: %w", url, gitutil.LibGit2Error(err))
|
||||||
}
|
}
|
||||||
defer repo.Free()
|
defer repo.Free()
|
||||||
oid, err := git2go.NewOid(c.commit)
|
oid, err := git2go.NewOid(c.Commit)
|
||||||
if err != nil {
|
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)
|
cc, err := checkoutDetachedHEAD(repo, oid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -134,11 +134,11 @@ func (c *CheckoutCommit) Checkout(ctx context.Context, path, url string, opts *g
|
||||||
}
|
}
|
||||||
|
|
||||||
type CheckoutSemVer struct {
|
type CheckoutSemVer struct {
|
||||||
semVer string
|
SemVer string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, opts *git.AuthOptions) (*git.Commit, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("semver parse error: %w", err)
|
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)
|
matchedVersions = append(matchedVersions, v)
|
||||||
}
|
}
|
||||||
if len(matchedVersions) == 0 {
|
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
|
// Sort versions
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ func TestCheckoutBranch_Checkout(t *testing.T) {
|
||||||
g := NewWithT(t)
|
g := NewWithT(t)
|
||||||
|
|
||||||
branch := CheckoutBranch{
|
branch := CheckoutBranch{
|
||||||
branch: tt.branch,
|
Branch: tt.branch,
|
||||||
}
|
}
|
||||||
tmpDir, _ := os.MkdirTemp("", "test")
|
tmpDir, _ := os.MkdirTemp("", "test")
|
||||||
defer os.RemoveAll(tmpDir)
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
@ -148,7 +148,7 @@ func TestCheckoutTag_Checkout(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
tag := CheckoutTag{
|
tag := CheckoutTag{
|
||||||
tag: tt.checkoutTag,
|
Tag: tt.checkoutTag,
|
||||||
}
|
}
|
||||||
tmpDir, _ := os.MkdirTemp("", "test")
|
tmpDir, _ := os.MkdirTemp("", "test")
|
||||||
defer os.RemoveAll(tmpDir)
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
@ -188,8 +188,7 @@ func TestCheckoutCommit_Checkout(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
commit := CheckoutCommit{
|
commit := CheckoutCommit{
|
||||||
commit: c.String(),
|
Commit: c.String(),
|
||||||
branch: "main",
|
|
||||||
}
|
}
|
||||||
tmpDir, _ := os.MkdirTemp("", "git2go")
|
tmpDir, _ := os.MkdirTemp("", "git2go")
|
||||||
defer os.RemoveAll(tmpDir)
|
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"))
|
g.Expect(os.ReadFile(filepath.Join(tmpDir, "commit"))).To(BeEquivalentTo("init"))
|
||||||
|
|
||||||
commit = CheckoutCommit{
|
commit = CheckoutCommit{
|
||||||
commit: "4dc3185c5fc94eb75048376edeb44571cece25f4",
|
Commit: "4dc3185c5fc94eb75048376edeb44571cece25f4",
|
||||||
}
|
}
|
||||||
tmpDir2, _ := os.MkdirTemp("", "git2go")
|
tmpDir2, _ := os.MkdirTemp("", "git2go")
|
||||||
defer os.RemoveAll(tmpDir)
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
@ -309,7 +308,7 @@ func TestCheckoutTagSemVer_Checkout(t *testing.T) {
|
||||||
g := NewWithT(t)
|
g := NewWithT(t)
|
||||||
|
|
||||||
semVer := CheckoutSemVer{
|
semVer := CheckoutSemVer{
|
||||||
semVer: tt.constraint,
|
SemVer: tt.constraint,
|
||||||
}
|
}
|
||||||
tmpDir, _ := os.MkdirTemp("", "test")
|
tmpDir, _ := os.MkdirTemp("", "test")
|
||||||
defer os.RemoveAll(tmpDir)
|
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.
|
// CheckoutOptions are the options used for a Git checkout.
|
||||||
type CheckoutOptions struct {
|
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
|
RecurseSubmodules bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,21 +17,23 @@ limitations under the License.
|
||||||
package strategy
|
package strategy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
|
|
||||||
"github.com/fluxcd/source-controller/pkg/git"
|
"github.com/fluxcd/source-controller/pkg/git"
|
||||||
"github.com/fluxcd/source-controller/pkg/git/gogit"
|
"github.com/fluxcd/source-controller/pkg/git/gogit"
|
||||||
"github.com/fluxcd/source-controller/pkg/git/libgit2"
|
"github.com/fluxcd/source-controller/pkg/git/libgit2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CheckoutStrategyForRef(ref *sourcev1.GitRepositoryRef, opt git.CheckoutOptions) (git.CheckoutStrategy, error) {
|
// CheckoutStrategyForImplementation returns the CheckoutStrategy for the given
|
||||||
switch opt.GitImplementation {
|
// git.Implementation and git.CheckoutOptions.
|
||||||
case sourcev1.GoGitImplementation:
|
func CheckoutStrategyForImplementation(ctx context.Context, impl git.Implementation, opts git.CheckoutOptions) (git.CheckoutStrategy, error) {
|
||||||
return gogit.CheckoutStrategyForRef(ref, opt), nil
|
switch impl {
|
||||||
case sourcev1.LibGit2Implementation:
|
case gogit.Implementation:
|
||||||
return libgit2.CheckoutStrategyForRef(ref, opt), nil
|
return gogit.CheckoutStrategyForOptions(ctx, opts), nil
|
||||||
|
case libgit2.Implementation:
|
||||||
|
return libgit2.CheckoutStrategyForOptions(ctx, opts), nil
|
||||||
default:
|
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