-
Notifications
You must be signed in to change notification settings - Fork 18
/
git.go
138 lines (112 loc) · 3.37 KB
/
git.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package git
import (
"context"
"errors"
"fmt"
"github.com/cirruslabs/cirrus-cli/pkg/larker/loader/git/bounded"
"github.com/docker/go-units"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/cache"
"github.com/go-git/go-git/v5/storage/filesystem"
"io/ioutil"
"os"
"regexp"
)
type Locator struct {
URL string
Path string
Revision string
}
var (
ErrRetrievalFailed = errors.New("failed to retrieve a file from Git repository")
ErrFileNotFound = errors.New("file not found in a Git repository")
)
const (
// Captures the path after / in non-greedy manner.
optionalPath = `(?:/(?P<path>.*?))?`
// Captures the revision after @ in non-greedy manner.
optionalRevision = `(?:@(?P<revision>.*))?`
)
var regexVariants = []*regexp.Regexp{
// GitHub
regexp.MustCompile(`^(?P<root>github\.com/.*?/.*?)` + optionalPath + optionalRevision + `$`),
// Other Git hosting services
regexp.MustCompile(`^(?P<root>.*?)\.git` + optionalPath + optionalRevision + `$`),
}
func Parse(module string) *Locator {
result := &Locator{
Path: "lib.star",
Revision: "main",
}
for _, regex := range regexVariants {
matches := regex.FindStringSubmatch(module)
if matches == nil {
continue
}
result.URL = "https://" + matches[regex.SubexpIndex("root")] + ".git"
path := matches[regex.SubexpIndex("path")]
if path != "" {
result.Path = path
}
revision := matches[regex.SubexpIndex("revision")]
if revision != "" {
result.Revision = revision
}
return result
}
return nil
}
func Retrieve(ctx context.Context, locator *Locator) ([]byte, error) {
const (
cacheBytes = 1 * units.MiB
storageBytes = 4 * units.MiB
storageFiles = 512
filesystemBytes = 4 * units.MiB
filesystemFiles = 512
)
boundedCache := cache.NewObjectLRU(cacheBytes)
boundedStorage := filesystem.NewStorage(bounded.NewFilesystem(storageBytes, storageFiles), boundedCache)
boundedFilesystem := bounded.NewFilesystem(filesystemBytes, filesystemFiles)
// Clone the repository
repo, err := git.CloneContext(ctx, boundedStorage, boundedFilesystem, &git.CloneOptions{
URL: locator.URL,
})
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrRetrievalFailed, err)
}
// Checkout the working tree to the specified revision
worktree, err := repo.Worktree()
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrRetrievalFailed, err)
}
// Without this ResolveRevision() would only work for default branch (e.g. master)
if err := repo.Fetch(&git.FetchOptions{
RefSpecs: []config.RefSpec{"refs/*:refs/*"},
}); err != nil {
return nil, fmt.Errorf("%w: %v", ErrRetrievalFailed, err)
}
hash, err := repo.ResolveRevision(plumbing.Revision(locator.Revision))
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrRetrievalFailed, err)
}
if err := worktree.Checkout(&git.CheckoutOptions{
Hash: *hash,
}); err != nil {
return nil, fmt.Errorf("%w: %v", ErrRetrievalFailed, err)
}
// Read the file from the working tree
file, err := worktree.Filesystem.Open(locator.Path)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil, fmt.Errorf("%w: %v", ErrFileNotFound, err)
}
return nil, fmt.Errorf("%w: %v", ErrRetrievalFailed, err)
}
fileBytes, err := ioutil.ReadAll(file)
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrRetrievalFailed, err)
}
return fileBytes, nil
}