forked from kynrai/tainted
-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
154 lines (136 loc) · 3.46 KB
/
main.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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package main
import (
"bufio"
"flag"
"fmt"
"go/build"
"log"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
)
func usage() {
fmt.Println(`Usage: <list of packages via stdin> | tainted
Example:
go list ./... | tainted
This program takes a lits of packages from stdin and returns a list of packages
which have beend tained and need to be rebuilt. A package is tained when one or
more of its dependacies have been modified`)
fmt.Println()
flag.PrintDefaults()
}
var (
packages map[string]struct{} // the packages to check for taint
changedDirs map[string]struct{} // the directories which contain modified files
cache map[string]*build.Package // a map[>package name>]<build.Package> to skip repeat lookups
gitDirPtr *string // the git directory to check for changes
commitFromPtr *string // the earliest commit to diff
commitToPtr *string // the latest commit to diff
)
func init() {
cache = make(map[string]*build.Package)
changedDirs = make(map[string]struct{})
packages = make(map[string]struct{})
}
func main() {
gitDirPtr = flag.String("dir", ".", "the git directory to check")
commitFromPtr = flag.String("from", "HEAD~1", "commit to take changes from")
commitToPtr = flag.String("to", "HEAD", "commit to take changes to")
flag.Usage = usage
flag.Parse()
// check if we have anything coming from stdin
stat, err := os.Stdin.Stat()
if err != nil {
fmt.Printf("failed to read from stdin: %s", err)
os.Exit(1)
}
if (stat.Mode() & os.ModeNamedPipe) == 0 {
flag.Usage()
os.Exit(0)
}
// populate the changed directories slice
modified()
// read packages from stdin
readPackages()
// for each package we want to get its full deps tree and see if it
// contains any elements from the changedDirs
cwd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
output := make(map[string]struct{})
for k := range packages {
// get all the deps
deps, err := findDeps(k, cwd)
if err != nil {
log.Fatal(err)
}
if hasChanges(deps) {
output[k] = struct{}{}
}
}
// finally to make it all pretty, sort it in a slice
prettyOutput := make([]string, 0, len(output))
for k := range output {
prettyOutput = append(prettyOutput, k)
}
if len(prettyOutput) == 0 {
return
}
sort.Strings(prettyOutput)
fmt.Println(strings.Join(prettyOutput, "\n"))
}
// checks to see if any of the deps have the same suffix as anything in the changedDirs
func hasChanges(deps []string) bool {
for _, v := range deps {
for k := range changedDirs {
if strings.HasSuffix(v, k) {
return true
}
}
}
return false
}
// read all the packages from stdin into the global packages var
func readPackages() {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
packages[scanner.Text()] = struct{}{}
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
}
// modified will use git to find out which folders have been changed
func modified() {
cmdArgs := []string{
"--no-pager",
"-C",
*gitDirPtr,
"diff",
"--name-only",
*commitFromPtr,
*commitToPtr,
}
cmd := exec.Command("git", cmdArgs...)
cmdReader, err := cmd.StdoutPipe()
if err != nil {
log.Fatal(err)
}
scanner := bufio.NewScanner(cmdReader)
go func() {
for scanner.Scan() {
if dir := filepath.Dir(scanner.Text()); dir != "." {
changedDirs[dir] = struct{}{}
}
}
}()
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
if err := cmd.Wait(); err != nil {
log.Fatal(err)
}
}