|
9 | 9 |
|
10 | 10 | "github.com/boneskull/gh-stack/internal/config" |
11 | 11 | "github.com/boneskull/gh-stack/internal/git" |
| 12 | + "github.com/boneskull/gh-stack/internal/health" |
12 | 13 | "github.com/boneskull/gh-stack/internal/style" |
13 | 14 | "github.com/spf13/cobra" |
14 | 15 | ) |
@@ -106,125 +107,98 @@ func runDoctor(cmd *cobra.Command, args []string) error { |
106 | 107 | func checkBranch(g *git.Git, cfg *config.Config, s *style.Style, branch string, fix bool) branchResult { |
107 | 108 | result := branchResult{name: branch} |
108 | 109 |
|
109 | | - // Check 1: branch exists locally |
110 | | - if !g.BranchExists(branch) { |
111 | | - result.issues = append(result.issues, s.Error("Branch does not exist locally")) |
| 110 | + issues := health.CheckBranch(g, cfg, branch) |
| 111 | + if len(issues) == 0 { |
| 112 | + result.healthy = true |
112 | 113 | return result |
113 | 114 | } |
114 | 115 |
|
115 | | - // Check 2: parent exists locally |
116 | | - parent, err := cfg.GetParent(branch) |
117 | | - if err != nil { |
118 | | - result.issues = append(result.issues, s.Error("No parent configured")) |
| 116 | + // If not fixing, format issues with styling and return. |
| 117 | + if !fix { |
| 118 | + for _, iss := range issues { |
| 119 | + switch iss.Kind { |
| 120 | + case health.KindBranchMissing, health.KindParentMissing, health.KindNoForkPoint: |
| 121 | + result.issues = append(result.issues, s.Error(iss.Message)) |
| 122 | + default: |
| 123 | + result.issues = append(result.issues, iss.Message) |
| 124 | + } |
| 125 | + } |
119 | 126 | return result |
120 | 127 | } |
121 | 128 |
|
122 | | - trunk, _ := cfg.GetTrunk() //nolint:errcheck // already validated in runDoctor |
123 | | - if parent == trunk { |
124 | | - if !g.BranchExists(trunk) { |
125 | | - result.issues = append(result.issues, s.Errorf("Trunk branch %s does not exist locally", trunk)) |
126 | | - return result |
| 129 | + // Attempt to fix: dispatch based on the issue kind. |
| 130 | + kind := issues[0].Kind |
| 131 | + if !issues[0].Fixable && kind != health.KindDrift { |
| 132 | + // Unfixable issues (missing branch, missing parent, check failures) |
| 133 | + for _, iss := range issues { |
| 134 | + result.issues = append(result.issues, s.Error(iss.Message)) |
127 | 135 | } |
128 | | - } else if !g.BranchExists(parent) { |
129 | | - result.issues = append(result.issues, s.Errorf("Parent branch %s does not exist locally", parent)) |
130 | 136 | return result |
131 | 137 | } |
132 | 138 |
|
133 | | - // Check 3: fork point is stored |
134 | | - storedFP, err := cfg.GetForkPoint(branch) |
135 | | - if err != nil { |
136 | | - if fix { |
137 | | - newFP, fixErr := computeForkPoint(g, parent, branch) |
138 | | - if fixErr != nil { |
139 | | - result.issues = append(result.issues, fmt.Sprintf("No fork point stored and could not compute one: %v", fixErr)) |
140 | | - return result |
141 | | - } |
142 | | - if setErr := cfg.SetForkPoint(branch, newFP); setErr != nil { |
143 | | - result.issues = append(result.issues, fmt.Sprintf("Failed to set fork point: %v", setErr)) |
144 | | - return result |
145 | | - } |
146 | | - result.fixed = true |
147 | | - result.fixMsg = fmt.Sprintf("set fork point to %s", git.AbbrevSHA(newFP)) |
| 139 | + parent, _ := cfg.GetParent(branch) //nolint:errcheck // health check validated parent exists |
| 140 | + storedFP, _ := cfg.GetForkPoint(branch) //nolint:errcheck // may be empty for KindNoForkPoint |
| 141 | + |
| 142 | + switch kind { |
| 143 | + case health.KindNoForkPoint: |
| 144 | + newFP, fixErr := computeForkPoint(g, parent, branch) |
| 145 | + if fixErr != nil { |
| 146 | + result.issues = append(result.issues, fmt.Sprintf("No fork point stored and could not compute one: %v", fixErr)) |
148 | 147 | return result |
149 | 148 | } |
150 | | - result.issues = append(result.issues, s.Error("No fork point stored")) |
151 | | - return result |
152 | | - } |
153 | | - |
154 | | - // Check 4: fork point commit exists |
155 | | - if !g.CommitExists(storedFP) { |
156 | | - if fix { |
157 | | - newFP, fixErr := computeForkPoint(g, parent, branch) |
158 | | - if fixErr != nil { |
159 | | - result.issues = append(result.issues, fmt.Sprintf("Stored fork point %s does not exist and could not compute replacement: %v", git.AbbrevSHA(storedFP), fixErr)) |
160 | | - return result |
161 | | - } |
162 | | - if setErr := setForkPointWithComment(g, cfg, branch, storedFP, newFP); setErr != nil { |
163 | | - result.issues = append(result.issues, fmt.Sprintf("Failed to set fork point: %v", setErr)) |
164 | | - return result |
165 | | - } |
166 | | - result.fixed = true |
167 | | - result.fixMsg = fmt.Sprintf("updated fork point %s \u2192 %s", git.AbbrevSHA(storedFP), git.AbbrevSHA(newFP)) |
| 149 | + if setErr := cfg.SetForkPoint(branch, newFP); setErr != nil { |
| 150 | + result.issues = append(result.issues, fmt.Sprintf("Failed to set fork point: %v", setErr)) |
168 | 151 | return result |
169 | 152 | } |
170 | | - result.issues = append(result.issues, fmt.Sprintf("Stored fork point %s does not exist", git.AbbrevSHA(storedFP))) |
171 | | - return result |
172 | | - } |
| 153 | + result.fixed = true |
| 154 | + result.fixMsg = fmt.Sprintf("set fork point to %s", git.AbbrevSHA(newFP)) |
173 | 155 |
|
174 | | - // Check 5: fork point is ancestor of branch |
175 | | - isAnc, err := g.IsAncestor(storedFP, branch) |
176 | | - if err != nil { |
177 | | - result.issues = append(result.issues, fmt.Sprintf("Failed to check if stored fork point %s is an ancestor of %s: %v", git.AbbrevSHA(storedFP), branch, err)) |
178 | | - return result |
179 | | - } |
180 | | - if !isAnc { |
181 | | - if fix { |
182 | | - newFP, fixErr := computeForkPoint(g, parent, branch) |
183 | | - if fixErr != nil { |
184 | | - result.issues = append(result.issues, fmt.Sprintf("Stored fork point %s is not an ancestor of %s and could not compute replacement: %v", git.AbbrevSHA(storedFP), branch, fixErr)) |
185 | | - return result |
186 | | - } |
187 | | - if setErr := setForkPointWithComment(g, cfg, branch, storedFP, newFP); setErr != nil { |
188 | | - result.issues = append(result.issues, fmt.Sprintf("Failed to set fork point: %v", setErr)) |
189 | | - return result |
190 | | - } |
191 | | - result.fixed = true |
192 | | - result.fixMsg = fmt.Sprintf("updated fork point %s \u2192 %s", git.AbbrevSHA(storedFP), git.AbbrevSHA(newFP)) |
| 156 | + case health.KindForkPointMissing: |
| 157 | + newFP, fixErr := computeForkPoint(g, parent, branch) |
| 158 | + if fixErr != nil { |
| 159 | + result.issues = append(result.issues, fmt.Sprintf("Stored fork point %s does not exist and could not compute replacement: %v", git.AbbrevSHA(storedFP), fixErr)) |
193 | 160 | return result |
194 | 161 | } |
195 | | - result.issues = append(result.issues, fmt.Sprintf("Stored fork point %s is not an ancestor of %s", git.AbbrevSHA(storedFP), branch)) |
196 | | - return result |
197 | | - } |
| 162 | + if setErr := setForkPointWithComment(g, cfg, branch, storedFP, newFP); setErr != nil { |
| 163 | + result.issues = append(result.issues, fmt.Sprintf("Failed to set fork point: %v", setErr)) |
| 164 | + return result |
| 165 | + } |
| 166 | + result.fixed = true |
| 167 | + result.fixMsg = fmt.Sprintf("updated fork point %s \u2192 %s", git.AbbrevSHA(storedFP), git.AbbrevSHA(newFP)) |
198 | 168 |
|
199 | | - // Check 6: drift detection |
200 | | - mergeBase, err := g.GetMergeBase(parent, branch) |
201 | | - if err != nil { |
202 | | - result.issues = append(result.issues, |
203 | | - fmt.Sprintf("Failed to compute merge-base for %s and %s: %v", parent, branch, err), |
204 | | - ) |
205 | | - return result |
206 | | - } |
| 169 | + case health.KindForkPointNotAncestor: |
| 170 | + newFP, fixErr := computeForkPoint(g, parent, branch) |
| 171 | + if fixErr != nil { |
| 172 | + result.issues = append(result.issues, fmt.Sprintf("Stored fork point %s is not an ancestor of %s and could not compute replacement: %v", git.AbbrevSHA(storedFP), branch, fixErr)) |
| 173 | + return result |
| 174 | + } |
| 175 | + if setErr := setForkPointWithComment(g, cfg, branch, storedFP, newFP); setErr != nil { |
| 176 | + result.issues = append(result.issues, fmt.Sprintf("Failed to set fork point: %v", setErr)) |
| 177 | + return result |
| 178 | + } |
| 179 | + result.fixed = true |
| 180 | + result.fixMsg = fmt.Sprintf("updated fork point %s \u2192 %s", git.AbbrevSHA(storedFP), git.AbbrevSHA(newFP)) |
207 | 181 |
|
208 | | - if storedFP != mergeBase { |
209 | | - if fix { |
210 | | - if setErr := setForkPointWithComment(g, cfg, branch, storedFP, mergeBase); setErr != nil { |
211 | | - result.issues = append(result.issues, fmt.Sprintf("Failed to set fork point: %v", setErr)) |
212 | | - return result |
213 | | - } |
214 | | - result.fixed = true |
215 | | - result.fixMsg = fmt.Sprintf("updated fork point %s \u2192 %s", git.AbbrevSHA(storedFP), git.AbbrevSHA(mergeBase)) |
| 182 | + case health.KindDrift: |
| 183 | + mergeBase, err := g.GetMergeBase(parent, branch) |
| 184 | + if err != nil { |
| 185 | + result.issues = append(result.issues, fmt.Sprintf("Failed to compute merge-base for fix: %v", err)) |
216 | 186 | return result |
217 | 187 | } |
218 | | - result.issues = append(result.issues, |
219 | | - fmt.Sprintf("Stored parent: %s", parent), |
220 | | - fmt.Sprintf("Stored fork: %s", git.AbbrevSHA(storedFP)), |
221 | | - fmt.Sprintf("Actual fork: %s", git.AbbrevSHA(mergeBase)), |
222 | | - "Drift detected: stored fork point does not match merge-base", |
223 | | - ) |
224 | | - return result |
| 188 | + if setErr := setForkPointWithComment(g, cfg, branch, storedFP, mergeBase); setErr != nil { |
| 189 | + result.issues = append(result.issues, fmt.Sprintf("Failed to set fork point: %v", setErr)) |
| 190 | + return result |
| 191 | + } |
| 192 | + result.fixed = true |
| 193 | + result.fixMsg = fmt.Sprintf("updated fork point %s \u2192 %s", git.AbbrevSHA(storedFP), git.AbbrevSHA(mergeBase)) |
| 194 | + |
| 195 | + default: |
| 196 | + // Shouldn't happen, but surface issues if it does |
| 197 | + for _, iss := range issues { |
| 198 | + result.issues = append(result.issues, iss.Message) |
| 199 | + } |
225 | 200 | } |
226 | 201 |
|
227 | | - result.healthy = true |
228 | 202 | return result |
229 | 203 | } |
230 | 204 |
|
|
0 commit comments