-
Notifications
You must be signed in to change notification settings - Fork 60
/
fileset.go
124 lines (112 loc) · 2.83 KB
/
fileset.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package git
import (
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
"time"
ignore "github.com/sabhiram/go-gitignore"
)
type File struct {
fs.DirEntry
Absolute, Relative string
}
func (f File) Modified() (ts time.Time) {
info, err := f.Info()
if err != nil {
// return default time, beginning of epoch
return ts
}
return info.ModTime()
}
// FileSet facilitates fast recursive tracked file listing
// with respect to patterns defined in `.gitignore` file
//
// root: Root of the git repository
// ignore: List of patterns defined in `.gitignore`.
// We do not sync files that match this pattern
type FileSet struct {
root string
ignore *ignore.GitIgnore
}
// GetFileSet retrieves FileSet from Git repository checkout root
// or panics if no root is detected.
func GetFileSet() (FileSet, error) {
root, err := Root()
return NewFileSet(root), err
}
// Retuns FileSet for the repository located at `root`
func NewFileSet(root string) FileSet {
lines := []string{".git", ".bricks"}
rawIgnore, err := os.ReadFile(fmt.Sprintf("%s/.gitignore", root))
if err == nil {
// add entries from .gitignore if the file exists (did read correctly)
for _, line := range strings.Split(string(rawIgnore), "\n") {
// underlying library doesn't behave well with Rule 5 of .gitignore,
// hence this workaround
lines = append(lines, strings.Trim(line, "/"))
}
}
return FileSet{
root: root,
ignore: ignore.CompileIgnoreLines(lines...),
}
}
// Return root for fileset.
func (w *FileSet) Root() string {
return w.root
}
// Return all tracked files for Repo
func (w *FileSet) All() ([]File, error) {
return w.RecursiveListFiles(w.root)
}
func (w *FileSet) IsGitIgnored(pattern string) bool {
return w.ignore.MatchesPath(pattern)
}
// Recursively traverses dir in a depth first manner and returns a list of all files
// that are being tracked in the FileSet (ie not being ignored for matching one of the
// patterns in w.ignore)
func (w *FileSet) RecursiveListFiles(dir string) (fileList []File, err error) {
queue, err := readDir(dir, w.root)
if err != nil {
return nil, err
}
for len(queue) > 0 {
current := queue[0]
queue = queue[1:]
if w.ignore.MatchesPath(current.Relative) {
continue
}
if !current.IsDir() {
fileList = append(fileList, current)
continue
}
children, err := readDir(current.Absolute, w.root)
if err != nil {
return nil, err
}
queue = append(queue, children...)
}
return fileList, nil
}
func readDir(dir, root string) (queue []File, err error) {
f, err := os.Open(dir)
if err != nil {
return
}
defer f.Close()
dirs, err := f.ReadDir(-1)
if err != nil {
return
}
for _, v := range dirs {
absolute := filepath.Join(dir, v.Name())
relative, err := filepath.Rel(root, absolute)
if err != nil {
return nil, err
}
queue = append(queue, File{v, absolute, relative})
}
return
}