| name | description |
|---|---|
rebase-stack |
Rebases a stack of dependent git branches in order, starting from the root branch. Use when the user mentions "rebase stack", "stacked branches", "stacked PRs", branch dependencies, or needs to update a chain of branches after upstream changes. |
Rebase a stack of dependent branches in sequential order, ensuring each branch maintains exactly the expected number of commits after rebasing and force pushing.
This skill uses the following git aliases (ensure they exist in the user's .gitconfig):
| Alias | Command | Purpose |
|---|---|---|
git sync |
git fetch origin master:master && git rebase master |
Fetch master and rebase current branch onto it |
git sync-main |
git fetch origin main:main && git rebase main |
Fetch main and rebase current branch onto it (for repos using main) |
git sync-on <branch> |
git fetch origin <branch>:<branch> && git rebase <branch> |
Fetch specified branch and rebase current branch onto it |
Shell alias (typically in .zshrc or .bashrc):
| Alias | Command | Purpose |
|---|---|---|
gps |
git push --force-with-lease |
Safe force push that prevents overwriting others' work |
Recommended .gitconfig setting:
[push]
autoSetupRemote = trueThis automatically sets up remote tracking when pushing new branches, eliminating the need for -u flag.
The git sync-on <branch> alias works by:
- Fetching the remote version of the parent branch:
git fetch origin <branch>:<branch> - Rebasing the current branch onto the local (now updated) parent:
git rebase <branch>
This means if you rebase a parent branch locally but don't push it, when you switch to the child branch and run git sync-on <parent>, it will:
- Fetch the old remote version of the parent (not your rebased local version)
- Rebase the child onto that old version
- Result in duplicated commits or incorrect history
Always push the parent before syncing its children.
Before starting, check the current commit count for each PR/branch:
# For each PR number
gh pr view <PR_NUMBER> --json commits --jq '.commits | length'
# Or check commits between branches
git rev-list --count <parent-branch>..<child-branch>Record these counts - they must match after rebasing.
Process branches in order from root (closest to master/main) to leaf (furthest downstream):
# For the root branch (Phase 0) - rebase on master
git switch <root-branch>
git sync # or git sync-main for main-based repos
# CRITICAL: Force push immediately after successful rebase
gps
# Verify commit count matches expected
gh pr view <PR_NUMBER> --json commits --jq '.commits | length'
# For each subsequent branch - rebase on parent
git switch <child-branch>
git sync-on <parent-branch>
# CRITICAL: Force push before moving to next branch
gps
# Verify commit count
gh pr view <PR_NUMBER> --json commits --jq '.commits | length'After completing all rebases:
# Verify all PRs have correct commit counts
for pr in <PR1> <PR2> <PR3>; do
echo -n "PR #$pr: "
gh pr view $pr --json commits --jq '.commits | length'
doneWhen conflicts occur during rebase, follow these practices:
- Understand the conflict first - Read both versions before choosing a resolution
- Prefer manual resolution - Don't blindly use
--theirsor--ours - Test after resolution - Ensure the code compiles/runs after resolving
# View conflicting files
git status
# View the conflict markers in a file
git diff <file>
# After manually resolving conflicts
git add <resolved-file>
git rebase --continue
# If you need to abort and try again
git rebase --abortUse these ONLY when you're certain about the correct version:
# Accept all changes from the branch being rebased onto (upstream)
# Use when: Your changes are obsolete and upstream is correct
git checkout --theirs <file>
git add <file>
# Accept all changes from your branch (current work)
# Use when: You know your changes should override upstream
git checkout --ours <file>
git add <file>
# For binary files or when you want entire file from one side
git checkout --theirs -- <path/to/file>
git checkout --ours -- <path/to/file>- Schema/Generated Files: If conflict is in generated files (GraphQL schemas, protobuf, etc.), regenerate after resolving source conflicts
- Lock Files: For
package-lock.json,go.sum, etc., often best to accept one version then regenerate:git checkout --theirs package-lock.json npm install # regenerate git add package-lock.json - Import Statements: Usually safe to keep both and let the linter/compiler tell you if there are duplicates
- Whitespace Conflicts: Check if it's just formatting - may indicate need for code formatter
# Continue the rebase
git rebase --continue
# If more conflicts arise, repeat resolution process
# Once rebase completes successfully:
gps
# Verify commit count is still correct
gh pr view <PR_NUMBER> --json commits --jq '.commits | length'- Always force push before switching branches - See Why Force Push Before Switching is Critical. The
sync-onalias fetches from remote, so unpushed changes will be lost. - Use
git switch- Prefergit switchovergit checkoutfor branch switching - Use
--force-with-lease- Never usegit push -f, always usegps(force-with-lease) for safety - Verify commit counts - After each push, verify the PR commit count matches expected
- Process in order - Always rebase root branch first, then work downstream
- One branch at a time - Complete the full cycle (rebase → push → verify) before moving to next branch
Given this stack:
master
└── feature-base (PR #100, 1 commit)
└── feature-part2 (PR #101, 1 commit)
└── feature-part3 (PR #102, 1 commit)
Execute:
# Phase 0: Root branch
git switch feature-base
git sync
gps
gh pr view 100 --json commits --jq '.commits | length' # Should be 1
# Phase 1: Second branch
git switch feature-part2
git sync-on feature-base
gps
gh pr view 101 --json commits --jq '.commits | length' # Should be 1
# Phase 2: Third branch
git switch feature-part3
git sync-on feature-part2
gps
gh pr view 102 --json commits --jq '.commits | length' # Should be 1
# Final verification
for pr in 100 101 102; do
echo -n "PR #$pr: "
gh pr view $pr --json commits --jq '.commits | length'
doneThis usually means duplicate commits were introduced. Check:
git log --oneline <parent-branch>..<current-branch>If duplicates exist, interactive rebase may be needed (with user confirmation).
If gps fails with "stale info", someone else pushed. Fetch and check:
git fetch origin
git log origin/<branch> --oneline -5If push fails because branch doesn't track remote (rare with autoSetupRemote = true):
git push -u --force-with-lease origin <branch-name>With autoSetupRemote = true in .gitconfig, this is handled automatically on first push.