Skip to content

refactor: dismantle the gocognit hotspots, one concern per function - #14060

Merged
glours merged 12 commits into
mainfrom
refactor-wait-dependencies
Aug 18, 2026
Merged

refactor: dismantle the gocognit hotspots, one concern per function#14060
glours merged 12 commits into
mainfrom
refactor-wait-dependencies

Conversation

@ndeloof

@ndeloof ndeloof commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

What I did

Consolidates the whole gocognit-refactoring stack (former #14061#14071, one commit each) into a single PR based on main. Each commit dismantles one cognitive-complexity hotspot into named, single-concern functions, with no behavior change, and removes the corresponding FIXME/nolint:gocognit suppression:

  • waitDependencies: one function per depends_on condition
  • RootCommand.PersistentPreRunE concerns extracted (resolveAnsiMode, applyDisplayMode, normalizeProjectOptions, ...)
  • monitor.Start: one handler per container event, with tests
  • interactive up materialized as an upSession
  • pull scheduling materialized as an imagePuller
  • watch trigger preparation and initial-sync walk flattened
  • ociRemoteLoader.Load pull and index resolution split
  • Ps, restart and Logs orchestration flattened
  • doBuildImage context preparation staged
  • publish push path and sensitive-data scanning split
  • create endpoint and volume translation decisions extracted
  • last gocognit candidates: bridge resources and TestViz

🤖 Generated with Claude Code

@ndeloof
ndeloof requested review from a team as code owners August 17, 2026 07:54
@ndeloof
ndeloof requested review from glours and removed request for a team August 17, 2026 07:54
@glours
glours force-pushed the refactor-wait-dependencies branch from d77d68e to accd1f4 Compare August 18, 2026 08:42
Base automatically changed from fix-bake-target-collision to main August 18, 2026 09:41
ndeloof and others added 4 commits August 18, 2026 11:41
waitDependencies (cognitive complexity 76) inlined the polling loop
and the per-condition logic for all three depends_on conditions, each
with its own optional-dependency degradation. The polling loop moves
to waitDependency, and each condition check becomes a function
reporting (done, err) — (false, nil) means keep polling. The driver
drops to complexity 11 and its FIXME suppression is gone.

No behavior change: same events, same log messages, same errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
RootCommand (cognitive complexity 62) buried four independent
concerns inside the PersistentPreRunE closure, where every branch
costs double. Each moves to a named function: runParentPreRun (cobra
doesn't chain the parent's PersistentPreRunE automatically),
resolveAnsiMode (--ansi vs deprecated --no-ansi vs COMPOSE_ANSI),
applyDisplayMode (ANSI + NO_COLOR + progress mode),
normalizeProjectOptions (--workdir deprecation, env-file paths) and
resolveMaxConcurrency (COMPOSE_PARALLEL_LIMIT vs --parallel). The
prerun now reads as the sequence of those steps; RootCommand drops to
complexity 22 and its FIXME suppression is gone.

No behavior change: same errors, same deprecation warnings, same
precedence rules.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
monitor.Start (cognitive complexity 77) inlined the handling of all
four container actions in the event loop. Each action moves to an
onContainerX handler, with the recurring bits named: watched() (is
this service's container ours to track), notify() (broadcast to
listeners), initialContainers() (seed the tracking set). The loop
drops to complexity 19 and reads as: seed, subscribe, dispatch until
no containers remain. The tracking sets stay explicit parameters so
data flow remains visible.

The monitor had no unit test despite driving up/logs termination; the
refactor is locked by a new suite covering the full lifecycle
(created/recreated/started/restarted/exited, default-name trimming,
exit codes), the restart detection through both engine states
(Restarting, and Running per moby/moby#45538), the already-removed
container on die, service filtering, the no-container fast path,
events stream errors, context cancellation, and exit-code parse
errors.

No behavior change: same events, same ordering, same termination
conditions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
Up (cognitive complexity 79) interleaved service creation with an
implicit session object: an errgroup, an error collector, exit status
and termination flag, all shared between six closures. That session is
now an explicit upSession type; each concern becomes a method:
runEventLoop (signals/keyboard/cancellation), stopApplication and
killApplication (the first/second-interrupt actions), stopOnFirstExit
(--abort-on-container-exit cascade), captureExitCodeFrom
(--exit-code-from), followStartedContainers + streamContainerLogs
(attach to (re)started containers). Keyboard menu setup moves to
setupNavigationMenu, closing the keyboard on the desktop-detection
error path exactly where Up's deferred Close used to.

Also fixes a latent data race: the graceful-stop and cascade-stop
goroutines assigned their error to Up's outer err variable (racing
with the main goroutine) before handing it to the collector; the
assignment was redundant and is now local.

No behavior change otherwise: same listeners in the same order, same
cancellation points, same error report.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
@ndeloof
ndeloof force-pushed the refactor-wait-dependencies branch from accd1f4 to cc5e408 Compare August 18, 2026 09:41
ndeloof and others added 8 commits August 18, 2026 11:41
pull (cognitive complexity 56) interleaved two scheduling loops
(service images, pre_start hook images) sharing dedup state, failure
slots and the must-build fallback list. That state becomes an explicit
imagePuller; the loops become pullServiceImages and pullHookImages,
the per-service goroutine body becomes runServicePull, and the four
copies of the Skipped event literal collapse into eventSkippedPull.

Also fixes a latent data race: mustBuild was appended from concurrent
pull goroutines without synchronization; it is now guarded by a mutex.

No behavior change otherwise: same skip events, same fail-fast rules,
same error report.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
watch (cognitive complexity 52) inlined three passes over each
service's triggers; they become prepareRebuildTriggers (validation +
always-build marking), watchTriggerPaths (paths to monitor, bind-mount
skips, initial sync) and initialSyncRequested (the DEPRECATED
x-initialSync fallback). initialSyncFiles (34) delegates its WalkDir
closure to initialSyncDirectory, keeping the Info() call order so
error behavior on unreadable entries is unchanged.

No behavior change: same validations, same warnings, same collected
paths and sync mappings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
Load (cognitive complexity 48) mixed the enable/offline guards with
the artifact pull, the cache check and the image-index indirection.
The pull path moves to pullComposeArtifact and the index-to-manifest
resolution to resolveComposeManifest. Load reads as: guards, cache
lookup, pull on miss.

No behavior change, including the last-matching-manifest-wins loop
and the silent cache hit on a non-NotExist stat error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
Three command paths with the same shape — a setup phase then per-
container goroutines with inlined bodies:

- Ps (44): the goroutine body becomes containerSummary, with pure
  helpers for publishers, health/exit-code, mounts and networks.
- restart (42): project resolution moves to prepareRestartProject,
  the per-container body to restartContainer. Also fixes a latent
  data race: hooks and restart assigned their error to the function's
  outer err from concurrent goroutines; now local.
- Logs (31): container selection moves to selectLogsContainers, the
  streaming body to logContainer, and the follow-mode listener to
  followStartedContainersLogs.

No behavior change: same events, same warnings, same results.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
doBuildImage (cognitive complexity 43) mixed builder-capability
guards, context-type resolution (with in-scope defers), tar archiving
and credential conversion. Each becomes a function; the context
resolution returns a classicBuildContext carrying a cleanup so the
defers run at the same point they used to.

No behavior change: same errors, same context handling.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
publish (cognitive complexity 34) moves its non-dry-run push path to
pushComposeArtifact, with the application image index construction in
pushApplicationIndex. checkForSensitiveData (34) splits per source
kind: scanEnvFiles (per-service env files with the required/missing
rules) and scanFiles (file-backed configs and secrets, deduplicating
two identical loops).

No behavior change: same events, same errors, same findings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
createEndpointSettings (cognitive complexity 31) moves address parsing
to parseEndpointIPAM and the interface_name/driver_opts merge to
endpointDriverOpts. buildContainerVolumes (34) gets one decision
helper per mount type: bindStringForMount, volumeBindString,
checkImageMountSupported. A dead branch in the bind case (source was
reassigned to the same value) is simplified away — the surrounding
behavior is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
LoadAdditionalResources (cognitive complexity 31) moves its
per-service body to loadServiceImageResources, with the local-vs-pull
inspection decision in inspectServiceImage. TestViz (35) factors the
shared graph assertions into assertVizGraphNodes and
assertVizDependencyEdges (t.Helper), merging the allowed/forbidden
edge bookkeeping into one equivalent loop.

This removes the last two of the 19 //nolint:gocognit suppressions
introduced by the gocyclo->gocognit migration: the FIXME debt is fully
paid, the linter now runs suppression-free.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
glours
glours previously approved these changes Aug 18, 2026
@ndeloof ndeloof changed the title refactor: one function per depends_on condition in waitDependencies refactor: dismantle the gocognit hotspots, one concern per function Aug 18, 2026
@glours
glours merged commit 02bc297 into main Aug 18, 2026
186 of 209 checks passed
@glours
glours deleted the refactor-wait-dependencies branch August 18, 2026 11:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants