Skip to content

Commit 1e4add9

Browse files
(alpha update): Only run makefile targets on files where conflict is not found
Assisted-by: ChatGPT (OpenAI)
1 parent 4a67828 commit 1e4add9

File tree

3 files changed

+201
-30
lines changed

3 files changed

+201
-30
lines changed
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package helpers
18+
19+
import (
20+
"errors"
21+
"io/fs"
22+
"os"
23+
"os/exec"
24+
"path/filepath"
25+
"strings"
26+
)
27+
28+
var (
29+
errFoundConflict = errors.New("found-conflict")
30+
errGoConflict = errors.New("go-conflict")
31+
)
32+
33+
type ConflictSummary struct {
34+
Makefile bool // Makefile or makefile conflicted
35+
API bool // anything under api/ or apis/ conflicted
36+
AnyGo bool // any *.go file anywhere conflicted
37+
}
38+
39+
func DetectConflicts() ConflictSummary {
40+
return ConflictSummary{
41+
Makefile: hasConflict("Makefile", "makefile"),
42+
API: hasConflict("api", "apis"),
43+
AnyGo: hasGoConflicts(), // checks all *.go in repo (index fast path + FS scan)
44+
}
45+
}
46+
47+
// hasConflict: file/dir conflicts via index fast path + marker scan.
48+
func hasConflict(paths ...string) bool {
49+
if len(paths) == 0 {
50+
return false
51+
}
52+
// Fast path: any unmerged entry under these pathspecs?
53+
args := append([]string{"ls-files", "-u", "--"}, paths...)
54+
out, err := exec.Command("git", args...).Output()
55+
if err == nil && len(strings.TrimSpace(string(out))) > 0 {
56+
return true
57+
}
58+
59+
// Fallback: scan for conflict markers.
60+
hasMarkers := func(p string) bool {
61+
// Best-effort, skip large likely-binaries.
62+
if fi, err := os.Stat(p); err == nil && fi.Size() > 1<<20 {
63+
return false
64+
}
65+
b, err := os.ReadFile(p)
66+
if err != nil {
67+
return false
68+
}
69+
s := string(b)
70+
return strings.Contains(s, "<<<<<<<") &&
71+
strings.Contains(s, "=======") &&
72+
strings.Contains(s, ">>>>>>>")
73+
}
74+
75+
for _, root := range paths {
76+
info, err := os.Stat(root)
77+
if err != nil {
78+
continue
79+
}
80+
if !info.IsDir() {
81+
if hasMarkers(root) {
82+
return true
83+
}
84+
continue
85+
}
86+
87+
werr := filepath.WalkDir(root, func(p string, d fs.DirEntry, walkErr error) error {
88+
if walkErr != nil || d.IsDir() {
89+
return nil
90+
}
91+
// Skip obvious noise dirs.
92+
if d.Name() == ".git" || strings.Contains(p, string(filepath.Separator)+".git"+string(filepath.Separator)) {
93+
return nil
94+
}
95+
if hasMarkers(p) {
96+
return errFoundConflict
97+
}
98+
return nil
99+
})
100+
if errors.Is(werr, errFoundConflict) {
101+
return true
102+
}
103+
}
104+
return false
105+
}
106+
107+
// hasGoConflicts: any *.go file conflicted (repo-wide).
108+
func hasGoConflicts(roots ...string) bool {
109+
// Fast path: any unmerged *.go anywhere?
110+
if out, err := exec.Command("git", "ls-files", "-u", "--", "*.go").Output(); err == nil {
111+
if len(strings.TrimSpace(string(out))) > 0 {
112+
return true
113+
}
114+
}
115+
// Fallback: filesystem scan (repo-wide or limited to roots if provided).
116+
if len(roots) == 0 {
117+
roots = []string{"."}
118+
}
119+
for _, root := range roots {
120+
werr := filepath.WalkDir(root, func(p string, d fs.DirEntry, walkErr error) error {
121+
if walkErr != nil || d.IsDir() || !strings.HasSuffix(p, ".go") {
122+
return nil
123+
}
124+
// Skip .git and large files.
125+
if strings.Contains(p, string(filepath.Separator)+".git"+string(filepath.Separator)) {
126+
return nil
127+
}
128+
if fi, err := os.Stat(p); err == nil && fi.Size() > 1<<20 {
129+
return nil
130+
}
131+
b, err := os.ReadFile(p)
132+
if err != nil {
133+
return nil
134+
}
135+
s := string(b)
136+
if strings.Contains(s, "<<<<<<<") &&
137+
strings.Contains(s, "=======") &&
138+
strings.Contains(s, ">>>>>>>") {
139+
return errGoConflict
140+
}
141+
return nil
142+
})
143+
if errors.Is(werr, errGoConflict) {
144+
return true
145+
}
146+
}
147+
return false
148+
}
149+
150+
// DecideMakeTargets applies simple policy over the summary.
151+
func DecideMakeTargets(cs ConflictSummary) []string {
152+
all := []string{"manifests", "generate", "fmt", "vet", "lint-fix"}
153+
if cs.Makefile {
154+
return nil
155+
}
156+
keep := make([]string, 0, len(all))
157+
for _, t := range all {
158+
if cs.API && (t == "manifests" || t == "generate") {
159+
continue
160+
}
161+
if cs.AnyGo && (t == "fmt" || t == "vet" || t == "lint-fix") {
162+
continue
163+
}
164+
keep = append(keep, t)
165+
}
166+
return keep
167+
}

pkg/cli/alpha/internal/update/update.go

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -458,36 +458,44 @@ func cleanupBranch() error {
458458
return nil
459459
}
460460

461-
// runMakeTargets is a helper function to run make with the targets necessary
462-
// to ensure all the necessary components are generated, formatted and linted.
463-
func runMakeTargets() {
464-
targets := []string{"manifests", "generate", "fmt", "vet", "lint-fix"}
465-
for _, target := range targets {
466-
err := util.RunCmd(fmt.Sprintf("Running make %s", target), "make", target)
467-
if err != nil {
468-
log.Warn("make target failed", "target", target, "error", err)
461+
// runMakeTargets runs the make targets needed to keep the tree consistent.
462+
// If skipConflicts is true, it avoids running targets that are guaranteed
463+
// to fail noisily when there are unresolved conflicts.
464+
func runMakeTargets(skipConflicts bool) {
465+
if !skipConflicts {
466+
for _, t := range []string{"manifests", "generate", "fmt", "vet", "lint-fix"} {
467+
if err := util.RunCmd(fmt.Sprintf("Running make %s", t), "make", t); err != nil {
468+
log.Warn("make target failed", "target", t, "error", err)
469+
}
469470
}
471+
return
470472
}
471-
}
472473

473-
// hasMakefileConflict returns true if the Makefile (or makefile) is in conflict.
474-
func hasMakefileConflict() bool {
475-
// Check unmerged entries for Makefile/makefile.
476-
out, err := exec.Command("git", "ls-files", "-u", "--", "Makefile", "makefile").Output()
477-
if err == nil && len(strings.TrimSpace(string(out))) > 0 {
478-
return true
474+
// Conflict-aware path: decide what to run based on repo state.
475+
cs := helpers.DetectConflicts()
476+
targets := helpers.DecideMakeTargets(cs)
477+
478+
if cs.Makefile {
479+
log.Warn("Skipping all make targets because Makefile has merge conflicts")
480+
return
481+
}
482+
if cs.API {
483+
log.Warn("API conflicts detected; skipping make targets: manifests, generate")
484+
}
485+
if cs.AnyGo {
486+
log.Warn("Go conflicts detected; skipping make targets: fmt, vet, lint-fix")
479487
}
480488

481-
// Fallback: file content contains conflict markers.
482-
check := func(p string) bool {
483-
b, err := os.ReadFile(p)
484-
if err != nil {
485-
return false
489+
if len(targets) == 0 {
490+
log.Warn("No make targets will be run due to conflicts")
491+
return
492+
}
493+
494+
for _, t := range targets {
495+
if err := util.RunCmd(fmt.Sprintf("Running make %s", t), "make", t); err != nil {
496+
log.Warn("make target failed", "target", t, "error", err)
486497
}
487-
s := string(b)
488-
return strings.Contains(s, "<<<<<<<") && strings.Contains(s, "=======") && strings.Contains(s, ">>>>>>>")
489498
}
490-
return check("Makefile") || check("makefile")
491499
}
492500

493501
// runAlphaGenerate executes the old Kubebuilder version's 'alpha generate' command
@@ -507,7 +515,7 @@ func runAlphaGenerate(tempDir, version string) error {
507515
}
508516

509517
log.Info("Project scaffold generation complete", "version", version)
510-
runMakeTargets()
518+
runMakeTargets(false)
511519
return nil
512520
}
513521

@@ -620,11 +628,7 @@ func (opts *Update) mergeOriginalToUpgrade() (bool, error) {
620628
}
621629

622630
// Best effort to run make targets to ensure the project is in a good state
623-
if hasMakefileConflict() {
624-
log.Warn("Skipping make targets because the Makefile has merge conflicts.")
625-
} else {
626-
runMakeTargets()
627-
}
631+
runMakeTargets(true)
628632

629633
// Step 4: Stage and commit
630634
if err := helpers.GitCmd(opts.GitConfig, "add", "--all").Run(); err != nil {

pkg/cli/alpha/internal/update/update_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ exit 1`
300300
Expect(mockBinResponse(fail, mockMake)).To(Succeed())
301301

302302
// Should not panic even if make fails; just logs a warning.
303-
runMakeTargets()
303+
runMakeTargets(false)
304304
})
305305
})
306306

0 commit comments

Comments
 (0)