Skip to content

Commit bcf83cb

Browse files
committed
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>
1 parent 5593778 commit bcf83cb

12 files changed

+241
-137
lines changed

api/v1beta1/gitrepository_types.go

-1
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,6 @@ type GitRepositoryInclude struct {
120120
// GitRepositoryRef defines the Git ref used for pull and checkout operations.
121121
type GitRepositoryRef struct {
122122
// The Git branch to checkout, defaults to master.
123-
// +kubebuilder:default:=master
124123
// +optional
125124
Branch string `json:"branch,omitempty"`
126125

config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml

-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ spec:
9191
description: The Git reference to checkout and monitor for changes, defaults to master branch.
9292
properties:
9393
branch:
94-
default: master
9594
description: The Git branch to checkout, defaults to master.
9695
type: string
9796
commit:

controllers/gitrepository_controller.go

+9-8
Original file line numberDiff line numberDiff line change
@@ -249,14 +249,15 @@ func (r *GitRepositoryReconciler) reconcile(ctx context.Context, repository sour
249249
return sourcev1.GitRepositoryNotReady(repository, sourcev1.AuthenticationFailedReason, err.Error()), err
250250
}
251251
}
252-
253-
checkoutStrategy, err := strategy.CheckoutStrategyForRef(
254-
repository.Spec.Reference,
255-
git.CheckoutOptions{
256-
GitImplementation: repository.Spec.GitImplementation,
257-
RecurseSubmodules: repository.Spec.RecurseSubmodules,
258-
},
259-
)
252+
checkoutOpts := git.CheckoutOptions{RecurseSubmodules: repository.Spec.RecurseSubmodules}
253+
if ref := repository.Spec.Reference; ref != nil {
254+
checkoutOpts.Branch = ref.Branch
255+
checkoutOpts.Commit = ref.Commit
256+
checkoutOpts.Tag = ref.Tag
257+
checkoutOpts.SemVer = ref.SemVer
258+
}
259+
checkoutStrategy, err := strategy.CheckoutStrategyForImplementation(ctx,
260+
git.Implementation(repository.Spec.GitImplementation), checkoutOpts)
260261
if err != nil {
261262
return sourcev1.GitRepositoryNotReady(repository, sourcev1.GitOperationFailedReason, err.Error()), err
262263
}

pkg/git/git.go

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import (
2626
"github.com/ProtonMail/go-crypto/openpgp"
2727
)
2828

29+
type Implementation string
30+
2931
type Hash []byte
3032

3133
// String returns the SHA1 Hash as a string.

pkg/git/gogit/checkout.go

+51-52
Original file line numberDiff line numberDiff line change
@@ -25,57 +25,55 @@ import (
2525
"time"
2626

2727
"github.com/Masterminds/semver/v3"
28-
"github.com/fluxcd/pkg/gitutil"
29-
"github.com/fluxcd/pkg/version"
3028
extgogit "github.com/go-git/go-git/v5"
3129
"github.com/go-git/go-git/v5/plumbing"
3230
"github.com/go-git/go-git/v5/plumbing/object"
3331

34-
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
32+
"github.com/fluxcd/pkg/gitutil"
33+
"github.com/fluxcd/pkg/version"
34+
3535
"github.com/fluxcd/source-controller/pkg/git"
3636
)
3737

38-
func CheckoutStrategyForRef(ref *sourcev1.GitRepositoryRef, opt git.CheckoutOptions) git.CheckoutStrategy {
38+
// CheckoutStrategyForOptions returns the git.CheckoutStrategy for the given
39+
// git.CheckoutOptions.
40+
func CheckoutStrategyForOptions(_ context.Context, opts git.CheckoutOptions) git.CheckoutStrategy {
3941
switch {
40-
case ref == nil:
41-
return &CheckoutBranch{branch: git.DefaultBranch}
42-
case ref.SemVer != "":
43-
return &CheckoutSemVer{semVer: ref.SemVer, recurseSubmodules: opt.RecurseSubmodules}
44-
case ref.Tag != "":
45-
return &CheckoutTag{tag: ref.Tag, recurseSubmodules: opt.RecurseSubmodules}
46-
case ref.Commit != "":
47-
strategy := &CheckoutCommit{branch: ref.Branch, commit: ref.Commit, recurseSubmodules: opt.RecurseSubmodules}
48-
if strategy.branch == "" {
49-
strategy.branch = git.DefaultBranch
50-
}
51-
return strategy
52-
case ref.Branch != "":
53-
return &CheckoutBranch{branch: ref.Branch, recurseSubmodules: opt.RecurseSubmodules}
42+
case opts.Commit != "":
43+
return &CheckoutCommit{Branch: opts.Branch, Commit: opts.Commit, RecurseSubmodules: opts.RecurseSubmodules}
44+
case opts.SemVer != "":
45+
return &CheckoutSemVer{SemVer: opts.SemVer, RecurseSubmodules: opts.RecurseSubmodules}
46+
case opts.Tag != "":
47+
return &CheckoutTag{Tag: opts.Tag, RecurseSubmodules: opts.RecurseSubmodules}
5448
default:
55-
return &CheckoutBranch{branch: git.DefaultBranch}
49+
branch := opts.Branch
50+
if branch == "" {
51+
branch = git.DefaultBranch
52+
}
53+
return &CheckoutBranch{Branch: branch}
5654
}
5755
}
5856

5957
type CheckoutBranch struct {
60-
branch string
61-
recurseSubmodules bool
58+
Branch string
59+
RecurseSubmodules bool
6260
}
6361

6462
func (c *CheckoutBranch) Checkout(ctx context.Context, path, url string, opts *git.AuthOptions) (*git.Commit, error) {
6563
authMethod, err := transportAuth(opts)
6664
if err != nil {
6765
return nil, fmt.Errorf("failed to construct auth method with options: %w", err)
6866
}
69-
ref := plumbing.NewBranchReferenceName(c.branch)
67+
ref := plumbing.NewBranchReferenceName(c.Branch)
7068
repo, err := extgogit.PlainCloneContext(ctx, path, false, &extgogit.CloneOptions{
7169
URL: url,
7270
Auth: authMethod,
7371
RemoteName: git.DefaultOrigin,
74-
ReferenceName: plumbing.NewBranchReferenceName(c.branch),
72+
ReferenceName: plumbing.NewBranchReferenceName(c.Branch),
7573
SingleBranch: true,
7674
NoCheckout: false,
7775
Depth: 1,
78-
RecurseSubmodules: recurseSubmodules(c.recurseSubmodules),
76+
RecurseSubmodules: recurseSubmodules(c.RecurseSubmodules),
7977
Progress: nil,
8078
Tags: extgogit.NoTags,
8179
CABundle: caBundle(opts),
@@ -85,7 +83,7 @@ func (c *CheckoutBranch) Checkout(ctx context.Context, path, url string, opts *g
8583
}
8684
head, err := repo.Head()
8785
if err != nil {
88-
return nil, fmt.Errorf("failed to resolve HEAD of branch '%s': %w", c.branch, err)
86+
return nil, fmt.Errorf("failed to resolve HEAD of branch '%s': %w", c.Branch, err)
8987
}
9088
cc, err := repo.CommitObject(head.Hash())
9189
if err != nil {
@@ -95,25 +93,25 @@ func (c *CheckoutBranch) Checkout(ctx context.Context, path, url string, opts *g
9593
}
9694

9795
type CheckoutTag struct {
98-
tag string
99-
recurseSubmodules bool
96+
Tag string
97+
RecurseSubmodules bool
10098
}
10199

102100
func (c *CheckoutTag) Checkout(ctx context.Context, path, url string, opts *git.AuthOptions) (*git.Commit, error) {
103101
authMethod, err := transportAuth(opts)
104102
if err != nil {
105103
return nil, fmt.Errorf("failed to construct auth method with options: %w", err)
106104
}
107-
ref := plumbing.NewTagReferenceName(c.tag)
105+
ref := plumbing.NewTagReferenceName(c.Tag)
108106
repo, err := extgogit.PlainCloneContext(ctx, path, false, &extgogit.CloneOptions{
109107
URL: url,
110108
Auth: authMethod,
111109
RemoteName: git.DefaultOrigin,
112-
ReferenceName: plumbing.NewTagReferenceName(c.tag),
110+
ReferenceName: plumbing.NewTagReferenceName(c.Tag),
113111
SingleBranch: true,
114112
NoCheckout: false,
115113
Depth: 1,
116-
RecurseSubmodules: recurseSubmodules(c.recurseSubmodules),
114+
RecurseSubmodules: recurseSubmodules(c.RecurseSubmodules),
117115
Progress: nil,
118116
Tags: extgogit.NoTags,
119117
CABundle: caBundle(opts),
@@ -123,7 +121,7 @@ func (c *CheckoutTag) Checkout(ctx context.Context, path, url string, opts *git.
123121
}
124122
head, err := repo.Head()
125123
if err != nil {
126-
return nil, fmt.Errorf("failed to resolve HEAD of tag '%s': %w", c.tag, err)
124+
return nil, fmt.Errorf("failed to resolve HEAD of tag '%s': %w", c.Tag, err)
127125
}
128126
cc, err := repo.CommitObject(head.Hash())
129127
if err != nil {
@@ -133,59 +131,60 @@ func (c *CheckoutTag) Checkout(ctx context.Context, path, url string, opts *git.
133131
}
134132

135133
type CheckoutCommit struct {
136-
branch string
137-
commit string
138-
recurseSubmodules bool
134+
Branch string
135+
Commit string
136+
RecurseSubmodules bool
139137
}
140138

141139
func (c *CheckoutCommit) Checkout(ctx context.Context, path, url string, opts *git.AuthOptions) (*git.Commit, error) {
142140
authMethod, err := transportAuth(opts)
143141
if err != nil {
144142
return nil, fmt.Errorf("failed to construct auth method with options: %w", err)
145143
}
146-
ref := plumbing.NewBranchReferenceName(c.branch)
147-
repo, err := extgogit.PlainCloneContext(ctx, path, false, &extgogit.CloneOptions{
144+
cloneOpts := &extgogit.CloneOptions{
148145
URL: url,
149146
Auth: authMethod,
150147
RemoteName: git.DefaultOrigin,
151-
ReferenceName: ref,
152-
SingleBranch: true,
153-
NoCheckout: false,
154-
RecurseSubmodules: recurseSubmodules(c.recurseSubmodules),
148+
SingleBranch: false,
149+
NoCheckout: true,
150+
RecurseSubmodules: recurseSubmodules(c.RecurseSubmodules),
155151
Progress: nil,
156152
Tags: extgogit.NoTags,
157153
CABundle: caBundle(opts),
158-
})
154+
}
155+
if c.Branch != "" {
156+
cloneOpts.SingleBranch = true
157+
cloneOpts.ReferenceName = plumbing.NewBranchReferenceName(c.Branch)
158+
}
159+
repo, err := extgogit.PlainCloneContext(ctx, path, false, cloneOpts)
159160
if err != nil {
160161
return nil, fmt.Errorf("unable to clone '%s', error: %w", url, gitutil.GoGitError(err))
161162
}
162163
w, err := repo.Worktree()
163164
if err != nil {
164165
return nil, fmt.Errorf("failed to open Git worktree: %w", err)
165166
}
166-
f, _ := repo.Head()
167-
f.String()
168-
cc, err := repo.CommitObject(plumbing.NewHash(c.commit))
167+
cc, err := repo.CommitObject(plumbing.NewHash(c.Commit))
169168
if err != nil {
170-
return nil, fmt.Errorf("failed to resolve commit object for '%s': %w", c.commit, err)
169+
return nil, fmt.Errorf("failed to resolve commit object for '%s': %w", c.Commit, err)
171170
}
172171
err = w.Checkout(&extgogit.CheckoutOptions{
173172
Hash: cc.Hash,
174173
Force: true,
175174
})
176175
if err != nil {
177-
return nil, fmt.Errorf("failed to checkout commit '%s': %w", c.commit, err)
176+
return nil, fmt.Errorf("failed to checkout commit '%s': %w", c.Commit, err)
178177
}
179-
return commitWithRef(cc, ref)
178+
return commitWithRef(cc, cloneOpts.ReferenceName)
180179
}
181180

182181
type CheckoutSemVer struct {
183-
semVer string
184-
recurseSubmodules bool
182+
SemVer string
183+
RecurseSubmodules bool
185184
}
186185

187186
func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, opts *git.AuthOptions) (*git.Commit, error) {
188-
verConstraint, err := semver.NewConstraint(c.semVer)
187+
verConstraint, err := semver.NewConstraint(c.SemVer)
189188
if err != nil {
190189
return nil, fmt.Errorf("semver parse error: %w", err)
191190
}
@@ -201,7 +200,7 @@ func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, opts *g
201200
RemoteName: git.DefaultOrigin,
202201
NoCheckout: false,
203202
Depth: 1,
204-
RecurseSubmodules: recurseSubmodules(c.recurseSubmodules),
203+
RecurseSubmodules: recurseSubmodules(c.RecurseSubmodules),
205204
Progress: nil,
206205
Tags: extgogit.AllTags,
207206
CABundle: caBundle(opts),
@@ -247,7 +246,7 @@ func (c *CheckoutSemVer) Checkout(ctx context.Context, path, url string, opts *g
247246
matchedVersions = append(matchedVersions, v)
248247
}
249248
if len(matchedVersions) == 0 {
250-
return nil, fmt.Errorf("no match found for semver: %s", c.semVer)
249+
return nil, fmt.Errorf("no match found for semver: %s", c.SemVer)
251250
}
252251

253252
// Sort versions

0 commit comments

Comments
 (0)