all repos — legit @ d6fa0d66c9f6a6c41030f28872132f4290671c48

Unnamed repository; edit this file 'description' to name the repository.

git/git.go (view raw)

  1package git
  2
  3import (
  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
 17type GitRepo struct {
 18	r *git.Repository
 19	h plumbing.Hash
 20}
 21
 22type 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.
 30type TagReference struct {
 31	ref *plumbing.Reference
 32	tag *object.Tag
 33}
 34
 35// CommitReference aggregate all the references for a given commit
 36type CommitReference struct {
 37	commit *object.Commit
 38	refs   []*plumbing.Reference
 39}
 40
 41// infoWrapper wraps the property of a TreeEntry so it can export fs.FileInfo
 42// to tar WriteHeader
 43type infoWrapper struct {
 44	name    string
 45	size    int64
 46	mode    fs.FileMode
 47	modTime time.Time
 48	isDir   bool
 49}
 50
 51func (self *TagList) Len() int {
 52	return len(self.refs)
 53}
 54
 55func (self *TagList) Swap(i, j int) {
 56	self.refs[i], self.refs[j] = self.refs[j], self.refs[i]
 57}
 58
 59// sorting tags in reverse chronological order
 60func (self *TagList) Less(i, j int) bool {
 61	var dateI time.Time
 62	var dateJ time.Time
 63
 64	if self.refs[i].tag != nil {
 65		dateI = self.refs[i].tag.Tagger.When
 66	} else {
 67		c, err := self.r.CommitObject(self.refs[i].ref.Hash())
 68		if err != nil {
 69			dateI = time.Now()
 70		} else {
 71			dateI = c.Committer.When
 72		}
 73	}
 74
 75	if self.refs[j].tag != nil {
 76		dateJ = self.refs[j].tag.Tagger.When
 77	} else {
 78		c, err := self.r.CommitObject(self.refs[j].ref.Hash())
 79		if err != nil {
 80			dateJ = time.Now()
 81		} else {
 82			dateJ = c.Committer.When
 83		}
 84	}
 85
 86	return dateI.After(dateJ)
 87}
 88
 89func Open(path string, ref string) (*GitRepo, error) {
 90	var err error
 91	g := GitRepo{}
 92	g.r, err = git.PlainOpen(path)
 93	if err != nil {
 94		return nil, fmt.Errorf("opening %s: %w", path, err)
 95	}
 96
 97	if ref == "" {
 98		head, err := g.r.Head()
 99		if err != nil {
100			return nil, fmt.Errorf("getting head of %s: %w", path, err)
101		}
102		g.h = head.Hash()
103	} else {
104		hash, err := g.r.ResolveRevision(plumbing.Revision(ref))
105		if err != nil {
106			return nil, fmt.Errorf("resolving rev %s for %s: %w", ref, path, err)
107		}
108		g.h = *hash
109	}
110	return &g, nil
111}
112
113func (g *GitRepo) Commits() ([]*CommitReference, error) {
114	ci, err := g.r.Log(&git.LogOptions{From: g.h})
115	if err != nil {
116		return nil, fmt.Errorf("commits from ref: %w", err)
117	}
118
119	commitRefs := []*CommitReference{}
120	ci.ForEach(func(c *object.Commit) error {
121		commitRefs = append(commitRefs, &CommitReference{commit: c})
122		return nil
123	})
124
125	// new we fetch for possible tags for each commit
126	iter, err := g.r.References()
127	if err != nil {
128		return nil, err
129	}
130
131	if err := iter.ForEach(func(ref *plumbing.Reference) error {
132		for _, c := range commitRefs {
133			obj, err := g.r.TagObject(ref.Hash())
134			switch err {
135			case nil:
136				if obj.Target == c.commit.Hash {
137					c.AddReference(ref)
138				}
139			case plumbing.ErrObjectNotFound:
140				if c.commit.Hash == ref.Hash() {
141					c.AddReference(ref)
142				}
143			default:
144				return err
145			}
146		}
147		return nil
148	}); err != nil {
149		return nil, err
150	}
151
152	return commitRefs, nil
153}
154
155func (g *GitRepo) LastCommit() (*CommitReference, error) {
156	c, err := g.r.CommitObject(g.h)
157	if err != nil {
158		return nil, fmt.Errorf("last commit: %w", err)
159	}
160
161	iter, err := g.r.Tags()
162	if err != nil {
163		return nil, err
164	}
165
166	commitRef := &CommitReference{commit: c}
167	if err := iter.ForEach(func(ref *plumbing.Reference) error {
168		obj, err := g.r.TagObject(ref.Hash())
169		switch err {
170		case nil:
171			if obj.Target == commitRef.commit.Hash {
172				commitRef.AddReference(ref)
173			}
174		case plumbing.ErrObjectNotFound:
175			if commitRef.commit.Hash == ref.Hash() {
176				commitRef.AddReference(ref)
177			}
178		default:
179			return err
180		}
181
182		return nil
183	}); err != nil {
184		return nil, err
185	}
186
187	return commitRef, nil
188}
189
190func (g *GitRepo) FileContent(path string) (string, error) {
191	c, err := g.r.CommitObject(g.h)
192	if err != nil {
193		return "", fmt.Errorf("commit object: %w", err)
194	}
195
196	tree, err := c.Tree()
197	if err != nil {
198		return "", fmt.Errorf("file tree: %w", err)
199	}
200
201	file, err := tree.File(path)
202	if err != nil {
203		return "", err
204	}
205
206	isbin, _ := file.IsBinary()
207
208	if !isbin {
209		return file.Contents()
210	} else {
211		return "Not displaying binary file", nil
212	}
213}
214
215func (g *GitRepo) Tags() ([]*TagReference, error) {
216	iter, err := g.r.Tags()
217	if err != nil {
218		return nil, fmt.Errorf("tag objects: %w", err)
219	}
220
221	tags := make([]*TagReference, 0)
222
223	if err := iter.ForEach(func(ref *plumbing.Reference) error {
224		obj, err := g.r.TagObject(ref.Hash())
225		switch err {
226		case nil:
227			tags = append(tags, &TagReference{
228				ref: ref,
229				tag: obj,
230			})
231		case plumbing.ErrObjectNotFound:
232			tags = append(tags, &TagReference{
233				ref: ref,
234			})
235		default:
236			return err
237		}
238		return nil
239	}); err != nil {
240		return nil, err
241	}
242
243	tagList := &TagList{r: g.r, refs: tags}
244	sort.Sort(tagList)
245	return tags, nil
246}
247
248func (g *GitRepo) Branches() ([]*plumbing.Reference, error) {
249	bi, err := g.r.Branches()
250	if err != nil {
251		return nil, fmt.Errorf("branchs: %w", err)
252	}
253
254	branches := []*plumbing.Reference{}
255
256	_ = bi.ForEach(func(ref *plumbing.Reference) error {
257		branches = append(branches, ref)
258		return nil
259	})
260
261	return branches, nil
262}
263
264func (g *GitRepo) FindMainBranch(branches []string) (string, error) {
265	for _, b := range branches {
266		_, err := g.r.ResolveRevision(plumbing.Revision(b))
267		if err == nil {
268			return b, nil
269		}
270	}
271	return "", fmt.Errorf("unable to find main branch")
272}
273
274// WriteTar writes itself from a tree into a binary tar file format.
275// prefix is root folder to be appended.
276func (g *GitRepo) WriteTar(w io.Writer, prefix string) error {
277	tw := tar.NewWriter(w)
278	defer tw.Close()
279
280	c, err := g.r.CommitObject(g.h)
281	if err != nil {
282		return fmt.Errorf("commit object: %w", err)
283	}
284
285	tree, err := c.Tree()
286	if err != nil {
287		return err
288	}
289
290	walker := object.NewTreeWalker(tree, true, nil)
291	defer walker.Close()
292
293	name, entry, err := walker.Next()
294	for ; err == nil; name, entry, err = walker.Next() {
295		info, err := newInfoWrapper(name, prefix, &entry, tree)
296		if err != nil {
297			return err
298		}
299
300		header, err := tar.FileInfoHeader(info, "")
301		if err != nil {
302			return err
303		}
304
305		err = tw.WriteHeader(header)
306		if err != nil {
307			return err
308		}
309
310		if !info.IsDir() {
311			file, err := tree.File(name)
312			if err != nil {
313				return err
314			}
315
316			reader, err := file.Blob.Reader()
317			if err != nil {
318				return err
319			}
320
321			_, err = io.Copy(tw, reader)
322			if err != nil {
323				reader.Close()
324				return err
325			}
326			reader.Close()
327		}
328	}
329
330	return nil
331}
332
333func newInfoWrapper(
334	name string,
335	prefix string,
336	entry *object.TreeEntry,
337	tree *object.Tree,
338) (*infoWrapper, error) {
339	var (
340		size  int64
341		mode  fs.FileMode
342		isDir bool
343	)
344
345	if entry.Mode.IsFile() {
346		file, err := tree.TreeEntryFile(entry)
347		if err != nil {
348			return nil, err
349		}
350		mode = fs.FileMode(file.Mode)
351
352		size, err = tree.Size(name)
353		if err != nil {
354			return nil, err
355		}
356	} else {
357		isDir = true
358		mode = fs.ModeDir | fs.ModePerm
359	}
360
361	fullname := path.Join(prefix, name)
362	return &infoWrapper{
363		name:    fullname,
364		size:    size,
365		mode:    mode,
366		modTime: time.Unix(0, 0),
367		isDir:   isDir,
368	}, nil
369}
370
371func (i *infoWrapper) Name() string {
372	return i.name
373}
374
375func (i *infoWrapper) Size() int64 {
376	return i.size
377}
378
379func (i *infoWrapper) Mode() fs.FileMode {
380	return i.mode
381}
382
383func (i *infoWrapper) ModTime() time.Time {
384	return i.modTime
385}
386
387func (i *infoWrapper) IsDir() bool {
388	return i.isDir
389}
390
391func (i *infoWrapper) Sys() any {
392	return nil
393}
394
395func (t *TagReference) Name() string {
396	return t.ref.Name().Short()
397}
398
399func (t *TagReference) Message() string {
400	if t.tag != nil {
401		return t.tag.Message
402	}
403	return ""
404}
405
406func (c *CommitReference) Commit() *object.Commit {
407	return c.commit
408}
409
410func (c *CommitReference) HasReference() bool {
411	return len(c.refs) > 0
412}
413
414func (c *CommitReference) References() []*plumbing.Reference {
415	return c.refs
416}
417
418func (c *CommitReference) AddReference(ref *plumbing.Reference) {
419	c.refs = append(c.refs, ref)
420}