Skip to content

Commit

Permalink
gogit: allow checkout of commit without branch
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
hiddeco committed Oct 24, 2021
1 parent 5593778 commit bcf83cb
Show file tree
Hide file tree
Showing 12 changed files with 241 additions and 137 deletions.
1 change: 0 additions & 1 deletion api/v1beta1/gitrepository_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
17 changes: 9 additions & 8 deletions controllers/gitrepository_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
103 changes: 51 additions & 52 deletions pkg/git/gogit/checkout.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,57 +25,55 @@ 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}
}
}

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) {
authMethod, err := transportAuth(opts)
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),
Expand All @@ -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 {
Expand All @@ -95,25 +93,25 @@ 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) {
authMethod, err := transportAuth(opts)
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),
Expand All @@ -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 {
Expand All @@ -133,59 +131,60 @@ 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) {
authMethod, err := transportAuth(opts)
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))
}
w, err := repo.Worktree()
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)
}
Expand All @@ -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),
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit bcf83cb

Please sign in to comment.