1package git23import (4 "bufio"5 "bytes"6 "io"7 "io/fs"8 "path/filepath"9 "sort"1011 "github.com/aymanbagabas/git-module"12)1314// Tree is a wrapper around git.Tree with helper methods.15type Tree struct {16 *git.Tree17 Path string18 Repository *Repository19}2021// TreeEntry is a wrapper around git.TreeEntry with helper methods.22type TreeEntry struct {23 *git.TreeEntry24 // path is the full path of the file25 path string26}2728// Entries is a wrapper around git.Entries.29type Entries []*TreeEntry3031var 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}3940// Len implements sort.Interface.41func (es Entries) Len() int { return len(es) }4243// Swap implements sort.Interface.44func (es Entries) Swap(i, j int) { es[i], es[j] = es[j], es[i] }4546// Less implements sort.Interface.47func (es Entries) Less(i, j int) bool {48 t1, t2 := es[i], es[j]49 var k int50 for k = 0; k < len(sorters)-1; k++ {51 sorter := sorters[k]52 switch {53 case sorter(t1, t2):54 return true55 case sorter(t2, t1):56 return false57 }58 }59 return sorters[k](t1, t2)60}6162// Sort sorts the entries in the tree.63func (es Entries) Sort() {64 sort.Sort(es)65}6667// File is a wrapper around git.Blob with helper methods.68type File struct {69 *git.Blob70 Entry *TreeEntry71}7273// Name returns the name of the file.74func (f *File) Name() string {75 return f.Entry.Name()76}7778// Path returns the full path of the file.79func (f *File) Path() string {80 return f.Entry.path81}8283// 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, err88 }89 return &Tree{90 Tree: tree,91 Path: path,92 Repository: t.Repository,93 }, nil94}9596// 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, err101 }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, nil110}111112// 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, err117 }118 return &TreeEntry{119 TreeEntry: entry,120 path: filepath.Join(t.Path, entry.Name()),121 }, nil122}123124const sniffLen = 8000125126// 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#n198128func IsBinary(r io.Reader) (bool, error) {129 reader := bufio.NewReader(r)130 c := 0131 for {132 if c == sniffLen {133 break134 }135136 b, err := reader.ReadByte()137 if err == io.EOF {138 break139 }140 if err != nil {141 return false, err142 }143144 if b == byte(0) {145 return true, nil146 }147148 c++149 }150151 return false, nil152}153154// 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, err161 }162 r := bufio.NewReader(stdout)163 return IsBinary(r)164}165166// 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.ModePerm172 default:173 return fs.FileMode(m) //nolint:gosec174 }175}176177// 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}185186// Contents returns the contents of the file.187func (e *TreeEntry) Contents() ([]byte, error) {188 return e.File().Contents()189}190191// Contents returns the contents of the file.192func (f *File) Contents() ([]byte, error) {193 return f.Blob.Bytes()194}