cerrado @ 6b96b76d66a929a2b428505809fda23a19005c63

  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			file, err := tree.File(name)
259			if err != nil {
260				return err
261			}
262
263			reader, err := file.Blob.Reader()
264			if err != nil {
265				return err
266			}
267
268			_, err = io.Copy(tw, reader)
269			if err != nil {
270				reader.Close()
271				return err
272			}
273			reader.Close()
274		}
275	}
276
277	return nil
278}
279
280func newInfoWrapper(
281	filename string,
282	prefix string,
283	entry *object.TreeEntry,
284	tree *object.Tree,
285) (*infoWrapper, error) {
286	var (
287		size  int64
288		mode  fs.FileMode
289		isDir bool
290	)
291
292	if entry.Mode.IsFile() {
293		file, err := tree.TreeEntryFile(entry)
294		if err != nil {
295			return nil, err
296		}
297		mode = fs.FileMode(file.Mode)
298
299		size, err = tree.Size(filename)
300		if err != nil {
301			return nil, err
302		}
303	} else {
304		isDir = true
305		mode = fs.ModeDir | fs.ModePerm
306	}
307
308	fullname := path.Join(prefix, filename)
309	return &infoWrapper{
310		name:    fullname,
311		size:    size,
312		mode:    mode,
313		modTime: time.Unix(0, 0),
314		isDir:   isDir,
315	}, nil
316}
317
318func (i *infoWrapper) Name() string {
319	return i.name
320}
321
322func (i *infoWrapper) Size() int64 {
323	return i.size
324}
325
326func (i *infoWrapper) Mode() fs.FileMode {
327	return i.mode
328}
329
330func (i *infoWrapper) ModTime() time.Time {
331	return i.modTime
332}
333
334func (i *infoWrapper) IsDir() bool {
335	return i.isDir
336}
337
338func (i *infoWrapper) Sys() any {
339	return nil
340}