cerrado @ 8f9853c8e26ffbad74e6414cec31104281a3860b

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