tree.go

  1package git
  2
  3import (
  4	"bufio"
  5	"bytes"
  6	"io"
  7	"io/fs"
  8	"path/filepath"
  9	"sort"
 10
 11	"github.com/aymanbagabas/git-module"
 12)
 13
 14// Tree is a wrapper around git.Tree with helper methods.
 15type Tree struct {
 16	*git.Tree
 17	Path       string
 18	Repository *Repository
 19}
 20
 21// TreeEntry is a wrapper around git.TreeEntry with helper methods.
 22type TreeEntry struct {
 23	*git.TreeEntry
 24	// path is the full path of the file
 25	path string
 26}
 27
 28// Entries is a wrapper around git.Entries.
 29type Entries []*TreeEntry
 30
 31var sorters = []func(t1, t2 *TreeEntry) bool{
 32	func(t1, t2 *TreeEntry) bool {
 33		return (t1.IsTree() || t1.IsCommit()) && !t2.IsTree() && !t2.IsCommit()
 34	},
 35	func(t1, t2 *TreeEntry) bool {
 36		return t1.Name() < t2.Name()
 37	},
 38}
 39
 40// Len implements sort.Interface.
 41func (es Entries) Len() int { return len(es) }
 42
 43// Swap implements sort.Interface.
 44func (es Entries) Swap(i, j int) { es[i], es[j] = es[j], es[i] }
 45
 46// Less implements sort.Interface.
 47func (es Entries) Less(i, j int) bool {
 48	t1, t2 := es[i], es[j]
 49	var k int
 50	for k = 0; k < len(sorters)-1; k++ {
 51		sorter := sorters[k]
 52		switch {
 53		case sorter(t1, t2):
 54			return true
 55		case sorter(t2, t1):
 56			return false
 57		}
 58	}
 59	return sorters[k](t1, t2)
 60}
 61
 62// Sort sorts the entries in the tree.
 63func (es Entries) Sort() {
 64	sort.Sort(es)
 65}
 66
 67// File is a wrapper around git.Blob with helper methods.
 68type File struct {
 69	*git.Blob
 70	Entry *TreeEntry
 71}
 72
 73// Name returns the name of the file.
 74func (f *File) Name() string {
 75	return f.Entry.Name()
 76}
 77
 78// Path returns the full path of the file.
 79func (f *File) Path() string {
 80	return f.Entry.path
 81}
 82
 83// SubTree returns the sub-tree at the given path.
 84func (t *Tree) SubTree(path string) (*Tree, error) {
 85	tree, err := t.Subtree(path)
 86	if err != nil {
 87		return nil, err //nolint:wrapcheck
 88	}
 89	return &Tree{
 90		Tree:       tree,
 91		Path:       path,
 92		Repository: t.Repository,
 93	}, nil
 94}
 95
 96// Entries returns the entries in the tree.
 97func (t *Tree) Entries() (Entries, error) {
 98	entries, err := t.Tree.Entries()
 99	if err != nil {
100		return nil, err //nolint:wrapcheck
101	}
102	ret := make(Entries, len(entries))
103	for i, e := range entries {
104		ret[i] = &TreeEntry{
105			TreeEntry: e,
106			path:      filepath.Join(t.Path, e.Name()),
107		}
108	}
109	return ret, nil
110}
111
112// TreeEntry returns the TreeEntry for the file path.
113func (t *Tree) TreeEntry(path string) (*TreeEntry, error) {
114	entry, err := t.Tree.TreeEntry(path)
115	if err != nil {
116		return nil, err //nolint:wrapcheck
117	}
118	return &TreeEntry{
119		TreeEntry: entry,
120		path:      filepath.Join(t.Path, entry.Name()),
121	}, nil
122}
123
124const sniffLen = 8000
125
126// IsBinary detects if data is a binary value based on:
127// http://git.kernel.org/cgit/git/git.git/tree/xdiff-interface.c?id=HEAD#n198
128func IsBinary(r io.Reader) (bool, error) {
129	reader := bufio.NewReader(r)
130	c := 0
131	for c != sniffLen {
132		b, err := reader.ReadByte()
133		if err == io.EOF {
134			break
135		}
136		if err != nil {
137			return false, err //nolint:wrapcheck
138		}
139
140		if b == byte(0) {
141			return true, nil
142		}
143
144		c++
145	}
146
147	return false, nil
148}
149
150// IsBinary returns true if the file is binary.
151func (f *File) IsBinary() (bool, error) {
152	stdout := new(bytes.Buffer)
153	stderr := new(bytes.Buffer)
154	err := f.Pipeline(stdout, stderr)
155	if err != nil {
156		return false, err //nolint:wrapcheck
157	}
158	r := bufio.NewReader(stdout)
159	return IsBinary(r)
160}
161
162// Mode returns the mode of the file in fs.FileMode format.
163func (e *TreeEntry) Mode() fs.FileMode {
164	m := e.Blob().Mode()
165	switch m { //nolint:exhaustive
166	case git.EntryTree:
167		return fs.ModeDir | fs.ModePerm
168	default:
169		return fs.FileMode(m) //nolint:gosec
170	}
171}
172
173// File returns the file for the TreeEntry.
174func (e *TreeEntry) File() *File {
175	b := e.Blob()
176	return &File{
177		Blob:  b,
178		Entry: e,
179	}
180}
181
182// Contents returns the contents of the file.
183func (e *TreeEntry) Contents() ([]byte, error) {
184	return e.File().Contents()
185}
186
187// Contents returns the contents of the file.
188func (f *File) Contents() ([]byte, error) {
189	return f.Bytes() //nolint:wrapcheck
190}