-
Notifications
You must be signed in to change notification settings - Fork 2
/
files.go
122 lines (100 loc) · 2.75 KB
/
files.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
package eclint
import (
"bytes"
"context"
"errors"
"fmt"
"io/fs"
"os"
"os/exec"
"github.com/go-logr/logr"
)
// ListFilesContext lists the files in an asynchronous fashion
//
// When its empty, it relies on `git ls-files` first, which
// would fail if `git` is not present or the current working
// directory is not managed by it. In that case, it work the
// current working directory.
//
// When args are given, it recursively walks into them.
func ListFilesContext(ctx context.Context, args ...string) (<-chan string, <-chan error) {
if len(args) > 0 {
return WalkContext(ctx, args...)
}
dir := "."
log := logr.FromContextOrDiscard(ctx)
log.V(3).Info("fallback to `git ls-files`", "dir", dir)
return GitLsFilesContext(ctx, dir)
}
// WalkContext iterates on each path item recursively (asynchronously).
//
// Future work: use godirwalk.
func WalkContext(ctx context.Context, paths ...string) (<-chan string, <-chan error) {
filesChan := make(chan string, 128)
errChan := make(chan error, 1)
go func() {
defer close(filesChan)
defer close(errChan)
for _, path := range paths {
// shortcircuit files
if fi, err := os.Stat(path); err == nil && !fi.IsDir() {
filesChan <- path
break
}
err := fs.WalkDir(os.DirFS(path), ".", func(filename string, _ fs.DirEntry, err error) error {
if err != nil {
return err
}
select {
case filesChan <- filename:
return nil
case <-ctx.Done():
return fmt.Errorf("walking dir got interrupted: %w", ctx.Err())
}
})
if err != nil {
errChan <- err
break
}
}
}()
return filesChan, errChan
}
// GitLsFilesContext returns the list of file base on what is in the git index (asynchronously).
//
// -z is mandatory as some repositories non-ASCII file names which creates
// quoted and escaped file names. This method also returns directories for
// any submodule there is. Submodule will be skipped afterwards and thus
// not checked.
func GitLsFilesContext(ctx context.Context, path string) (<-chan string, <-chan error) {
filesChan := make(chan string, 128)
errChan := make(chan error, 1)
go func() {
defer close(filesChan)
defer close(errChan)
output, err := exec.CommandContext(ctx, "git", "ls-files", "-z", path).Output()
if err != nil {
var e *exec.ExitError
if ok := errors.As(err, &e); ok {
if e.ExitCode() == 128 {
err = fmt.Errorf("not a git repository: %w", e)
} else {
err = fmt.Errorf("git ls-files failed with %s: %w", e.Stderr, e)
}
}
errChan <- err
return
}
fs := bytes.Split(output, []byte{0})
// last line is empty
for _, f := range fs[:len(fs)-1] {
select {
case filesChan <- string(f):
// everything is good
case <-ctx.Done():
return
}
}
}()
return filesChan, errChan
}