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}