-345
git/git.go
-345
git/git.go
···
1
-
package git
2
-
3
-
import (
4
-
"archive/tar"
5
-
"fmt"
6
-
"io"
7
-
"io/fs"
8
-
"path"
9
-
"sort"
10
-
"time"
11
-
12
-
"github.com/go-git/go-git/v5"
13
-
"github.com/go-git/go-git/v5/plumbing"
14
-
"github.com/go-git/go-git/v5/plumbing/object"
15
-
)
16
-
17
-
type GitRepo struct {
18
-
r *git.Repository
19
-
h plumbing.Hash
20
-
}
21
-
22
-
type TagList struct {
23
-
refs []*TagReference
24
-
r *git.Repository
25
-
}
26
-
27
-
// TagReference is used to list both tag and non-annotated tags.
28
-
// Non-annotated tags should only contains a reference.
29
-
// Annotated tags should contain its reference and its tag information.
30
-
type TagReference struct {
31
-
ref *plumbing.Reference
32
-
tag *object.Tag
33
-
}
34
-
35
-
// infoWrapper wraps the property of a TreeEntry so it can export fs.FileInfo
36
-
// to tar WriteHeader
37
-
type infoWrapper struct {
38
-
name string
39
-
size int64
40
-
mode fs.FileMode
41
-
modTime time.Time
42
-
isDir bool
43
-
}
44
-
45
-
func (self *TagList) Len() int {
46
-
return len(self.refs)
47
-
}
48
-
49
-
func (self *TagList) Swap(i, j int) {
50
-
self.refs[i], self.refs[j] = self.refs[j], self.refs[i]
51
-
}
52
-
53
-
// sorting tags in reverse chronological order
54
-
func (self *TagList) Less(i, j int) bool {
55
-
var dateI time.Time
56
-
var dateJ time.Time
57
-
58
-
if self.refs[i].tag != nil {
59
-
dateI = self.refs[i].tag.Tagger.When
60
-
} else {
61
-
c, err := self.r.CommitObject(self.refs[i].ref.Hash())
62
-
if err != nil {
63
-
dateI = time.Now()
64
-
} else {
65
-
dateI = c.Committer.When
66
-
}
67
-
}
68
-
69
-
if self.refs[j].tag != nil {
70
-
dateJ = self.refs[j].tag.Tagger.When
71
-
} else {
72
-
c, err := self.r.CommitObject(self.refs[j].ref.Hash())
73
-
if err != nil {
74
-
dateJ = time.Now()
75
-
} else {
76
-
dateJ = c.Committer.When
77
-
}
78
-
}
79
-
80
-
return dateI.After(dateJ)
81
-
}
82
-
83
-
func Open(path string, ref string) (*GitRepo, error) {
84
-
var err error
85
-
g := GitRepo{}
86
-
g.r, err = git.PlainOpen(path)
87
-
if err != nil {
88
-
return nil, fmt.Errorf("opening %s: %w", path, err)
89
-
}
90
-
91
-
if ref == "" {
92
-
head, err := g.r.Head()
93
-
if err != nil {
94
-
return nil, fmt.Errorf("getting head of %s: %w", path, err)
95
-
}
96
-
g.h = head.Hash()
97
-
} else {
98
-
hash, err := g.r.ResolveRevision(plumbing.Revision(ref))
99
-
if err != nil {
100
-
return nil, fmt.Errorf("resolving rev %s for %s: %w", ref, path, err)
101
-
}
102
-
g.h = *hash
103
-
}
104
-
return &g, nil
105
-
}
106
-
107
-
func (g *GitRepo) Commits() ([]*object.Commit, error) {
108
-
ci, err := g.r.Log(&git.LogOptions{From: g.h})
109
-
if err != nil {
110
-
return nil, fmt.Errorf("commits from ref: %w", err)
111
-
}
112
-
113
-
commits := []*object.Commit{}
114
-
ci.ForEach(func(c *object.Commit) error {
115
-
commits = append(commits, c)
116
-
return nil
117
-
})
118
-
119
-
return commits, nil
120
-
}
121
-
122
-
func (g *GitRepo) LastCommit() (*object.Commit, error) {
123
-
c, err := g.r.CommitObject(g.h)
124
-
if err != nil {
125
-
return nil, fmt.Errorf("last commit: %w", err)
126
-
}
127
-
return c, nil
128
-
}
129
-
130
-
func (g *GitRepo) FileContent(path string) (string, error) {
131
-
c, err := g.r.CommitObject(g.h)
132
-
if err != nil {
133
-
return "", fmt.Errorf("commit object: %w", err)
134
-
}
135
-
136
-
tree, err := c.Tree()
137
-
if err != nil {
138
-
return "", fmt.Errorf("file tree: %w", err)
139
-
}
140
-
141
-
file, err := tree.File(path)
142
-
if err != nil {
143
-
return "", err
144
-
}
145
-
146
-
isbin, _ := file.IsBinary()
147
-
148
-
if !isbin {
149
-
return file.Contents()
150
-
} else {
151
-
return "Not displaying binary file", nil
152
-
}
153
-
}
154
-
155
-
func (g *GitRepo) Tags() ([]*TagReference, error) {
156
-
iter, err := g.r.Tags()
157
-
if err != nil {
158
-
return nil, fmt.Errorf("tag objects: %w", err)
159
-
}
160
-
161
-
tags := make([]*TagReference, 0)
162
-
163
-
if err := iter.ForEach(func(ref *plumbing.Reference) error {
164
-
obj, err := g.r.TagObject(ref.Hash())
165
-
switch err {
166
-
case nil:
167
-
tags = append(tags, &TagReference{
168
-
ref: ref,
169
-
tag: obj,
170
-
})
171
-
case plumbing.ErrObjectNotFound:
172
-
tags = append(tags, &TagReference{
173
-
ref: ref,
174
-
})
175
-
default:
176
-
return err
177
-
}
178
-
return nil
179
-
}); err != nil {
180
-
return nil, err
181
-
}
182
-
183
-
tagList := &TagList{r: g.r, refs: tags}
184
-
sort.Sort(tagList)
185
-
return tags, nil
186
-
}
187
-
188
-
func (g *GitRepo) Branches() ([]*plumbing.Reference, error) {
189
-
bi, err := g.r.Branches()
190
-
if err != nil {
191
-
return nil, fmt.Errorf("branchs: %w", err)
192
-
}
193
-
194
-
branches := []*plumbing.Reference{}
195
-
196
-
_ = bi.ForEach(func(ref *plumbing.Reference) error {
197
-
branches = append(branches, ref)
198
-
return nil
199
-
})
200
-
201
-
return branches, nil
202
-
}
203
-
204
-
func (g *GitRepo) FindMainBranch(branches []string) (string, error) {
205
-
206
-
for _, b := range branches {
207
-
_, err := g.r.ResolveRevision(plumbing.Revision(b))
208
-
if err == nil {
209
-
return b, nil
210
-
}
211
-
}
212
-
return "", fmt.Errorf("unable to find main branch")
213
-
}
214
-
215
-
// WriteTar writes itself from a tree into a binary tar file format.
216
-
// prefix is root folder to be appended.
217
-
func (g *GitRepo) WriteTar(w io.Writer, prefix string) error {
218
-
tw := tar.NewWriter(w)
219
-
defer tw.Close()
220
-
221
-
c, err := g.r.CommitObject(g.h)
222
-
if err != nil {
223
-
return fmt.Errorf("commit object: %w", err)
224
-
}
225
-
226
-
tree, err := c.Tree()
227
-
if err != nil {
228
-
return err
229
-
}
230
-
231
-
walker := object.NewTreeWalker(tree, true, nil)
232
-
defer walker.Close()
233
-
234
-
name, entry, err := walker.Next()
235
-
for ; err == nil; name, entry, err = walker.Next() {
236
-
info, err := newInfoWrapper(name, prefix, &entry, tree)
237
-
if err != nil {
238
-
return err
239
-
}
240
-
241
-
header, err := tar.FileInfoHeader(info, "")
242
-
if err != nil {
243
-
return err
244
-
}
245
-
246
-
err = tw.WriteHeader(header)
247
-
if err != nil {
248
-
return err
249
-
}
250
-
251
-
if !info.IsDir() {
252
-
file, err := tree.File(name)
253
-
if err != nil {
254
-
return err
255
-
}
256
-
257
-
reader, err := file.Blob.Reader()
258
-
if err != nil {
259
-
return err
260
-
}
261
-
262
-
_, err = io.Copy(tw, reader)
263
-
if err != nil {
264
-
reader.Close()
265
-
return err
266
-
}
267
-
reader.Close()
268
-
}
269
-
}
270
-
271
-
return nil
272
-
}
273
-
274
-
func newInfoWrapper(
275
-
name string,
276
-
prefix string,
277
-
entry *object.TreeEntry,
278
-
tree *object.Tree,
279
-
) (*infoWrapper, error) {
280
-
var (
281
-
size int64
282
-
mode fs.FileMode
283
-
isDir bool
284
-
)
285
-
286
-
if entry.Mode.IsFile() {
287
-
file, err := tree.TreeEntryFile(entry)
288
-
if err != nil {
289
-
return nil, err
290
-
}
291
-
mode = fs.FileMode(file.Mode)
292
-
293
-
size, err = tree.Size(name)
294
-
if err != nil {
295
-
return nil, err
296
-
}
297
-
} else {
298
-
isDir = true
299
-
mode = fs.ModeDir | fs.ModePerm
300
-
}
301
-
302
-
fullname := path.Join(prefix, name)
303
-
return &infoWrapper{
304
-
name: fullname,
305
-
size: size,
306
-
mode: mode,
307
-
modTime: time.Unix(0, 0),
308
-
isDir: isDir,
309
-
}, nil
310
-
}
311
-
312
-
func (i *infoWrapper) Name() string {
313
-
return i.name
314
-
}
315
-
316
-
func (i *infoWrapper) Size() int64 {
317
-
return i.size
318
-
}
319
-
320
-
func (i *infoWrapper) Mode() fs.FileMode {
321
-
return i.mode
322
-
}
323
-
324
-
func (i *infoWrapper) ModTime() time.Time {
325
-
return i.modTime
326
-
}
327
-
328
-
func (i *infoWrapper) IsDir() bool {
329
-
return i.isDir
330
-
}
331
-
332
-
func (i *infoWrapper) Sys() any {
333
-
return nil
334
-
}
335
-
336
-
func (t *TagReference) Name() string {
337
-
return t.ref.Name().Short()
338
-
}
339
-
340
-
func (t *TagReference) Message() string {
341
-
if t.tag != nil {
342
-
return t.tag.Message
343
-
}
344
-
return ""
345
-
}
···