Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

gg sync

Push entry branches and create/update PRs/MRs for the current stack.

gg sync [OPTIONS]

Options

  • -d, --draft: Create new PRs/MRs as draft (does not affect existing PRs/MRs)
  • -f, --force: Force push even if remote is ahead
  • --update-descriptions: Update PR/MR descriptions from commit messages
  • --update-title: Update PR/MR titles from commit messages
  • -l, --lint: Run lint before sync (aborts sync on lint failure and restores repository state to the pre-sync snapshot)
  • --no-lint: Disable lint before sync (overrides config default)
  • --no-rebase-check: Skip checking whether your stack base is behind origin/<base>
  • --no-verify: Skip the pre-push hook for pushes performed by this sync (forwards git push --no-verify). Opt-in per invocation; does not affect other hooks.
  • -u, --until <UNTIL>: Sync up to target commit (position, GG-ID, or SHA)
  • --json: Output structured JSON for automation (suppresses human/progress output)
  • --jsonl: Output streaming NDJSON for automation (one JSON event per line, flushed after each; see Streaming Events below)

Before pushing, gg sync checks whether your stack base is behind origin/<base>. If it is behind by at least the configured threshold, git-gud warns and suggests rebasing first (gg rebase).

When you run gg sync --lint, lint runs before any push/PR updates. If lint fails, sync aborts immediately and git-gud restores your repository to the pre-sync snapshot.

Before pushing, gg sync also normalizes commit metadata (GG-ID and GG-Parent) for the whole stack. This normalization is always enforced during sync (including adding missing GG-ID trailers) to keep stack identity and PR/MR mappings stable.

If the current stack branch has a valid stack shape but uses a different prefix than defaults.branch_username, gg sync continues and warns that stack discovery, listing, and saved PR/MR mappings may be inaccurate. In --json mode, this message is included in sync.warnings.

If an existing mapped PR/MR is attached to the wrong source branch (for example after moving commits into a new stack with gg unstack), providers cannot retarget that source branch in place. gg sync creates a replacement PR/MR with the correct branch, updates the local mapping, comments on the old PR/MR, and closes it. In JSON output that entry uses action "recreated".

You can control this behavior with config:

  • defaults.sync_auto_rebase (sync.auto_rebase): automatically run gg rebase before sync when behind threshold is reached
  • defaults.sync_behind_threshold (sync.behind_threshold): minimum number of commits behind before warning/rebase logic applies (0 disables the check)

Examples

# First publish as drafts
gg sync --draft

# Sync only first two entries
gg sync --until 2

# Refresh PR/MR descriptions after commit message edits
gg sync --update-descriptions

# Also update PR/MR titles to match commit subjects
gg sync --update-title

# Run lint as part of sync
gg sync --lint

# Skip behind-base check once
gg sync --no-rebase-check

# Machine-readable output
# (useful in scripts/agents)
gg sync --json

# Skip pre-push hooks for this sync only
gg sync --no-verify

Target Branch Resolution

When computing the target branch for each PR/MR, gg sync walks backwards through predecessor entries and skips any that are already merged or closed. If all predecessors have been merged, the target falls back to stack.base. This ensures downstream MRs are correctly retargeted after an upstream MR is merged — whether merged via gg land or directly in the provider UI.

PR/MR Body Ownership

When gg sync creates a new PR/MR, the generated description is wrapped in invisible HTML comment markers:

<!-- gg:managed:start -->
(generated content from commit message / template)
<!-- gg:managed:end -->

On subsequent syncs with --update-descriptions (or when sync_update_descriptions is enabled in config), only the content inside the managed block is regenerated. Any text you add above or below the markers on GitHub/GitLab is preserved across syncs.

This means you can safely:

  • Add review checklists above or below the managed block
  • Write reviewer notes that survive re-syncs
  • Check/uncheck task boxes outside the managed section

Content inside the managed block (the generated description) is regenerated on every sync. If your PR template includes a checklist, place persistent checklists outside the markers after creation.

Legacy PRs (created before this feature) have no managed markers. gg sync will skip body updates for these PRs and log a warning, to avoid overwriting manual edits.

Stack navigation comments

If defaults.stack_nav_comments is enabled in .git/gg/config.json, every full gg sync (no --until) reconciles a managed comment on each PR/MR in the stack. The comment shows all entries in the stack in bottom-up order, with a 👉 marker on the entry that PR corresponds to — letting reviewers see where they are in the chain and click through to siblings.

The comment is identified by a hidden HTML marker (<!-- gg:stack-nav -->) and never touches comments git-gud didn’t create. Disabling the setting and re-syncing cleans up any previously-posted comments automatically.

Merged or closed PRs are left alone — gg sync never modifies comments on historical PRs.

When running with --json or --jsonl, each entry includes an optional nav_comment_action field (one of "created", "updated", "unchanged", "deleted", "error") when a reconcile decision was made.

Streaming NDJSON (--jsonl)

gg sync --jsonl emits one compact JSON object per line on stdout and flushes after each line. Human/progress output is suppressed on stdout but diagnostics may still appear on stderr. This mode is intended for agents and long-running pipelines that want incremental updates instead of waiting for a final aggregate payload.

Every line is a valid JSON object with at least:

  • version: output schema version (1)
  • command: always "sync"
  • event: event kind (see below)

Event kinds:

EventFieldsEmitted when
startstack, base, total_entriesSync begins
entry_startedposition, sha, title, gg_id, branchProcessing begins for a stack entry
push_startedposition, branchA push is about to start
push_doneposition, branch, forcedPush succeeded
push_errorposition, branch, errorPush failed (entry is skipped)
pr_createdposition, pr_number, pr_url, draftNew PR/MR created
pr_updatedposition, pr_number, actionExisting PR/MR updated (updated/recreated)
pr_skipped_closedposition, pr_numberExisting PR/MR is merged/closed and skipped
nav_commentposition, pr_number, action, errorManaged nav comment reconciled (created/updated/unchanged/deleted/error/skip)
errormessageFatal error before completion
summarysame shape as --json sync objectSync finished (success or partial failure)

The last event is always summary, so consumers can detect completion without reconstructing state. If an unrecoverable error happens before the summary, an error event is emitted and the process exits non-zero.

Example --jsonl output

{"version":1,"command":"sync","event":"start","stack":"my-stack","base":"main","total_entries":2}
{"version":1,"command":"sync","event":"entry_started","position":1,"sha":"abc1234","title":"Add feature","gg_id":"c-abc1234","branch":"user/my-stack--c-abc1234"}
{"version":1,"command":"sync","event":"push_started","position":1,"branch":"user/my-stack--c-abc1234"}
{"version":1,"command":"sync","event":"push_done","position":1,"branch":"user/my-stack--c-abc1234","forced":false}
{"version":1,"command":"sync","event":"pr_created","position":1,"pr_number":42,"pr_url":"https://github.com/org/repo/pull/42","draft":false}
{"version":1,"command":"sync","event":"summary","stack":"my-stack","base":"main","rebased_before_sync":false,"warnings":[],"metadata":{"gg_ids_added":0,"gg_parents_updated":0,"gg_parents_removed":0},"entries":[{"position":1,"sha":"abc1234","title":"Add feature","gg_id":"c-abc1234","branch":"user/my-stack--c-abc1234","action":"created","pr_number":42,"pr_url":"https://github.com/org/repo/pull/42","draft":false,"pushed":true,"error":null}]}

When to use --json vs --jsonl

  • Use --json when you want a single, complete aggregate payload at the end and do not need progress while the command runs.
  • Use --jsonl when you want to stream events as they happen, monitor progress, or pipe the output to a line-oriented consumer. Every line is self-contained.

Example JSON (shape):

{
  "version": 1,
  "sync": {
    "stack": "my-stack",
    "base": "main",
    "rebased_before_sync": false,
    "metadata": {
      "gg_ids_added": 0,
      "gg_parents_updated": 1,
      "gg_parents_removed": 0
    },
    "entries": [
      {
        "position": 1,
        "sha": "abc1234",
        "title": "Add feature",
        "gg_id": "c-abc1234",
        "branch": "user/my-stack--c-abc1234",
        "action": "created",
        "pr_number": 42,
        "pr_url": "https://github.com/org/repo/pull/42",
        "draft": false,
        "pushed": true,
        "error": null
      }
    ]
  }
}