fix: exclude Docker nested bind-mount points from git clean during source sync#46
Conversation
WalkthroughThis PR computes per-source git-clean exclude patterns based on nested Docker bind-mount topology and threads them through the sync pipeline. ChangesNested Mount Exclude Support
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Update as syncSources
participant Proj as Project
participant Git as git.Service
participant CLI as git CLI
Update->>Proj: SourceCleanExcludes()
Proj-->>Update: map[source][]excludePaths
loop per source
Update->>Git: New(repoDir, excludes[name]...)
Git-->>Update: Service (excludes stored)
Update->>Git: Sync/reset
Git->>CLI: git -C targetPath clean <flags> -e pattern...
CLI-->>Git: clean result
end
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/project/mounts_exclude.go`:
- Around line 44-80: The nested bind handling in mounts_exclude.go is
incorrectly treating file bind mounts under a source mount as clean excludes,
which can preserve mounted files during reset/clean. Update the logic around the
bind scan in the loop over binds so it skips child mounts that are file mounts
before computing parent/child relationships and appending to result. Use the
existing sourceSegment, bindVol, and the parent/child target matching logic to
ensure only directory-backed source mounts contribute excludes, not nested file
binds like config.yaml.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 6876022e-26e9-4237-ae05-e85f30525309
⛔ Files ignored due to path filters (4)
internal/git/git_test.gois excluded by!**/*_test.gointernal/project/mounts_exclude_test.gois excluded by!**/*_test.gotests/e2e/e2e_test.gois excluded by!**/*_test.gotests/e2e/fixtures/manifest-nested/docker-compose.ymlis excluded by!tests/e2e/fixtures/**
📒 Files selected for processing (3)
cmd/devbox/update.gointernal/git/git.gointernal/project/mounts_exclude.go
29ee675 to
ec0c9c7
Compare
Why
devbox update(anddevbox up) fail while the project is already running. Source sync runsgit reset --hard+git cleanin each source checkout, andgit cleantries tormdira directory that Docker is actively using as a nested bind-mount point — which returnsPermission denied:The failure cascades: the first source to fail cancels the shared context, so other in-flight sources abort too. Today the only workaround is
down→update→up.The trigger is a compose service with two bind volumes where one target is nested under the other, e.g.
./sources/service-a/cmd/service-a → /appand./sources/shared → /app/shared. Because/appis the host source dir, Docker materializes the mountpointcmd/service-a/sharedon the host and mounts a different source there. While the stack is up, that host dir is a live mountgit cleancannot remove.Note this is not the
-fdxchange from #40: the mountpoint is untracked and not gitignored, so plaingit clean -fdhits it too. Dropping-xwould not help — a path-based exclude is the only thing that works.What
Compute the set of nested mountpoint paths from the compose volume topology and pass them to
git cleanas-e <path>excludes, per source.The discriminator is deliberately narrow: exclude a nested bind mount only when its own host source is a different source under
sources/than the enclosing parent mount's source. That isolates the one genuine case and leaves the lookalikes alone:cmd/<name>subdir, etc.) materialize as tracked dirs of the same source —git cleanneverrmdirs those, and excluding them would wrongly stop it from cleaning cruft inside.envs/<svc>/.env → /app/.env) materialize as file mountpoints, whichgit cleanremoves fine.With no excludes the
git cleaninvocation is byte-for-byte unchanged, so nothing else is weakened.git.Newbecomes variadic to carry the excludes, which keeps thegit.Serviceinterface (and its generated mocks) untouched.Testing
git clean … -e …args for both-fdand-fdx.gittest proving the computed exclude string actually anchors: the mountpoint survivesclean -fdx -e <path>while sibling untracked cruft is removed.updatewhile it's up. Verified it fails without this fix (Permission denied, exit 1) and passes with it; the full e2e suite stays green.Out of scope
Whether
updateshould rungit reset --hard+cleanon sources that are actively mounted into a running stack at all (it currently wipes uncommitted changes there). Also, a non-sources/bind (e.g. a locally-mounted source) nested as a directory into a checkout would still hit the samermdirfailure but isn't excluded — distinguishing that purely would need a runtimestat. Neither pattern is the crash being fixed here.