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