Skip to content

Commit 40b1369

Browse files
committed
git: add checkout strategies
1 parent e42561f commit 40b1369

File tree

1 file changed

+192
-0
lines changed

1 file changed

+192
-0
lines changed

internal/git/checkout.go

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
/*
2+
Copyright 2020 The Flux CD contributors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package git
18+
19+
import (
20+
"context"
21+
"fmt"
22+
23+
"github.com/blang/semver"
24+
"github.com/go-git/go-git/v5"
25+
"github.com/go-git/go-git/v5/plumbing"
26+
)
27+
28+
type CheckoutStrategy interface {
29+
Checkout(ctx context.Context, path string) error
30+
}
31+
32+
type CheckoutBranch struct {
33+
url string
34+
branch string
35+
}
36+
37+
func (c *CheckoutBranch) Checkout(ctx context.Context, path string) (string, error) {
38+
repo, err := git.PlainCloneContext(ctx, path, false, &git.CloneOptions{
39+
URL: c.url,
40+
RemoteName: "origin",
41+
ReferenceName: plumbing.NewBranchReferenceName(c.branch),
42+
SingleBranch: true,
43+
NoCheckout: false,
44+
Depth: 1,
45+
RecurseSubmodules: 0,
46+
Progress: nil,
47+
Tags: git.NoTags,
48+
})
49+
if err != nil {
50+
return "", fmt.Errorf("git clone error: %w", err)
51+
}
52+
head, err := repo.Head()
53+
if err != nil {
54+
return "", fmt.Errorf(" git resolve HEAD error: %w", err)
55+
}
56+
return fmt.Sprintf("%s/%s", c.branch, head.Hash().String()), nil
57+
}
58+
59+
type CheckoutTag struct {
60+
url string
61+
tag string
62+
}
63+
64+
func (c *CheckoutTag) Checkout(ctx context.Context, path string) (string, error) {
65+
repo, err := git.PlainCloneContext(ctx, path, false, &git.CloneOptions{
66+
URL: c.url,
67+
RemoteName: "origin",
68+
ReferenceName: plumbing.NewTagReferenceName(c.tag),
69+
SingleBranch: true,
70+
NoCheckout: false,
71+
Depth: 1,
72+
RecurseSubmodules: 0,
73+
Progress: nil,
74+
Tags: git.NoTags,
75+
})
76+
if err != nil {
77+
return "", fmt.Errorf("git clone error: %w", err)
78+
}
79+
head, err := repo.Head()
80+
if err != nil {
81+
return "", fmt.Errorf(" git resolve HEAD error: %w", err)
82+
}
83+
return fmt.Sprintf("%s/%s", c.tag, head.Hash().String()), nil
84+
}
85+
86+
type CheckoutCommit struct {
87+
url string
88+
branch string
89+
commit string
90+
}
91+
92+
func (c *CheckoutCommit) Checkout(ctx context.Context, path string) (string, error) {
93+
repo, err := git.PlainCloneContext(ctx, path, false, &git.CloneOptions{
94+
URL: c.url,
95+
RemoteName: "origin",
96+
ReferenceName: plumbing.NewBranchReferenceName(c.branch),
97+
SingleBranch: true,
98+
NoCheckout: false,
99+
RecurseSubmodules: 0,
100+
Progress: nil,
101+
Tags: git.NoTags,
102+
})
103+
if err != nil {
104+
return "", fmt.Errorf("git clone error: %w", err)
105+
}
106+
w, err := repo.Worktree()
107+
if err != nil {
108+
return "", fmt.Errorf("git worktree error: %w", err)
109+
}
110+
commit, err := repo.CommitObject(plumbing.NewHash(c.commit))
111+
if err != nil {
112+
return "", fmt.Errorf("git commit not found: %w", err)
113+
}
114+
err = w.Checkout(&git.CheckoutOptions{
115+
Hash: commit.Hash,
116+
Force: true,
117+
})
118+
if err != nil {
119+
return "", fmt.Errorf("git checkout error: %w", err)
120+
}
121+
return fmt.Sprintf("%s/%s", c.branch, commit.Hash.String()), nil
122+
}
123+
124+
type CheckoutSemVer struct {
125+
url string
126+
semver string
127+
}
128+
129+
func (c *CheckoutSemVer) Checkout(ctx context.Context, path string) (string, error) {
130+
rng, err := semver.ParseRange(c.semver)
131+
if err != nil {
132+
return "", fmt.Errorf("semver parse range error: %w", err)
133+
}
134+
135+
repo, err := git.PlainCloneContext(ctx, path, false, &git.CloneOptions{
136+
URL: c.url,
137+
RemoteName: "origin",
138+
SingleBranch: true,
139+
NoCheckout: false,
140+
Depth: 1,
141+
RecurseSubmodules: 0,
142+
Progress: nil,
143+
Tags: git.AllTags,
144+
})
145+
if err != nil {
146+
return "", fmt.Errorf("git clone error: %w", err)
147+
}
148+
149+
repoTags, err := repo.Tags()
150+
if err != nil {
151+
return "", fmt.Errorf("git list tags error: %w", err)
152+
}
153+
154+
tags := make(map[string]string)
155+
_ = repoTags.ForEach(func(t *plumbing.Reference) error {
156+
tags[t.Name().Short()] = t.Strings()[1]
157+
return nil
158+
})
159+
160+
svTags := make(map[string]string)
161+
var svers []semver.Version
162+
for tag, _ := range tags {
163+
v, _ := semver.ParseTolerant(tag)
164+
if rng(v) {
165+
svers = append(svers, v)
166+
svTags[v.String()] = tag
167+
}
168+
}
169+
170+
if len(svers) == 0 {
171+
return "", fmt.Errorf("no match found for semver: %s", c.semver)
172+
}
173+
174+
semver.Sort(svers)
175+
v := svers[len(svers)-1]
176+
t := svTags[v.String()]
177+
commit := tags[t]
178+
179+
w, err := repo.Worktree()
180+
if err != nil {
181+
return "", fmt.Errorf("git worktree error: %w", err)
182+
}
183+
184+
err = w.Checkout(&git.CheckoutOptions{
185+
Hash: plumbing.NewHash(commit),
186+
})
187+
if err != nil {
188+
return "", fmt.Errorf("git checkout error: %w", err)
189+
}
190+
191+
return fmt.Sprintf("%s/%s", t, commit), nil
192+
}

0 commit comments

Comments
 (0)