feat(worktree): develop against a fork and its upstream from one clone - #69
Merged
Conversation
Fork maintenance regularly needs both sides at once: reading upstream Django to see what changed, and editing the fork branch to adapt. A second clone means a second full fetch of Django's history and two object stores to keep current. Add git worktree support so one clone can have both checked out, sharing a single object store: base_dir/django/django/ # fork clone, on mongodb-6.0.x base_dir/django/django-upstream/ # worktree, on upstream-main - New `dbx worktree add|list|remove`. - New `upstream_worktree` group config key, honoured by `dbx clone` right after the preferred-branch switch. Enabled for django. - `find_all_repos` now tags each entry `worktree` (a linked worktree has .git as a file, so it was already being picked up as an ordinary repo by the existing .exists() check). The worktree's branch is named `upstream-<default branch>` rather than the bare branch name, so it cannot collide with a same-named branch already tracking origin in the fork. Read-only commands (status, log, branch) include worktrees; commands that write to a checkout skip them: - sync: rebasing a worktree onto upstream and force-pushing to origin would push upstream's history into the fork. Guarded in _sync_repository, the worker every sync path routes through, so an explicitly named worktree is refused too. - switch -g: a branch can only be checked out in one worktree at a time. Only group enumeration is filtered; `dbx switch <worktree> <branch>` stays allowed, since it is reversible and git refuses on its own if the branch is checked out elsewhere. - install -g: a worktree shares its clone's package name and would replace the clone's editable install in the group venv. `dbx remove` removes worktrees before the clone they belong to and goes through `git worktree remove`, so no stale .git/worktrees entries.
CI runners have no global git user.name/user.email, so the fixture's
seed commit failed with exit 128 ("Author identity unknown") and errored
all 19 tests that build a real repository. Supply the identity through
GIT_AUTHOR_*/GIT_COMMITTER_* in the environment, which keeps it scoped
to these tests instead of writing to the developer's global config, and
force commit.gpgsign=false so a developer's global signing config cannot
prompt or fail here either.
Verified by running the suite with HOME, GIT_CONFIG_GLOBAL and
GIT_CONFIG_SYSTEM stripped, which reproduces the CI failure without the
fix and passes with it.
The CI runners set init.defaultBranch=master. The fixture created the bare origin with the machine default and then pushed HEAD:main, leaving the bare repo's HEAD pointing at an unborn `master` while the only real branch was `main`. upstream/HEAD was therefore unresolvable and get_remote_head_branch returned None, failing 8 tests. Pass --initial-branch=main so the branch name is pinned rather than inherited. Reproduced locally with a global config setting init.defaultBranch=master: 8 failures without this change (the same 8 CI reported), 24 passing with it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
dbx clone djangogives you one checkout ofmongodb-forks/djangowith anupstreamremote. Fork maintenance regularly needs both sides at once — reading upstream Django to see what changed, and editing the fork branch to adapt. A second clone means a second full fetch of Django's history and two object stores to keep current.Solution
Git worktree support, so one clone has both checked out sharing a single object store:
Comparisons are then local and immediate:
git log upstream-main..mongodb-6.0.x.dbx worktree add|list|remove— manages worktrees for any repo.upstream_worktreegroup config key —dbx clonecreates the worktree automatically. Enabled fordjango.find_all_reposnow tags each entryworktree. Worth noting: a linked worktree has.gitas a file, so the existing.exists()check was already picking them up as ordinary repos.The worktree's branch is
upstream-<default branch>, not the bare branch name, so it can't collide with a same-named branch already trackingoriginin the fork.Which commands see worktrees
Read-only commands include them; commands that write to a checkout skip them.
status,log,branchsyncoriginwould push upstream's history into the forkswitch -g <group>install -g <group>syncis guarded in_sync_repository, the worker all four sync paths route through, so an explicitly named worktree is refused too — the force-push isn't recoverable.switchfilters group enumeration only;dbx switch <worktree> <branch>stays allowed, since it's reversible and git refuses on its own if the branch is checked out elsewhere.dbx removeremoves worktrees before their clone and goes throughgit worktree remove, so no stale.git/worktreesentries.Testing
467 tests pass (24 new). The worktree tests drive real git repositories rather than mocking subprocess, since the behaviour depends on git's own bookkeeping —
.gitas a file, worktree registrations, refusing a branch checked out twice. One test assertssubprocess.runis never called for a worktree insync, so the guard can't regress into "runs git, then bails".Docs updated in
features/repo-management.rst,features/django-fork.rst,design/command-structure.rst, and the API pages; sphinx builds clean.