Files

3.4 KiB

name, description
name description
github-stack-rebase Safely rebase and update a dependent GitHub pull-request stack. Use when asked to refresh, repair conflicts in, rebase, or force-push a stacked set of Git branches/PRs onto a newer base branch, especially when descendants must be rewritten in order.

GitHub Stack Rebase

Use this workflow for a stack that has already been identified as ordered from a base branch through one or more dependent PR branches. Never infer a stack solely from ticket keys or branch names.

Confirm the rewrite boundary

  1. Obtain explicit authorization to rewrite and force-push the named branches. Treat an explicit request to rebase the stack as authorization only for those branches.
  2. Check git status --short --branch. Stop if the worktree is dirty.
  3. Fetch the remote: git fetch <remote> --prune.
  4. Identify each PR's actual head branch and verify every parent relation with git merge-base --is-ancestor <parent> <child>. A ticket key may have multiple, unrelated branches.
  5. Report the complete, bottom-to-top chain and its base. Stop if a head branch is missing, the graph is not linear, or the requested base is ambiguous.

Preserve recovery points

Before changing a branch, record its fetched remote SHA and create a unique local backup ref for every branch, for example:

backup_ref="codex/rebase-backup/$(date +%Y%m%d-%H%M%S)/<branch>"
git branch "$backup_ref" "origin/<branch>"

Do not overwrite an existing backup ref. Keep the original remote SHA for the push lease.

Rebase bottom-to-top

Rebase the first stack branch onto the fetched base:

git switch <first-branch>
git rebase origin/<base-branch>

For every child, save the parent's pre-rebase SHA before rewriting it, then replay only the child commits:

old_parent=<saved-pre-rebase-parent-sha>
git switch <child-branch>
git rebase --onto <rewritten-parent-branch> "$old_parent"

Use --rebase-merges only after confirming that the stack intentionally contains merge commits that must remain. Do not use --skip merely to get through a conflict: it can silently remove feature work.

Resolve conflicts deliberately

When a rebase stops:

  1. Announce the pause and leave the rebase in progress.
  2. Inspect the conflicted files, the current base behavior, and the commit being replayed. Retain both behaviors unless the task intentionally replaces one.
  3. Stage only the resolved files and run git rebase --continue.
  4. Run the smallest relevant test or build after resolving non-trivial product conflicts.

Use git rebase --abort if the intended resolution cannot be determined. Do not force-push a branch whose rebase is incomplete.

Push each rewritten branch safely

After a branch has rebased successfully, push it with the recorded SHA as an explicit lease:

git push --force-with-lease=refs/heads/<branch>:<original-remote-sha> \
  <remote> HEAD:refs/heads/<branch>

Push in the same bottom-to-top order. If a lease fails, stop: fetch, inspect the new remote commit, and ask for direction before overwriting anyone else's update.

Verify the result

Fetch again, then verify:

git merge-base --is-ancestor origin/<base-branch> origin/<first-branch>
git merge-base --is-ancestor origin/<parent-branch> origin/<child-branch>
git status --short --branch

Report the refreshed chain, rewritten branches, conflict resolutions, tests run, and backup-ref namespace. Create any new feature branch only from the verified top branch.