cerrado @ 27400b0fce5d4ef3b7fd5ef4d25bac8f00754e33

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