1package git
2
3import (
4 "bufio"
5 "bytes"
6 "io"
7 "io/fs"
8 "path/filepath"
9 "sort"
10
11 git "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
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
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
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 {
132 if c == sniffLen {
133 break
134 }
135
136 b, err := reader.ReadByte()
137 if err == io.EOF {
138 break
139 }
140 if err != nil {
141 return false, err
142 }
143
144 if b == byte(0) {
145 return true, nil
146 }
147
148 c++
149 }
150
151 return false, nil
152}
153
154// IsBinary returns true if the file is binary.
155func (f *File) IsBinary() (bool, error) {
156 stdout := new(bytes.Buffer)
157 stderr := new(bytes.Buffer)
158 err := f.Pipeline(stdout, stderr)
159 if err != nil {
160 return false, err
161 }
162 r := bufio.NewReader(stdout)
163 return IsBinary(r)
164}
165
166// Mode returns the mode of the file in fs.FileMode format.
167func (e *TreeEntry) Mode() fs.FileMode {
168 m := e.Blob().Mode()
169 switch m {
170 case git.EntryTree:
171 return fs.ModeDir | fs.ModePerm
172 default:
173 return fs.FileMode(m) //nolint:gosec
174 }
175}
176
177// File returns the file for the TreeEntry.
178func (e *TreeEntry) File() *File {
179 b := e.Blob()
180 return &File{
181 Blob: b,
182 Entry: e,
183 }
184}
185
186// Contents returns the contents of the file.
187func (e *TreeEntry) Contents() ([]byte, error) {
188 return e.File().Contents()
189}
190
191// Contents returns the contents of the file.
192func (f *File) Contents() ([]byte, error) {
193 return f.Blob.Bytes()
194}