···
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"
17
+
type GitRepo struct {
22
+
type TagList struct {
23
+
refs []*TagReference
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
35
+
// infoWrapper wraps the property of a TreeEntry so it can export fs.FileInfo
36
+
// to tar WriteHeader
37
+
type infoWrapper struct {
45
+
func (self *TagList) Len() int {
46
+
return len(self.refs)
49
+
func (self *TagList) Swap(i, j int) {
50
+
self.refs[i], self.refs[j] = self.refs[j], self.refs[i]
53
+
// sorting tags in reverse chronological order
54
+
func (self *TagList) Less(i, j int) bool {
58
+
if self.refs[i].tag != nil {
59
+
dateI = self.refs[i].tag.Tagger.When
61
+
c, err := self.r.CommitObject(self.refs[i].ref.Hash())
65
+
dateI = c.Committer.When
69
+
if self.refs[j].tag != nil {
70
+
dateJ = self.refs[j].tag.Tagger.When
72
+
c, err := self.r.CommitObject(self.refs[j].ref.Hash())
76
+
dateJ = c.Committer.When
80
+
return dateI.After(dateJ)
83
+
func Open(path string, ref string) (*GitRepo, error) {
86
+
g.r, err = git.PlainOpen(path)
88
+
return nil, fmt.Errorf("opening %s: %w", path, err)
92
+
head, err := g.r.Head()
94
+
return nil, fmt.Errorf("getting head of %s: %w", path, err)
98
+
hash, err := g.r.ResolveRevision(plumbing.Revision(ref))
100
+
return nil, fmt.Errorf("resolving rev %s for %s: %w", ref, path, err)
107
+
func (g *GitRepo) Commits() ([]*object.Commit, error) {
108
+
ci, err := g.r.Log(&git.LogOptions{From: g.h})
110
+
return nil, fmt.Errorf("commits from ref: %w", err)
113
+
commits := []*object.Commit{}
114
+
ci.ForEach(func(c *object.Commit) error {
115
+
commits = append(commits, c)
119
+
return commits, nil
122
+
func (g *GitRepo) LastCommit() (*object.Commit, error) {
123
+
c, err := g.r.CommitObject(g.h)
125
+
return nil, fmt.Errorf("last commit: %w", err)
130
+
func (g *GitRepo) FileContent(path string) (string, error) {
131
+
c, err := g.r.CommitObject(g.h)
133
+
return "", fmt.Errorf("commit object: %w", err)
136
+
tree, err := c.Tree()
138
+
return "", fmt.Errorf("file tree: %w", err)
141
+
file, err := tree.File(path)
146
+
isbin, _ := file.IsBinary()
149
+
return file.Contents()
151
+
return "Not displaying binary file", nil
155
+
func (g *GitRepo) Tags() ([]*TagReference, error) {
156
+
iter, err := g.r.Tags()
158
+
return nil, fmt.Errorf("tag objects: %w", err)
161
+
tags := make([]*TagReference, 0)
163
+
if err := iter.ForEach(func(ref *plumbing.Reference) error {
164
+
obj, err := g.r.TagObject(ref.Hash())
167
+
tags = append(tags, &TagReference{
171
+
case plumbing.ErrObjectNotFound:
172
+
tags = append(tags, &TagReference{
183
+
tagList := &TagList{r: g.r, refs: tags}
188
+
func (g *GitRepo) Branches() ([]*plumbing.Reference, error) {
189
+
bi, err := g.r.Branches()
191
+
return nil, fmt.Errorf("branchs: %w", err)
194
+
branches := []*plumbing.Reference{}
196
+
_ = bi.ForEach(func(ref *plumbing.Reference) error {
197
+
branches = append(branches, ref)
201
+
return branches, nil
204
+
func (g *GitRepo) FindMainBranch(branches []string) (string, error) {
206
+
for _, b := range branches {
207
+
_, err := g.r.ResolveRevision(plumbing.Revision(b))
212
+
return "", fmt.Errorf("unable to find main branch")
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)
221
+
c, err := g.r.CommitObject(g.h)
223
+
return fmt.Errorf("commit object: %w", err)
226
+
tree, err := c.Tree()
231
+
walker := object.NewTreeWalker(tree, true, nil)
232
+
defer walker.Close()
234
+
name, entry, err := walker.Next()
235
+
for ; err == nil; name, entry, err = walker.Next() {
236
+
info, err := newInfoWrapper(name, prefix, &entry, tree)
241
+
header, err := tar.FileInfoHeader(info, "")
246
+
err = tw.WriteHeader(header)
252
+
file, err := tree.File(name)
257
+
reader, err := file.Blob.Reader()
262
+
_, err = io.Copy(tw, reader)
274
+
func newInfoWrapper(
277
+
entry *object.TreeEntry,
279
+
) (*infoWrapper, error) {
286
+
if entry.Mode.IsFile() {
287
+
file, err := tree.TreeEntryFile(entry)
291
+
mode = fs.FileMode(file.Mode)
293
+
size, err = tree.Size(name)
299
+
mode = fs.ModeDir | fs.ModePerm
302
+
fullname := path.Join(prefix, name)
303
+
return &infoWrapper{
307
+
modTime: time.Unix(0, 0),
312
+
func (i *infoWrapper) Name() string {
316
+
func (i *infoWrapper) Size() int64 {
320
+
func (i *infoWrapper) Mode() fs.FileMode {
324
+
func (i *infoWrapper) ModTime() time.Time {
328
+
func (i *infoWrapper) IsDir() bool {
332
+
func (i *infoWrapper) Sys() any {
336
+
func (t *TagReference) Name() string {
337
+
return t.ref.Name().Short()
340
+
func (t *TagReference) Message() string {
342
+
return t.tag.Message