all repos — legit @ acac8d47d0dd4bab02274f750d22937044bee988

web frontend for git, written in go

git/git.go (view raw)

  1package git
  2
  3import (
  4	"archive/tar"
  5	"fmt"
  6	"io"
  7	"io/fs"
  8	"path"
  9	"sort"
 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
 17type GitRepo struct {
 18	r *git.Repository
 19	h plumbing.Hash
 20}
 21
 22type TagList []*object.Tag
 23
 24// infoWrapper wraps the property of a TreeEntry so it can export fs.FileInfo
 25// to tar WriteHeader
 26type infoWrapper struct {
 27	name    string
 28	size    int64
 29	mode    fs.FileMode
 30	modTime time.Time
 31	isDir   bool
 32}
 33
 34func (self TagList) Len() int {
 35	return len(self)
 36}
 37
 38func (self TagList) Swap(i, j int) {
 39	self[i], self[j] = self[j], self[i]
 40}
 41
 42// sorting tags in reverse chronological order
 43func (self TagList) Less(i, j int) bool {
 44	return self[i].Tagger.When.After(self[j].Tagger.When)
 45}
 46
 47func Open(path string, ref string) (*GitRepo, error) {
 48	var err error
 49	g := GitRepo{}
 50	g.r, err = git.PlainOpen(path)
 51	if err != nil {
 52		return nil, fmt.Errorf("opening %s: %w", path, err)
 53	}
 54
 55	if ref == "" {
 56		head, err := g.r.Head()
 57		if err != nil {
 58			return nil, fmt.Errorf("getting head of %s: %w", path, err)
 59		}
 60		g.h = head.Hash()
 61	} else {
 62		hash, err := g.r.ResolveRevision(plumbing.Revision(ref))
 63		if err != nil {
 64			return nil, fmt.Errorf("resolving rev %s for %s: %w", ref, path, err)
 65		}
 66		g.h = *hash
 67	}
 68	return &g, nil
 69}
 70
 71func (g *GitRepo) Commits() ([]*object.Commit, error) {
 72	ci, err := g.r.Log(&git.LogOptions{From: g.h})
 73	if err != nil {
 74		return nil, fmt.Errorf("commits from ref: %w", err)
 75	}
 76
 77	commits := []*object.Commit{}
 78	ci.ForEach(func(c *object.Commit) error {
 79		commits = append(commits, c)
 80		return nil
 81	})
 82
 83	return commits, nil
 84}
 85
 86func (g *GitRepo) LastCommit() (*object.Commit, error) {
 87	c, err := g.r.CommitObject(g.h)
 88	if err != nil {
 89		return nil, fmt.Errorf("last commit: %w", err)
 90	}
 91	return c, nil
 92}
 93
 94func (g *GitRepo) FileContent(path string) (string, error) {
 95	c, err := g.r.CommitObject(g.h)
 96	if err != nil {
 97		return "", fmt.Errorf("commit object: %w", err)
 98	}
 99
100	tree, err := c.Tree()
101	if err != nil {
102		return "", fmt.Errorf("file tree: %w", err)
103	}
104
105	file, err := tree.File(path)
106	if err != nil {
107		return "", err
108	}
109
110	isbin, _ := file.IsBinary()
111
112	if !isbin {
113		return file.Contents()
114	} else {
115		return "Not displaying binary file", nil
116	}
117}
118
119func (g *GitRepo) Tags() ([]*object.Tag, error) {
120	ti, err := g.r.TagObjects()
121	if err != nil {
122		return nil, fmt.Errorf("tag objects: %w", err)
123	}
124
125	tags := []*object.Tag{}
126
127	_ = ti.ForEach(func(t *object.Tag) error {
128		for i, existing := range tags {
129			if existing.Name == t.Name {
130				if t.Tagger.When.After(existing.Tagger.When) {
131					tags[i] = t
132				}
133				return nil
134			}
135		}
136		tags = append(tags, t)
137		return nil
138	})
139
140	var tagList TagList
141	tagList = tags
142	sort.Sort(tagList)
143
144	return tags, nil
145}
146
147func (g *GitRepo) Branches() ([]*plumbing.Reference, error) {
148	bi, err := g.r.Branches()
149	if err != nil {
150		return nil, fmt.Errorf("branchs: %w", err)
151	}
152
153	branches := []*plumbing.Reference{}
154
155	_ = bi.ForEach(func(ref *plumbing.Reference) error {
156		branches = append(branches, ref)
157		return nil
158	})
159
160	return branches, nil
161}
162
163func (g *GitRepo) FindMainBranch(branches []string) (string, error) {
164	for _, b := range branches {
165		_, err := g.r.ResolveRevision(plumbing.Revision(b))
166		if err == nil {
167			return b, nil
168		}
169	}
170	return "", fmt.Errorf("unable to find main branch")
171}
172
173// WriteTar writes itself from a tree into a binary tar file format.
174// prefix is root folder to be appended.
175func (g *GitRepo) WriteTar(w io.Writer, prefix string) error {
176	tw := tar.NewWriter(w)
177	defer tw.Close()
178
179	c, err := g.r.CommitObject(g.h)
180	if err != nil {
181		return fmt.Errorf("commit object: %w", err)
182	}
183
184	tree, err := c.Tree()
185	if err != nil {
186		return err
187	}
188
189	walker := object.NewTreeWalker(tree, true, nil)
190	defer walker.Close()
191
192	name, entry, err := walker.Next()
193	for ; err == nil; name, entry, err = walker.Next() {
194		info, err := newInfoWrapper(name, prefix, &entry, tree)
195		if err != nil {
196			return err
197		}
198
199		header, err := tar.FileInfoHeader(info, "")
200		if err != nil {
201			return err
202		}
203
204		err = tw.WriteHeader(header)
205		if err != nil {
206			return err
207		}
208
209		if !info.IsDir() {
210			file, err := tree.File(name)
211			if err != nil {
212				return err
213			}
214
215			reader, err := file.Blob.Reader()
216			if err != nil {
217				return err
218			}
219
220			_, err = io.Copy(tw, reader)
221			if err != nil {
222				reader.Close()
223				return err
224			}
225			reader.Close()
226		}
227	}
228
229	return nil
230}
231
232func newInfoWrapper(
233	name string,
234	prefix string,
235	entry *object.TreeEntry,
236	tree *object.Tree,
237) (*infoWrapper, error) {
238	var (
239		size  int64
240		mode  fs.FileMode
241		isDir bool
242	)
243
244	if entry.Mode.IsFile() {
245		file, err := tree.TreeEntryFile(entry)
246		if err != nil {
247			return nil, err
248		}
249		mode = fs.FileMode(file.Mode)
250
251		size, err = tree.Size(name)
252		if err != nil {
253			return nil, err
254		}
255	} else {
256		isDir = true
257		mode = fs.ModeDir | fs.ModePerm
258	}
259
260	fullname := path.Join(prefix, name)
261	return &infoWrapper{
262		name:    fullname,
263		size:    size,
264		mode:    mode,
265		modTime: time.Unix(0, 0),
266		isDir:   isDir,
267	}, nil
268}
269
270func (i *infoWrapper) Name() string {
271	return i.name
272}
273
274func (i *infoWrapper) Size() int64 {
275	return i.size
276}
277
278func (i *infoWrapper) Mode() fs.FileMode {
279	return i.mode
280}
281
282func (i *infoWrapper) ModTime() time.Time {
283	return i.modTime
284}
285
286func (i *infoWrapper) IsDir() bool {
287	return i.isDir
288}
289
290func (i *infoWrapper) Sys() any {
291	return nil
292}