cerrado @ e1664fcbc4685906d3dabc66bf947a17bce7efc0

  1package git
  2
  3import (
  4	"archive/tar"
  5	"errors"
  6	"fmt"
  7	"io"
  8	"io/fs"
  9	"path"
 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
 17var ()
 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
 29		ref plumbing.Hash
 30		// this is setRef when ref is setRef
 31		setRef bool
 32	}
 33	infoWrapper struct {
 34		name    string
 35		size    int64
 36		mode    fs.FileMode
 37		modTime time.Time
 38		isDir   bool
 39	}
 40)
 41
 42func OpenRepository(dir string) (*GitRepository, error) {
 43	g := &GitRepository{
 44		path: dir,
 45	}
 46
 47	repo, err := git.PlainOpen(dir)
 48	if err != nil {
 49		return nil, err
 50	}
 51	g.repository = repo
 52
 53	return g, nil
 54}
 55
 56func (g *GitRepository) SetRef(ref string) error {
 57	if ref == "" {
 58		head, err := g.repository.Head()
 59		if err != nil {
 60			return errors.Join(MissingRefErr, err)
 61		}
 62		g.ref = head.Hash()
 63	} else {
 64		hash, err := g.repository.ResolveRevision(plumbing.Revision(ref))
 65		if err != nil {
 66			return errors.Join(MissingRefErr, err)
 67		}
 68		g.ref = *hash
 69	}
 70	g.setRef = true
 71	return nil
 72}
 73
 74func (g *GitRepository) Path() string {
 75	return g.path
 76}
 77
 78func (g *GitRepository) LastCommit() (*object.Commit, error) {
 79	err := g.validateRef()
 80	if err != nil {
 81		return nil, err
 82	}
 83
 84	c, err := g.repository.CommitObject(g.ref)
 85	if err != nil {
 86		return nil, err
 87	}
 88	return c, nil
 89}
 90
 91func (g *GitRepository) Commits(count int) ([]*object.Commit, error) {
 92	err := g.validateRef()
 93	if err != nil {
 94		return nil, err
 95	}
 96
 97	ci, err := g.repository.Log(&git.LogOptions{From: g.ref})
 98	if err != nil {
 99		return nil, fmt.Errorf("commits from ref: %w", err)
100	}
101
102	commits := []*object.Commit{}
103	// TODO: for now only load first 1000
104	for x := 0; x < count; x++ {
105		c, err := ci.Next()
106		if err != nil && errors.Is(err, io.EOF) {
107			break
108		} else if err != nil {
109			return nil, err
110		}
111		commits = append(commits, c)
112	}
113
114	return commits, nil
115}
116
117func (g *GitRepository) Head() (*plumbing.Reference, error) {
118	return g.repository.Head()
119}
120
121func (g *GitRepository) Tags() ([]*plumbing.Reference, error) {
122	ti, err := g.repository.Tags()
123	if err != nil {
124		return nil, err
125	}
126
127	tags := []*plumbing.Reference{}
128	err = ti.ForEach(func(t *plumbing.Reference) error {
129		tags = append(tags, t)
130		return nil
131	})
132	if err != nil {
133		return nil, err
134	}
135
136	return tags, nil
137}
138
139func (g *GitRepository) Branches() ([]*plumbing.Reference, error) {
140	bs, err := g.repository.Branches()
141	if err != nil {
142		return nil, err
143	}
144
145	branches := []*plumbing.Reference{}
146	err = bs.ForEach(func(ref *plumbing.Reference) error {
147		branches = append(branches, ref)
148		return nil
149	})
150	if err != nil {
151		return nil, err
152	}
153
154	return branches, nil
155}
156
157func (g *GitRepository) Tree(path string) (*object.Tree, error) {
158	err := g.validateRef()
159	if err != nil {
160		return nil, err
161	}
162
163	c, err := g.repository.CommitObject(g.ref)
164	if err != nil {
165		return nil, err
166	}
167
168	tree, err := c.Tree()
169	if err != nil {
170		return nil, err
171	}
172
173	if path == "" {
174		return tree, nil
175	} else {
176		o, err := tree.FindEntry(path)
177		if err != nil {
178			return nil, err
179		}
180
181		if !o.Mode.IsFile() {
182			subtree, err := tree.Tree(path)
183			if err != nil {
184				return nil, err
185			}
186			return subtree, nil
187		} else {
188			return nil, TreeForFileErr
189		}
190	}
191}
192
193func (g *GitRepository) validateRef() error {
194	if !g.setRef {
195		return g.SetRef("")
196	}
197	return nil
198}
199
200func (g *GitRepository) FileContent(path string) (string, error) {
201	c, err := g.repository.CommitObject(g.ref)
202	if err != nil {
203		return "", err
204	}
205
206	tree, err := c.Tree()
207	if err != nil {
208		return "", err
209	}
210
211	file, err := tree.File(path)
212	if err != nil {
213		return "", err
214	}
215
216	isbin, err := file.IsBinary()
217	if err != nil {
218		return "", err
219	}
220
221	if !isbin {
222		return file.Contents()
223	} else {
224		return "Binary file", nil
225	}
226}
227
228func (g *GitRepository) WriteTar(w io.Writer, prefix string) error {
229	tw := tar.NewWriter(w)
230	defer tw.Close()
231
232	tree, err := g.Tree("")
233	if err != nil {
234		return err
235	}
236
237	walker := object.NewTreeWalker(tree, true, nil)
238	defer walker.Close()
239
240	name, entry, err := walker.Next()
241	for ; err == nil; name, entry, err = walker.Next() {
242		info, err := newInfoWrapper(name, prefix, &entry, tree)
243		if err != nil {
244			return err
245		}
246
247		header, err := tar.FileInfoHeader(info, "")
248		if err != nil {
249			return err
250		}
251
252		err = tw.WriteHeader(header)
253		if err != nil {
254			return err
255		}
256
257		if !info.IsDir() {
258			c, err := g.FileContent(name)
259			if err != nil {
260				return err
261			}
262
263			_, err = tw.Write([]byte(c))
264			if err != nil {
265				return err
266			}
267		}
268	}
269
270	return nil
271}
272
273func newInfoWrapper(
274	filename string,
275	prefix string,
276	entry *object.TreeEntry,
277	tree *object.Tree,
278) (*infoWrapper, error) {
279	var (
280		size  int64
281		mode  fs.FileMode
282		isDir bool
283	)
284
285	if entry.Mode.IsFile() {
286		file, err := tree.TreeEntryFile(entry)
287		if err != nil {
288			return nil, err
289		}
290		mode = fs.FileMode(file.Mode)
291
292		size, err = tree.Size(filename)
293		if err != nil {
294			return nil, err
295		}
296	} else {
297		isDir = true
298		mode = fs.ModeDir | fs.ModePerm
299	}
300
301	fullname := path.Join(prefix, filename)
302	return &infoWrapper{
303		name:    fullname,
304		size:    size,
305		mode:    mode,
306		modTime: time.Unix(0, 0),
307		isDir:   isDir,
308	}, nil
309}
310
311func (i *infoWrapper) Name() string {
312	return i.name
313}
314
315func (i *infoWrapper) Size() int64 {
316	return i.size
317}
318
319func (i *infoWrapper) Mode() fs.FileMode {
320	return i.mode
321}
322
323func (i *infoWrapper) ModTime() time.Time {
324	return i.modTime
325}
326
327func (i *infoWrapper) IsDir() bool {
328	return i.isDir
329}
330
331func (i *infoWrapper) Sys() any {
332	return nil
333}