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.

GitHub Stacked PRs (public preview)

For a full GitHub gg sync (without --until), git-gud collects the current stack’s active open and draft PR numbers in bottom-to-top order. When there are at least two resolved PRs and defaults.github.stacks_integration is not off, it can reconcile GitHub’s native Stacked PRs map through the optional official gh stack extension. This does not replace or change git-gud’s local stack metadata, and GitLab sync never uses this integration.

Install the extension yourself if you want the native UI:

gh extension install github/gh-stack

git-gud never installs or upgrades it automatically. The minimum supported extension version is 0.1.0.

Eligibility and safe outcomes

The integration is skipped when it is disabled, the sync is partial, fewer than two active PRs are available, or an active PR cannot be resolved. Full syncs create GitHub Stacked PRs when none of the active PRs is already associated, and append only new top PRs when the native active sequence is an exact ordered prefix of git-gud’s sequence. Appending by stack number preserves a merged prefix; git-gud does not relink merged PRs or retarget a PR onto a merged branch.

If the native and local sequences diverge, git-gud performs no mutation and returns a warning. Inspect the GitHub Stacked PRs map, explicitly unstack or repair it with GitHub’s tools, then rerun gg sync. Version one never removes, reorders, unlinks, or recreates GitHub Stacked PRs.

auto silently records a skipped result when the extension is missing or outdated, or the repository does not support the public preview. force uses the same create/append-only safety checks but surfaces those capability states as warnings. Divergence, malformed responses, authentication or network errors, and a failed gh stack link are warnings in both modes. Every native-integration failure is non-fatal to an otherwise successful gg sync.

JSON and JSONL output

Atomic gg sync --json includes a sync.github_stack object for GitHub syncs (and null when the provider is not GitHub or provider detection did not complete). It always contains mode, action, reason, stack_number, pr_numbers, and message:

{
  "version": 1,
  "sync": {
    "stack": "feature",
    "base": "main",
    "github_stack": {
      "mode": "auto",
      "action": "created",
      "reason": null,
      "stack_number": 7,
      "pr_numbers": [41, 42],
      "message": null
    }
  }
}

action is one of created, appended, unchanged, skipped, or warning. Stable reasons include disabled, partial_sync, insufficient_prs, unresolved_prs, missing_extension, outdated_extension, unsupported_repository, diverged, and backend_failed.

gg sync --jsonl emits a line-delimited github_stack event immediately after reconciliation and repeats the same result in the deterministic final summary event. Its status is ok for created, appended, unchanged, and skipped results, and warning for warning results. For example, a divergence event is:

{"version":1,"command":"sync","status":"warning","event":"github_stack","mode":"auto","action":"warning","reason":"diverged","stack_number":null,"pr_numbers":[41,42],"message":"Local pull requests do not match the native stack active sequence"}

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)
github_stackmode, action, reason, stack_number, pr_numbers, messageGitHub Stacked PRs reconciliation completed; status is ok or warning
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
      }
    ]
  }
}