Skip to content

Commit 685ed93

Browse files
authored
[refactor] Enable gocognit linter and fix all violations (#84)
* [refactor] Enable gocognit linter in golangci-lint config Add gocognit (cognitive complexity) linter alongside existing gocyclo with min-complexity threshold of 15. Exclude test files from checks. * [refactor] Reduce cognitive complexity in 10 production functions Extract helper functions from functions exceeding gocognit threshold of 15: - tool_list.go: filterListCandidates, detailsToListItems - tool_status.go: buildStatusResult, buildStatusItem - planner.go: buildConflictMatrix, selectMergeOrder - tool_conflict.go: processOverlaps, processDryMerges - archived.go: searchByPrefixedTask, searchByExactMatch, searchByTaskExtraction - status.go: buildCLIStatusSummary, buildStatusRow, formatAgeCell - exec.go: formatExecStatus, printIndentedOutput - copy.go: copyEntry, copyDirEntry - manager.go: tryCloneFromExisting
1 parent 5e50fe7 commit 685ed93

10 files changed

Lines changed: 328 additions & 227 deletions

File tree

.golangci.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ linters:
1515
- funcorder
1616
- goconst
1717
- gocritic
18+
- gocognit
1819
- gocyclo
1920
- gosec
2021
- intrange
@@ -50,6 +51,8 @@ linters:
5051
constructor: true
5152
struct-method: true
5253
alphabetical: false
54+
gocognit:
55+
min-complexity: 15
5356
gocyclo:
5457
min-complexity: 15
5558
goconst:
@@ -79,6 +82,8 @@ linters:
7982
rules:
8083
- path: (_test\.go|testutil/)
8184
linters: [gosec]
85+
- path: _test\.go
86+
linters: [gocognit]
8287
- path: _test\.go
8388
linters: [rimbalint]
8489
text: "nolateexport:"

cmd/exec.go

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cmd
33
import (
44
"errors"
55
"fmt"
6+
"io"
67
"strings"
78
"sync"
89

@@ -229,6 +230,7 @@ func filterDirtyWorktrees(r git.Runner, s *spinner.Spinner, worktrees []resolver
229230

230231
// printExecResults prints the formatted output for each execution result.
231232
func printExecResults(cmd *cobra.Command, p *termcolor.Painter, results []executor.Result, prefixes []string) {
233+
out := cmd.OutOrStdout()
232234
for _, r := range results {
233235
_, matchedPrefix := resolver.TaskFromBranch(r.Target.Branch, prefixes)
234236
typeName := strings.TrimSuffix(matchedPrefix, "/")
@@ -238,32 +240,38 @@ func printExecResults(cmd *cobra.Command, p *termcolor.Painter, results []execut
238240
taskLabel = p.Paint(taskLabel, c)
239241
}
240242

241-
var status string
242-
switch {
243-
case r.Cancelled:
244-
status = p.Paint("cancelled", termcolor.Gray)
245-
case r.Err != nil:
246-
status = p.Paint("error", termcolor.Red)
247-
case r.ExitCode != 0:
248-
status = p.Paint(fmt.Sprintf("exit %d", r.ExitCode), termcolor.Red)
249-
default:
250-
status = p.Paint("ok", termcolor.Green)
251-
}
243+
fmt.Fprintf(out, "%s %s\n", taskLabel, formatExecStatus(r, p))
244+
printIndentedOutput(out, r)
245+
}
246+
}
252247

253-
fmt.Fprintf(cmd.OutOrStdout(), "%s %s\n", taskLabel, status)
248+
// formatExecStatus returns the colored status string for an execution result.
249+
func formatExecStatus(r executor.Result, p *termcolor.Painter) string {
250+
switch {
251+
case r.Cancelled:
252+
return p.Paint("cancelled", termcolor.Gray)
253+
case r.Err != nil:
254+
return p.Paint("error", termcolor.Red)
255+
case r.ExitCode != 0:
256+
return p.Paint(fmt.Sprintf("exit %d", r.ExitCode), termcolor.Red)
257+
default:
258+
return p.Paint("ok", termcolor.Green)
259+
}
260+
}
254261

255-
if out := strings.TrimRight(string(r.Stdout), "\n"); out != "" {
256-
for line := range strings.SplitSeq(out, "\n") {
257-
fmt.Fprintf(cmd.OutOrStdout(), " %s\n", line)
258-
}
262+
// printIndentedOutput prints stdout/stderr with indentation for a result.
263+
func printIndentedOutput(out io.Writer, r executor.Result) {
264+
if s := strings.TrimRight(string(r.Stdout), "\n"); s != "" {
265+
for line := range strings.SplitSeq(s, "\n") {
266+
fmt.Fprintf(out, " %s\n", line)
259267
}
268+
}
260269

261-
if r.Err != nil {
262-
fmt.Fprintf(cmd.OutOrStdout(), " %s\n", r.Err)
263-
} else if errOut := strings.TrimRight(string(r.Stderr), "\n"); errOut != "" {
264-
for line := range strings.SplitSeq(errOut, "\n") {
265-
fmt.Fprintf(cmd.OutOrStdout(), " %s\n", line)
266-
}
270+
if r.Err != nil {
271+
fmt.Fprintf(out, " %s\n", r.Err)
272+
} else if s := strings.TrimRight(string(r.Stderr), "\n"); s != "" {
273+
for line := range strings.SplitSeq(s, "\n") {
274+
fmt.Fprintf(out, " %s\n", line)
267275
}
268276
}
269277
}

cmd/status.go

Lines changed: 49 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -138,29 +138,15 @@ func collectStatuses(r git.Runner, candidates []git.WorktreeEntry, s *spinner.Sp
138138

139139
// renderStatusDashboard prints the summary header and per-worktree table.
140140
func renderStatusDashboard(out io.Writer, p *termcolor.Painter, results []statusEntry, staleDays int) {
141-
var totalCount, dirtyCount, staleCount, behindCount int
142-
totalCount = len(results)
143141
staleThreshold := time.Now().Add(-time.Duration(staleDays) * 24 * time.Hour)
144-
145-
for _, r := range results {
146-
if r.status.Dirty {
147-
dirtyCount++
148-
}
149-
if r.status.Behind > 0 {
150-
behindCount++
151-
}
152-
if r.hasTime && r.commitTime.Before(staleThreshold) {
153-
staleCount++
154-
}
155-
}
156-
142+
summary := buildCLIStatusSummary(results, staleThreshold)
157143
prefixes := resolver.AllPrefixes()
158144

159145
fmt.Fprintf(out, "Worktrees: %s Dirty: %s Stale: %s Behind: %s\n\n",
160-
p.Paint(strconv.Itoa(totalCount), termcolor.Bold),
161-
colorCount(p, dirtyCount, termcolor.Yellow),
162-
colorCount(p, staleCount, termcolor.Red),
163-
colorCount(p, behindCount, termcolor.Red),
146+
p.Paint(strconv.Itoa(summary.total), termcolor.Bold),
147+
colorCount(p, summary.dirty, termcolor.Yellow),
148+
colorCount(p, summary.stale, termcolor.Red),
149+
colorCount(p, summary.behind, termcolor.Red),
164150
)
165151

166152
tbl := termcolor.NewTable(2)
@@ -173,33 +159,57 @@ func renderStatusDashboard(out io.Writer, p *termcolor.Painter, results []status
173159
)
174160

175161
for _, r := range results {
176-
task, matchedPrefix := resolver.TaskFromBranch(r.entry.Branch, prefixes)
177-
typeName := strings.TrimSuffix(matchedPrefix, "/")
162+
tbl.AddRow(buildStatusRow(r, prefixes, staleThreshold, p)...)
163+
}
178164

179-
taskCell := " " + task
180-
typeCell := typeName
181-
if c := typeColor(typeName); c != "" {
182-
typeCell = p.Paint(typeCell, c)
183-
}
165+
tbl.Render(out)
166+
}
184167

185-
statusCell := colorStatus(p, r.status)
168+
type cliStatusSummary struct {
169+
total, dirty, stale, behind int
170+
}
186171

187-
var ageCell string
188-
if r.hasTime {
189-
ageStr := resolver.FormatAge(r.commitTime)
190-
if r.commitTime.Before(staleThreshold) {
191-
ageCell = p.Paint(ageStr, termcolor.Red) + " " + p.Paint("⚠ stale", termcolor.Red)
192-
} else {
193-
ageCell = p.Paint(ageStr, resolver.AgeColor(r.commitTime))
194-
}
195-
} else {
196-
ageCell = p.Paint("unknown", termcolor.Gray)
172+
// buildCLIStatusSummary counts summary stats from results.
173+
func buildCLIStatusSummary(results []statusEntry, staleThreshold time.Time) cliStatusSummary {
174+
s := cliStatusSummary{total: len(results)}
175+
for _, r := range results {
176+
if r.status.Dirty {
177+
s.dirty++
197178
}
179+
if r.status.Behind > 0 {
180+
s.behind++
181+
}
182+
if r.hasTime && r.commitTime.Before(staleThreshold) {
183+
s.stale++
184+
}
185+
}
186+
return s
187+
}
198188

199-
tbl.AddRow(taskCell, typeCell, r.entry.Branch, statusCell, ageCell)
189+
// buildStatusRow formats a single worktree row for the status table.
190+
func buildStatusRow(r statusEntry, prefixes []string, staleThreshold time.Time, p *termcolor.Painter) []string {
191+
task, matchedPrefix := resolver.TaskFromBranch(r.entry.Branch, prefixes)
192+
typeName := strings.TrimSuffix(matchedPrefix, "/")
193+
194+
taskCell := " " + task
195+
typeCell := typeName
196+
if c := typeColor(typeName); c != "" {
197+
typeCell = p.Paint(typeCell, c)
200198
}
201199

202-
tbl.Render(out)
200+
return []string{taskCell, typeCell, r.entry.Branch, colorStatus(p, r.status), formatAgeCell(r, staleThreshold, p)}
201+
}
202+
203+
// formatAgeCell formats the age cell with color and stale indicator.
204+
func formatAgeCell(r statusEntry, staleThreshold time.Time, p *termcolor.Painter) string {
205+
if !r.hasTime {
206+
return p.Paint("unknown", termcolor.Gray)
207+
}
208+
ageStr := resolver.FormatAge(r.commitTime)
209+
if r.commitTime.Before(staleThreshold) {
210+
return p.Paint(ageStr, termcolor.Red) + " " + p.Paint("⚠ stale", termcolor.Red)
211+
}
212+
return p.Paint(ageStr, resolver.AgeColor(r.commitTime))
203213
}
204214

205215
// writeStatusJSON builds the JSON output for the status command.

internal/conflict/planner.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,17 @@ func PlanMergeOrder(overlaps []FileOverlap, branches []string) []MergeStep {
1818
return nil
1919
}
2020

21-
// Build branch index
2221
idx := make(map[string]int, len(branches))
2322
for i, b := range branches {
2423
idx[b] = i
2524
}
2625

27-
// Build NxN conflict matrix
28-
n := len(branches)
26+
matrix := buildConflictMatrix(overlaps, idx, len(branches))
27+
return selectMergeOrder(matrix, branches)
28+
}
29+
30+
// buildConflictMatrix builds an NxN matrix counting file overlaps between branch pairs.
31+
func buildConflictMatrix(overlaps []FileOverlap, idx map[string]int, n int) [][]int {
2932
matrix := make([][]int, n)
3033
for i := range n {
3134
matrix[i] = make([]int, n)
@@ -43,8 +46,12 @@ func PlanMergeOrder(overlaps []FileOverlap, branches []string) []MergeStep {
4346
}
4447
}
4548
}
49+
return matrix
50+
}
4651

47-
// Greedily pick branch with fewest total conflicts against remaining
52+
// selectMergeOrder greedily picks branches with fewest conflicts against remaining branches.
53+
func selectMergeOrder(matrix [][]int, branches []string) []MergeStep {
54+
n := len(branches)
4855
remaining := make([]int, n)
4956
for i := range n {
5057
remaining[i] = i

internal/deps/manager.go

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -114,26 +114,8 @@ func (m *Manager) installModule(worktreePath string, mh ModuleWithHash, existing
114114
return InstallResult{Module: mod}
115115
}
116116

117-
for _, wtPath := range existingPaths {
118-
otherHash, err := HashLockfile(wtPath, mod.Lockfile)
119-
if err != nil || otherHash != mh.Hash {
120-
continue
121-
}
122-
123-
modDir := filepath.Join(wtPath, mod.Dir)
124-
if info, err := os.Stat(modDir); err != nil || !info.IsDir() {
125-
continue
126-
}
127-
128-
if err := CloneModule(wtPath, worktreePath, mod); err != nil {
129-
if !mod.CloneOnly {
130-
installErr := runInstall(worktreePath, mod)
131-
return InstallResult{Module: mod, Error: installErr}
132-
}
133-
return InstallResult{Module: mod, Error: fmt.Errorf("clone from %s: %w", wtPath, err)}
134-
}
135-
136-
return InstallResult{Module: mod, Source: wtPath, Cloned: true}
117+
if result, ok := tryCloneFromExisting(worktreePath, mh, existingPaths); ok {
118+
return result
137119
}
138120

139121
if mod.CloneOnly {
@@ -173,6 +155,33 @@ func ResolveModules(worktreePath string, autoDetect bool, configModules []config
173155
return modules, nil
174156
}
175157

158+
// tryCloneFromExisting attempts to clone the module from an existing worktree with matching lockfile.
159+
func tryCloneFromExisting(worktreePath string, mh ModuleWithHash, existingPaths []string) (InstallResult, bool) {
160+
mod := mh.Module
161+
for _, wtPath := range existingPaths {
162+
otherHash, err := HashLockfile(wtPath, mod.Lockfile)
163+
if err != nil || otherHash != mh.Hash {
164+
continue
165+
}
166+
167+
modDir := filepath.Join(wtPath, mod.Dir)
168+
if info, err := os.Stat(modDir); err != nil || !info.IsDir() {
169+
continue
170+
}
171+
172+
if err := CloneModule(wtPath, worktreePath, mod); err != nil {
173+
if !mod.CloneOnly {
174+
installErr := runInstall(worktreePath, mod)
175+
return InstallResult{Module: mod, Error: installErr}, true
176+
}
177+
return InstallResult{Module: mod, Error: fmt.Errorf("clone from %s: %w", wtPath, err)}, true
178+
}
179+
180+
return InstallResult{Module: mod, Source: wtPath, Cloned: true}, true
181+
}
182+
return InstallResult{}, false
183+
}
184+
176185
func runInstall(worktreePath string, mod Module) error {
177186
dir := worktreePath
178187
if mod.WorkDir != "" {

0 commit comments

Comments
 (0)