cerrado @ f19adedc4b7f71c824a59d4a797bd8872bdb0bbd

  1package git
  2
  3import (
  4	"archive/tar"
  5	"bytes"
  6	"errors"
  7	"fmt"
  8	"io"
  9	"io/fs"
 10	"path"
 11	"sort"
 12	"time"
 13
 14	"github.com/go-git/go-git/v5"
 15	"github.com/go-git/go-git/v5/plumbing"
 16	"github.com/go-git/go-git/v5/plumbing/object"
 17)
 18
 19var (
 20	MissingRefErr  = errors.New("Reference not found")
 21	TreeForFileErr = errors.New("Trying to get tree of a file")
 22	eofIter        = errors.New("End of a iterator")
 23)
 24
 25type (
 26	GitRepository struct {
 27		path       string
 28		repository *git.Repository
 29		ref        plumbing.Hash
 30		setRef     bool
 31	}
 32	TagReference struct {
 33		ref *plumbing.Reference
 34		tag *object.Tag
 35	}
 36	CommitReference struct {
 37		commit *object.Commit
 38		refs   []*plumbing.Reference
 39	}
 40	infoWrapper struct {
 41		name    string
 42		size    int64
 43		mode    fs.FileMode
 44		modTime time.Time
 45		isDir   bool
 46	}
 47	tagList struct {
 48		refs []*TagReference
 49		r    *git.Repository
 50	}
 51)
 52
 53func OpenRepository(dir string) (*GitRepository, error) {
 54	g := &GitRepository{
 55		path: dir,
 56	}
 57
 58	repo, err := git.PlainOpen(dir)
 59	if err != nil {
 60		return nil, err
 61	}
 62	g.repository = repo
 63
 64	return g, nil
 65}
 66
 67func (g *GitRepository) SetRef(ref string) error {
 68	if ref == "" {
 69		head, err := g.repository.Head()
 70		if err != nil {
 71			return errors.Join(MissingRefErr, err)
 72		}
 73		g.ref = head.Hash()
 74	} else {
 75		hash, err := g.repository.ResolveRevision(plumbing.Revision(ref))
 76		if err != nil {
 77			return errors.Join(MissingRefErr, err)
 78		}
 79		g.ref = *hash
 80	}
 81	g.setRef = true
 82	return nil
 83}
 84
 85func (g *GitRepository) Path() string {
 86	return g.path
 87}
 88
 89func (g *GitRepository) LastCommit() (*CommitReference, error) {
 90	err := g.validateRef()
 91	if err != nil {
 92		return nil, err
 93	}
 94
 95	c, err := g.repository.CommitObject(g.ref)
 96	if err != nil {
 97		return nil, err
 98	}
 99
100	iter, err := g.repository.Tags()
101	if err != nil {
102		return nil, err
103	}
104
105	commitRef := &CommitReference{commit: c}
106	if err := iter.ForEach(func(ref *plumbing.Reference) error {
107		obj, err := g.repository.TagObject(ref.Hash())
108		switch err {
109		case nil:
110			if obj.Target == commitRef.commit.Hash {
111				commitRef.AddReference(ref)
112			}
113		case plumbing.ErrObjectNotFound:
114			if commitRef.commit.Hash == ref.Hash() {
115				commitRef.AddReference(ref)
116			}
117		default:
118			return err
119		}
120
121		return nil
122	}); err != nil {
123		return nil, err
124	}
125
126	return commitRef, nil
127}
128
129func (g *GitRepository) Commits(count int, from string) ([]*CommitReference, *object.Commit, error) {
130	err := g.validateRef()
131	if err != nil {
132		return nil, nil, err
133	}
134
135	opts := &git.LogOptions{Order: git.LogOrderCommitterTime}
136
137	if from != "" {
138		hash, err := g.repository.ResolveRevision(plumbing.Revision(from))
139		if err != nil {
140			return nil, nil, errors.Join(MissingRefErr, err)
141		}
142		opts.From = *hash
143	}
144
145	ci, err := g.repository.Log(opts)
146	if err != nil {
147		return nil, nil, fmt.Errorf("commits from ref: %w", err)
148	}
149
150	commitRefs := []*CommitReference{}
151	var next *object.Commit
152
153	// iterate one more item so we can fetch the next commit
154	for x := 0; x < (count + 1); x++ {
155		c, err := ci.Next()
156		if err != nil && errors.Is(err, io.EOF) {
157			break
158		} else if err != nil {
159			return nil, nil, err
160		}
161		if x == count {
162			next = c
163		} else {
164			commitRefs = append(commitRefs, &CommitReference{commit: c})
165		}
166	}
167
168	// new we fetch for possible tags for each commit
169	iter, err := g.repository.References()
170	if err != nil {
171		return nil, nil, err
172	}
173
174	if err := iter.ForEach(func(ref *plumbing.Reference) error {
175		for _, c := range commitRefs {
176			obj, err := g.repository.TagObject(ref.Hash())
177			switch err {
178			case nil:
179				if obj.Target == c.commit.Hash {
180					c.AddReference(ref)
181				}
182			case plumbing.ErrObjectNotFound:
183				if c.commit.Hash == ref.Hash() {
184					c.AddReference(ref)
185				}
186			default:
187				return err
188			}
189		}
190		return nil
191	}); err != nil {
192		return nil, nil, err
193	}
194
195	return commitRefs, next, nil
196}
197
198func (g *GitRepository) Head() (*plumbing.Reference, error) {
199	return g.repository.Head()
200}
201
202func (g *GitRepository) Tag() (*object.Commit, *TagReference, error) {
203	err := g.validateRef()
204	if err != nil {
205		return nil, nil, err
206	}
207
208	c, err := g.repository.CommitObject(g.ref)
209	if err != nil {
210		return nil, nil, err
211	}
212
213	var tagReference *TagReference
214
215	iter, err := g.repository.Tags()
216	if err != nil {
217		return nil, nil, err
218	}
219
220	if err := iter.ForEach(func(ref *plumbing.Reference) error {
221		obj, err := g.repository.TagObject(ref.Hash())
222		switch err {
223		case nil:
224			if obj.Target == c.Hash {
225				tagReference = &TagReference{
226					ref: ref,
227					tag: obj,
228				}
229				return eofIter
230			}
231			return nil
232		case plumbing.ErrObjectNotFound:
233			if c.Hash == ref.Hash() {
234				tagReference = &TagReference{
235					ref: ref,
236				}
237				return eofIter
238			}
239			return nil
240		default:
241			return err
242		}
243	}); err != nil && !errors.Is(eofIter, err) {
244		return nil, nil, err
245	}
246
247	return c, tagReference, nil
248}
249
250func (g *GitRepository) Tags() ([]*TagReference, error) {
251	iter, err := g.repository.Tags()
252	if err != nil {
253		return nil, err
254	}
255
256	tags := make([]*TagReference, 0)
257
258	if err := iter.ForEach(func(ref *plumbing.Reference) error {
259		obj, err := g.repository.TagObject(ref.Hash())
260		switch err {
261		case nil:
262			tags = append(tags, &TagReference{
263				ref: ref,
264				tag: obj,
265			})
266		case plumbing.ErrObjectNotFound:
267			tags = append(tags, &TagReference{
268				ref: ref,
269			})
270		default:
271			return err
272		}
273		return nil
274	}); err != nil {
275		return nil, err
276	}
277
278	// tagList modify the underlying tag list.
279	tagList := &tagList{r: g.repository, refs: tags}
280	sort.Sort(tagList)
281
282	return tags, nil
283}
284
285func (g *GitRepository) Branches() ([]*plumbing.Reference, error) {
286	bs, err := g.repository.Branches()
287	if err != nil {
288		return nil, err
289	}
290
291	branches := []*plumbing.Reference{}
292	err = bs.ForEach(func(ref *plumbing.Reference) error {
293		branches = append(branches, ref)
294		return nil
295	})
296	if err != nil {
297		return nil, err
298	}
299
300	return branches, nil
301}
302
303func (g *GitRepository) Diff() (string, error) {
304	err := g.validateRef()
305	if err != nil {
306		return "", err
307	}
308
309	c, err := g.repository.CommitObject(g.ref)
310	if err != nil {
311		return "", err
312	}
313
314	commitTree, err := c.Tree()
315	if err != nil {
316		return "", err
317	}
318
319	patch := &object.Patch{}
320	parentTree := &object.Tree{}
321	if c.NumParents() != 0 {
322		parent, err := c.Parents().Next()
323		if err == nil {
324			parentTree, err = parent.Tree()
325			if err == nil {
326				patch, err = parentTree.Patch(commitTree)
327				if err != nil {
328					return "", err
329				}
330			}
331		}
332	} else {
333		patch, err = parentTree.Patch(commitTree)
334		if err != nil {
335			return "", err
336		}
337	}
338
339	return patch.String(), nil
340}
341
342func (g *GitRepository) Tree(path string) (*object.Tree, error) {
343	err := g.validateRef()
344	if err != nil {
345		return nil, err
346	}
347
348	c, err := g.repository.CommitObject(g.ref)
349	if err != nil {
350		return nil, err
351	}
352
353	tree, err := c.Tree()
354	if err != nil {
355		return nil, err
356	}
357
358	if path == "" {
359		return tree, nil
360	} else {
361		o, err := tree.FindEntry(path)
362		if err != nil {
363			return nil, err
364		}
365
366		if !o.Mode.IsFile() {
367			subtree, err := tree.Tree(path)
368			if err != nil {
369				return nil, err
370			}
371			return subtree, nil
372		} else {
373			return nil, TreeForFileErr
374		}
375	}
376}
377
378func (g *GitRepository) validateRef() error {
379	if !g.setRef {
380		return g.SetRef("")
381	}
382	return nil
383}
384
385func (g *GitRepository) IsBinary(path string) (bool, error) {
386	tree, err := g.Tree("")
387	if err != nil {
388		return false, err
389	}
390
391	file, err := tree.File(path)
392	if err != nil {
393		return false, err
394	}
395
396	return file.IsBinary()
397}
398
399func (g *GitRepository) FileContent(path string) ([]byte, error) {
400	err := g.validateRef()
401	if err != nil {
402		return nil, err
403	}
404
405	c, err := g.repository.CommitObject(g.ref)
406	if err != nil {
407		return nil, err
408	}
409
410	tree, err := c.Tree()
411	if err != nil {
412		return nil, err
413	}
414
415	file, err := tree.File(path)
416	if err != nil {
417		return nil, err
418	}
419
420	r, err := file.Blob.Reader()
421	if err != nil {
422		return nil, err
423	}
424	defer r.Close()
425
426	var buf bytes.Buffer
427	_, err = io.Copy(&buf, r)
428	if err != nil {
429		return nil, err
430	}
431
432	return buf.Bytes(), nil
433}
434
435func (g *GitRepository) WriteTar(w io.Writer, prefix string) error {
436	tw := tar.NewWriter(w)
437	defer tw.Close()
438
439	tree, err := g.Tree("")
440	if err != nil {
441		return err
442	}
443
444	walker := object.NewTreeWalker(tree, true, nil)
445	defer walker.Close()
446
447	name, entry, err := walker.Next()
448	for ; err == nil; name, entry, err = walker.Next() {
449		info, err := newInfoWrapper(name, prefix, &entry, tree)
450		if err != nil {
451			return err
452		}
453
454		header, err := tar.FileInfoHeader(info, "")
455		if err != nil {
456			return err
457		}
458
459		err = tw.WriteHeader(header)
460		if err != nil {
461			return err
462		}
463
464		if !info.IsDir() {
465			file, err := tree.File(name)
466			if err != nil {
467				return err
468			}
469
470			reader, err := file.Blob.Reader()
471			if err != nil {
472				return err
473			}
474
475			_, err = io.Copy(tw, reader)
476			if err != nil {
477				reader.Close()
478				return err
479			}
480			reader.Close()
481		}
482	}
483
484	return nil
485}
486
487func newInfoWrapper(
488	filename string,
489	prefix string,
490	entry *object.TreeEntry,
491	tree *object.Tree,
492) (*infoWrapper, error) {
493	var (
494		size  int64
495		mode  fs.FileMode
496		isDir bool
497	)
498
499	if entry.Mode.IsFile() {
500		file, err := tree.TreeEntryFile(entry)
501		if err != nil {
502			return nil, err
503		}
504		mode = fs.FileMode(file.Mode)
505
506		size, err = tree.Size(filename)
507		if err != nil {
508			return nil, err
509		}
510	} else {
511		isDir = true
512		mode = fs.ModeDir | fs.ModePerm
513	}
514
515	fullname := path.Join(prefix, filename)
516	return &infoWrapper{
517		name:    fullname,
518		size:    size,
519		mode:    mode,
520		modTime: time.Unix(0, 0),
521		isDir:   isDir,
522	}, nil
523}
524
525func (i *infoWrapper) Name() string {
526	return i.name
527}
528
529func (i *infoWrapper) Size() int64 {
530	return i.size
531}
532
533func (i *infoWrapper) Mode() fs.FileMode {
534	return i.mode
535}
536
537func (i *infoWrapper) ModTime() time.Time {
538	return i.modTime
539}
540
541func (i *infoWrapper) IsDir() bool {
542	return i.isDir
543}
544
545func (i *infoWrapper) Sys() any {
546	return nil
547}
548
549func (t *TagReference) HashString() string {
550	return t.ref.Hash().String()
551}
552
553func (t *TagReference) ShortName() string {
554	return t.ref.Name().Short()
555}
556
557func (t *TagReference) Message() string {
558	if t.tag != nil {
559		return t.tag.Message
560	}
561	return ""
562}
563
564func (c *CommitReference) Commit() *object.Commit {
565	return c.commit
566}
567
568func (c *CommitReference) HasReference() bool {
569	return len(c.refs) > 0
570}
571
572func (c *CommitReference) References() []*plumbing.Reference {
573	return c.refs
574}
575
576func (c *CommitReference) AddReference(ref *plumbing.Reference) {
577	c.refs = append(c.refs, ref)
578}
579
580func (self *tagList) Len() int {
581	return len(self.refs)
582}
583
584func (self *tagList) Swap(i, j int) {
585	self.refs[i], self.refs[j] = self.refs[j], self.refs[i]
586}
587
588func (self *tagList) Less(i, j int) bool {
589	var dateI time.Time
590	var dateJ time.Time
591
592	if self.refs[i].tag != nil {
593		dateI = self.refs[i].tag.Tagger.When
594	} else {
595		c, err := self.r.CommitObject(self.refs[i].ref.Hash())
596		if err != nil {
597			dateI = time.Now()
598		} else {
599			dateI = c.Committer.When
600		}
601	}
602
603	if self.refs[j].tag != nil {
604		dateJ = self.refs[j].tag.Tagger.When
605	} else {
606		c, err := self.r.CommitObject(self.refs[j].ref.Hash())
607		if err != nil {
608			dateJ = time.Now()
609		} else {
610			dateJ = c.Committer.When
611		}
612	}
613
614	return dateI.After(dateJ)
615}