cerrado @ 64d9bcc4e567a0f816a0388bf740baa4c01d2323

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