gitq sync
Fetches the stack's remote trunk and rebases every branch in the stack onto its parent's new head.
Usage
gitq sync [--stack <name>]
Flags
| flag | meaning |
|---|---|
--stack <name> | Which tracked stack to sync. Optional when the repo has exactly one; required otherwise. See Global flags. |
Behavior
-
Guarded by the stack lease (
requireStackFree,src/cli/slots.ts:19-27): refuses if the stack already has a running or parked cascade. This is the lease, not the pause file; see The pause protocol. -
Leases a work slot for the rebase, creating one if none is free, up to
maxWorkSlots; refuses withall N work slots are busy (max N); finish or abort a cascade, or raise maxWorkSlots -- rt settings explain gitq.workSlots first, then rt settings set gitq.workSlots '{"maxWorkSlots":N,...}' --scope machine ...when the cap is reached and every slot is leased (src/cli/slots.ts:33-56). SeemaxWorkSlotsfor the full message and the settings-store precedence. -
Fetches
originunconditionally, every time, before anything else. If the fetch fails,syncthrows and the command exits1withFailed to fetch from remote: ...(src/core/rebase-engine.ts:1241,1245-1250). A repo with nooriginremote cannot sync, and this is where it normally dies:git fetch originfails before any ref is looked at, so the ordinary remote-less repo never reaches the message below. -
If
origin/<root>does not resolve after that fetch,syncfails hard: exit1, nothing rebased, messagecannot sync: origin/<root> does not resolve after fetching origin (<cause>). nothing was rebased(src/core/rebase-engine.ts:1252-1260). The<cause>names what to go check, the two arms split by one localgit remote get-url origin(src/core/rebase-engine.ts:873-894):$ gitq syncgitq: cannot sync: origin/develop does not resolve after fetching origin ("develop" was never pushed to origin, or this remote's fetch refspec does not cover it; if it was never pushed: git push -u origin develop). nothing was rebased$ gitq syncgitq: cannot sync: origin/main does not resolve after fetching origin (this repo has no remote named "origin"; gitq always syncs onto origin/<root>). nothing was rebasedThe first cause names two possibilities because one local check cannot separate them: a
git clone --single-branchleaves+refs/heads/main:refs/remotes/origin/mainas the whole fetch refspec, soorigin/developstays unresolvable however many times you pushdevelop. Try thegit push -uonly if the branch really is unpushed; otherwise widen the refspec (git remote set-branches --add origin <root>, then fetch).The second cause is the exotic one, not the everyday "I have no remote" case: reaching it means
git fetch originsucceeded while no remote is namedorigin, which happens when git resolvesoriginas a path to a local repo, or whenoriginis aremotes.<group>fetch group rather than a remote.There is deliberately no fallback to the local root branch.
syncnever fetches into your local root, so rebasing the stack onto it would report success while leaving the stack based on whatever your root happened to be, which is exactly the "looks synced, isn't" state the fetch exists to prevent. This case used to return an empty result and exit0with no warning; that silent no-op is gone. -
The local root branch is never modified. Branches whose parent is the stack root rebase directly onto
origin/<root>, the remote-tracking ref, never onto your local branch (src/core/rebase-engine.ts:1236-1238). -
Walks the stack in topological order, parents before children, skipping any node marked
unmanaged, rebasing each branch detached in the leased work slot, and moving its ref only with a compare-and-swap once the replay succeeds. -
A parent rewritten outside gitq is recovered from its recorded head. A
commit --amend, areset --hardplus a fresh commit, or a hand-rungit rebasebetween invocations leaves nothing holding the pre-rewrite head, and a plain merge-base against the rewritten parent resolves below the child's fork point — sweeping the parent's pre-rewrite commits into the child's range, where they replay as duplicates and conflict against the rewrite. The parent's storedlastKnownHeadis the surviving record of where the child forked, so it is used instead, but only when it still exists, the parent has genuinely moved off it (a parent that merely gained commits is the case plain merge-base already gets right), and it still anchors that child's own history (src/core/rebase-engine.ts:400-425). Any of those failing falls back to the merge-base. -
A skipped branch still catches its recorded head up. A branch already sitting on its parent contributes no result, but when its
lastKnownHeaddisagrees with where the branch actually is, the record is corrected (src/core/rebase-engine.ts:572-590). A skip is the one moment a head is known-good without having been touched. Without this, a store keeps pointing at pre-rewrite history indefinitely once a branch is moved by hand, which would leave the recovery above resolving against an anchor that is itself stale. -
On a conflict, the walk stops: the branch is left mid-rebase in the work slot, a pause file is written, the lease is parked, and the command exits
2. -
On completion, prints
completed: <branch> ok, ...(orFAILED (<error>)for any branch that failed) and exits0if every attempted branch succeeded,1if at least one failed. Only branches the walk actually attempted appear: a branch already sitting on its parent's head is skipped and contributes no entry, so a stack with nothing to do printscompleted:and nothing else — though the skip is not inert, it still corrects that branch's recorded head. -
Recorded to the operation log when the walk ran to a return value:
syncoverrides the defaultshouldLog(exit0only,src/cli/op-log.ts:44) with(code) => code !== 2(src/cli/commands/cascade.ts:70), so both exit0and the completed-with-a-failed-branch exit1write an entry, and a pause on exit2does not. A hard failure writes no entry at all: the fetch failure and the unresolvedorigin/<root>throw straight pastwithOperationLog, which only consultsshouldLogon a code the wrapped function returned (src/cli/op-log.ts:39-58), and the refusals ahead of it (lease held, no free work slot) never enter the wrapper. Verified:gitq logreports nothing for the repo after a failedsync. -
Under
--json, emits the shared cascade shape all ofsync,continue, andabortproduce, keyed onstate:paused(withpauseInfo) on exit2, orcompleted(withresultsandrebasedBranches) on exit0/1. A hard refusal, an unresolvedorigin/<root>among them, prints nothing on stdout in either mode, onlygitq: <message>on stderr. See JSON output.
Exit codes
-
0: completed, every attempted branch succeeded. A stack that was already current counts, but it reports nothing, not a row ofoks: up-to-date branches are skipped without pushing a result, so stdout iscompleted:with nothing after the colon, and--jsongives an empty walk:$ gitq synccompleted:$ gitq sync --json{"state": "completed","results": [],"rebasedBranches": []}That is byte-identical to what the old unresolved-
origin/<root>bug used to print, so stdout alone does not tell you a sync succeeded. What separates them is the exit code and stderr: success is exit0with an empty stderr, a hard failure is exit1withgitq: cannot sync: ...on stderr and nothing at all on stdout.What exit
0does now guarantee is that the fetch ran andorigin/<root>resolved: the silent no-op on an unresolvable remote trunk is gone. It is not a promise that every branch in the stack was examined against it. A branch whose base resolution throws is still swallowed by a catch-all that skips it exactly like an up-to-date one (src/core/rebase-engine.ts:430-432), leaving no result and no warning behind. -
1: a hard refusal (lease held, all work slots busy, fetch failed,origin/<root>unresolved,--stackunresolved), or completed with at least one failed branch. -
2: paused on a conflict. See The pause protocol and Pause file.
See Exit codes for the general contract.
See also
- The cascade: the full walk, step by step, including the merged-parent reconciliation case.
- Work slots and leases: where the rebase actually runs, and
maxWorkSlots. - Resolve a conflict: one real run of a
syncthat pauses twice. gitq continue,gitq abort,gitq preflight.