Add sync subcommand - #58
Open
leogdion wants to merge 1 commit into
Open
Conversation
`git trees sync [worktree] [--pull] [--ff-only|--rebase]` brings the
container up to date with origin. The default is fetch only, so it is
non-destructive and needs no --apply gate; --pull additionally updates
the working trees.
One `git fetch --prune origin` runs for the whole container rather than
one per worktree: they share a single object store, so a per-worktree
fetch transfers nothing after the first and costs only round-trips. That
is also why the pull loop uses `git merge --ff-only @{upstream}` and
`git rebase @{upstream}` instead of `git pull`, which would re-fetch
every time.
--ff-only is the default strategy — the only update that can neither
discard work nor stop half-finished. A strategy without --pull is an
error rather than a silent no-op.
Under --pull a worktree is skipped when it has a detached HEAD (reported
but not a failure, since detaching is deliberate), no upstream,
uncommitted changes, or has diverged under --ff-only. A rebase conflict
leaves the worktree mid-rebase rather than auto-aborting.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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 |
This was referenced Aug 21, 2026
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.
Implements
git trees sync [worktree] [--pull] [--ff-only|--rebase].Closes #50
Behavior
With no positional argument it covers every worktree; passing one names a single worktree, by branch name or by path.
The default is fetch only. It runs one
git fetch --prune originand stops — nothing in any working tree is touched, so there is no--applygate and the command acts immediately.--pulladditionally updates the working trees from the refs that fetch just brought in.The branch name of each successfully updated worktree goes to stdout, one per line; every notice goes to stderr. The loop always runs to completion, accumulating
failed, so a nonzero exit means partial success rather than a stop.Why
--ff-onlyis the defaultA fast-forward is the only update that can neither discard work nor stop half-finished. A rebase can conflict, and a conflicted rebase leaves the worktree mid-rebase — an unacceptable state for a command whose whole point is touching every worktree in the container unattended.
--rebaseis available when you want it, but you have to ask for it.Passing a strategy without
--pullis an error rather than a silent no-op:sync --rebasethat quietly only fetched would look like it had rebased.One fetch for the whole container
syncruns exactly onegit fetch --prune originup front, not one per worktree. Every worktree in the layout shares a single object store, so a per-worktree fetch transfers nothing after the first and costs only network round-trips — on a container with a dozen worktrees that is a dozen round-trips for one fetch's worth of data.Unlike
clean's fetch, this one is not silenced. With no flags the fetch is the command's entire job, and a silent success would be indistinguishable from a no-op. If the fetch fails,syncreturns 1 without pulling anything.A positional target is resolved before the fetch, so a typo'd name does not fire a network operation.
Why it does not use
git pullgit pullre-fetches on every invocation, which would undo the single-fetch design above — one fetch up front plus N more inside the loop. Instead the loop runsgit merge --ff-only @{upstream}orgit rebase @{upstream}, both of which work against the already-current remote-tracking refs, need no fetch of their own, and are idempotent.Skip conditions under
--pullsync --pullpermanently nonzero for anyone keeping such a worktreegit trees tracknamed as the remedy, counted as a failure--ff-only--rebasenamed as the remedy, counted as a failuregit rebase --abortnamed — deliberately not auto-aborted, which would discard the user's chance to resolve itDirtiness includes untracked files, matching the
dirtycolumn ingit trees listandgit worktree remove's own refusal. Documented in the README, since a stray.DS_Storeis then enough to skip a pull. The check runs only under--pull— fetch-only never touches a work tree.Notes for review
_ref_info:_is_dirtyand_worktree_paths. The latter filters thebarestanza thatgit worktree list --porcelainemits for the container's bare store, which has no work tree at all (git -C <bare> statusexits 128), so any iteration over worktrees must drop it._sync_targetis local to the sync section rather than factored out ofcmd_rm.cmd_rm's resolution hard-codes its own messages and carries a worktree-registration gate that exists specifically to keepTREES_RM_CMD="rm -rf"away from the container root — a concernsyncdoes not have. Extracting it would turn a purely additive change into one that rewrites the most safety-critical function in the file.TREES_*env var; the env surface is unchanged at five.Testing
57 new assertions in a
syncsection placed betweenrmandclean(cleanstays last, since its fixtures mutate the shared$ORIGIN). The sync fixtures also mutate$ORIGINbut commit onfeature-xonly, never onmain— stated in a comment. Every fixture step is asserted, so a silently failed setup cannot leave a branch already up to date and make later assertions pass against nothing.Covered: fetch-only advances
origin/feature-xwhile leaving the worktree HEAD and files untouched;--pullfast-forwards and names the branch on stdout; dirty worktree skipped with the uncommitted file preserved and the upstream change not applied; no-upstream skip; detached HEAD reported but not a failure; divergence under--ff-onlyreported with the local commit preserved;--rebasegets past the divergence keeping both commits; single target by branch name and by path; and the full argument-validation matrix.All network-free, on
file://fixtures undermktemp -d.🤖 Generated with Claude Code
Release coordination (v1.0.3)
One of five PRs into
release-1.0.3(#56 prune, #58 sync, #60 completions, #59 curl install, #57 Homebrew). All five are green on CI (smoke on Linux + macOS).Suggested merge order: #56 → #58 → #60 → #59 → #57. Only one pair conflicts: #58 vs #56.
Needs a rebase on
release-1.0.3after #56 merges. Both PRs append to the same two spots intests/smoke.sh— thehelp lists …assertion block and the new-section insertion point before# --- clean ---. Two trivial adjacent-append conflicts, no semantic overlap;git-treesandREADME.mdmerge cleanly (the two commands were given deliberately opposite insertion anchors there).Note: CodeRabbit skipped all five — "reviews are disabled for this base branch." These have not had automated review; that would come when
release-1.0.3merges tomain.The CHANGELOG is deliberately excluded from every PR — it needs the merged PR URLs, so it lands as one commit on
release-1.0.3before taggingv1.0.3.