Skip to main content

gitq sync

Fetches the stack's remote trunk and rebases every branch in the stack onto its parent's new head.

Usage​

bash
gitq sync [--stack <name>]

Flags​

flagmeaning
--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 with all 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). See maxWorkSlots for the full message and the settings-store precedence.

  • Fetches origin unconditionally, every time, before anything else. If the fetch fails, sync throws and the command exits 1 with Failed to fetch from remote: ... (src/core/rebase-engine.ts:1241,1245-1250). A repo with no origin remote cannot sync, and this is where it normally dies: git fetch origin fails 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, sync fails hard: exit 1, nothing rebased, message cannot 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 local git remote get-url origin (src/core/rebase-engine.ts:873-894):

    $ gitq sync
    gitq: 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 sync
    gitq: 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 rebased

    The first cause names two possibilities because one local check cannot separate them: a git clone --single-branch leaves +refs/heads/main:refs/remotes/origin/main as the whole fetch refspec, so origin/develop stays unresolvable however many times you push develop. Try the git push -u only 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 origin succeeded while no remote is named origin, which happens when git resolves origin as a path to a local repo, or when origin is a remotes.<group> fetch group rather than a remote.

    There is deliberately no fallback to the local root branch. sync never 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 exit 0 with 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, a reset --hard plus a fresh commit, or a hand-run git rebase between 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 stored lastKnownHead is 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 lastKnownHead disagrees 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, ... (or FAILED (<error>) for any branch that failed) and exits 0 if every attempted branch succeeded, 1 if 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 prints completed: 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: sync overrides the default shouldLog (exit 0 only, src/cli/op-log.ts:44) with (code) => code !== 2 (src/cli/commands/cascade.ts:70), so both exit 0 and the completed-with-a-failed-branch exit 1 write an entry, and a pause on exit 2 does not. A hard failure writes no entry at all: the fetch failure and the unresolved origin/<root> throw straight past withOperationLog, which only consults shouldLog on 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 log reports nothing for the repo after a failed sync.

  • Under --json, emits the shared cascade shape all of sync, continue, and abort produce, keyed on state: paused (with pauseInfo) on exit 2, or completed (with results and rebasedBranches) on exit 0/1. A hard refusal, an unresolved origin/<root> among them, prints nothing on stdout in either mode, only gitq: <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 of oks: up-to-date branches are skipped without pushing a result, so stdout is completed: with nothing after the colon, and --json gives an empty walk:

    $ gitq sync
    completed:

    $ 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 exit 0 with an empty stderr, a hard failure is exit 1 with gitq: cannot sync: ... on stderr and nothing at all on stdout.

    What exit 0 does now guarantee is that the fetch ran and origin/<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, --stack unresolved), 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​