7
7
"os"
8
8
"os/exec"
9
9
"path/filepath"
10
+ "sort"
10
11
"strings"
11
12
"sync"
12
13
@@ -29,6 +30,7 @@ var getwdFunc = os.Getwd
29
30
func main () { //go-cov:skip
30
31
var opts struct {
31
32
outDir string
33
+ dirDepth int
32
34
doTruncateSecrets bool
33
35
}
34
36
app := & cli.App {
@@ -41,6 +43,12 @@ func main() { //go-cov:skip
41
43
Usage : "Directory to output build manifests" ,
42
44
Destination : & opts .outDir ,
43
45
},
46
+ & cli.IntFlag {
47
+ Name : "depth" ,
48
+ Value : 0 ,
49
+ Usage : "Minimum directory depth to work with (e.g., 2 means paths will be at least two levels deep like 'aaa/bbb/')" ,
50
+ Destination : & opts .dirDepth ,
51
+ },
44
52
& cli.BoolFlag {
45
53
Name : "truncate-secrets" ,
46
54
Value : false ,
@@ -49,7 +57,12 @@ func main() { //go-cov:skip
49
57
},
50
58
},
51
59
Action : func (c * cli.Context ) error {
52
- return kustomizeBuildDirs (opts .outDir , opts .doTruncateSecrets , c .Args ().Slice ())
60
+ return kustomizeBuildDirs (
61
+ opts .outDir ,
62
+ opts .dirDepth ,
63
+ opts .doTruncateSecrets ,
64
+ c .Args ().Slice (),
65
+ )
53
66
},
54
67
}
55
68
@@ -59,7 +72,12 @@ func main() { //go-cov:skip
59
72
}
60
73
}
61
74
62
- func kustomizeBuildDirs (outDir string , doTruncateSecrets bool , filepaths []string ) error {
75
+ func kustomizeBuildDirs (
76
+ outDir string ,
77
+ dirDepth int ,
78
+ doTruncateSecrets bool ,
79
+ filepaths []string ,
80
+ ) error {
63
81
rootDir , err := getwdFunc ()
64
82
if err != nil {
65
83
return fmt .Errorf ("error reading working directory: %v" , err )
@@ -69,7 +87,7 @@ func kustomizeBuildDirs(outDir string, doTruncateSecrets bool, filepaths []strin
69
87
return err
70
88
}
71
89
72
- kustomizationRoots , err := findKustomizationRoots (rootDir , filepaths )
90
+ kustomizationRoots , err := findKustomizationRoots (rootDir , filepaths , dirDepth )
73
91
if err != nil {
74
92
return err
75
93
}
@@ -109,10 +127,103 @@ func checkKustomizeInstalled() error {
109
127
return nil
110
128
}
111
129
130
+ // splitPath takes a full file path string and returns a slice of its directory components.
131
+ // It extracts the directory portion of the path and splits it by forward slashes (/).
132
+ // Example: "aaa/bbb/ccc/file.yaml" => ["aaa", "bbb", "ccc"]
133
+ func splitPath (path string ) []string {
134
+ cleaned := filepath .ToSlash (filepath .Dir (path ))
135
+ if cleaned == "." {
136
+ return []string {}
137
+ }
138
+ return strings .Split (cleaned , "/" )
139
+ }
140
+
141
+ // commonPrefix compares two string slices (representing directory segments)
142
+ // and returns their longest shared prefix.
143
+ // Example: ["aaa", "bbb", "ccc"], ["aaa", "bbb", "ddd"] => ["aaa", "bbb"]
144
+ func commonPrefix (a , b []string ) []string {
145
+ minLen := len (a )
146
+ if len (b ) < minLen {
147
+ minLen = len (b )
148
+ }
149
+ out := make ([]string , 0 , minLen )
150
+ for i := 0 ; i < minLen ; i ++ {
151
+ if a [i ] != b [i ] {
152
+ break
153
+ }
154
+ out = append (out , a [i ])
155
+ }
156
+ return out
157
+ }
158
+
159
+ // deepestCommonDirs takes a list of file paths and returns a list of directory paths
160
+ // that represent the deepest common directory for each group of similarly prefixed files
161
+ // with a minimum directory depth enforced.
162
+ // Example (with minDepth = 2):
163
+ // Input: ["aaa/file.yaml", "aaa/bbb/file.yaml", "bbb/ccc/file1.yaml", "bbb/ccc/file2.yaml"]
164
+ // Output: ["aaa/bbb/", "bbb/ccc/"]
165
+ func deepestCommonDirs (paths []string , minDepth int ) []string {
166
+ if len (paths ) == 0 {
167
+ return nil
168
+ }
169
+
170
+ dirSet := make (map [string ]struct {})
171
+
172
+ for _ , path := range paths {
173
+ dir := filepath .ToSlash (filepath .Dir (path ))
174
+ if dir == "." {
175
+ // File in root directory is represented as empty string
176
+ dir = ""
177
+ }
178
+ dirSet [dir ] = struct {}{}
179
+ }
180
+
181
+ // Filter out directories that are shallower than minDepth
182
+ var dirs []string
183
+ for dir := range dirSet {
184
+ segments := strings .Split (dir , "/" )
185
+ if dir == "" {
186
+ segments = []string {}
187
+ }
188
+ if len (segments ) >= minDepth {
189
+ dirs = append (dirs , dir )
190
+ }
191
+ }
192
+ sort .Strings (dirs ) // Sort for consistent and hierarchical comparison
193
+
194
+ var result []string
195
+ skipPrefixes := make (map [string ]struct {})
196
+
197
+ for _ , dir := range dirs {
198
+ skip := false
199
+
200
+ for parent := range skipPrefixes {
201
+ if strings .HasPrefix (dir + "/" , parent + "/" ) {
202
+ skip = true
203
+ break
204
+ }
205
+ }
206
+
207
+ if ! skip {
208
+ skipPrefixes [dir ] = struct {}{}
209
+ if dir == "" {
210
+ result = append (result , "" )
211
+ } else {
212
+ result = append (result , dir + "/" )
213
+ }
214
+ }
215
+ }
216
+
217
+ return result
218
+ }
219
+
112
220
// findKustomizationRoots finds, for each given path, the first parent
113
221
// directory containing a 'kustomization.yaml'. It returns a list of such paths
114
222
// relative to the root
115
- func findKustomizationRoots (root string , paths []string ) ([]string , error ) {
223
+ func findKustomizationRoots (root string , paths []string , dirDepth int ) ([]string , error ) {
224
+ // Group paths by shared prefixes and return their deepest common directories
225
+ paths = deepestCommonDirs (paths , dirDepth )
226
+
116
227
// there may be multiple changes under the same path
117
228
// so use a map to track unique ones
118
229
rootsMap := map [string ]struct {}{}
0 commit comments