- name
- rebase
- description
- Systematically rebase a branch onto its upstream target. Use when a feature branch has fallen behind, needs conflict resolution, requires commit cleanup before merge, or when a PR review requests a rebase. Handles complex multi-commit branches with many conflicts via incremental strategies.
- argument-hint
- [TARGET_BRANCH | PR_NUMBER]
- allowed-tools
- ["Bash(git status *)","Bash(git fetch *)","Bash(git rev-parse *)","Bash(git rev-list *)","Bash(git merge-base *)","Bash(git log *)","Bash(git diff *)","Bash(git branch *)","Bash(git config *)","Bash(git rebase *)","Bash(git checkout *)","Bash(git cherry-pick *)","Bash(git add *)","Bash(git push --force-with-lease origin *)","Bash(git push --force-with-lease fork *)","Bash(git reset *)","Bash(git reflog *)","Bash(gh pr view *)","Bash(gh pr checkout *)","Bash(comm *)","Bash(diff *)","Bash(sed *)","Bash(head *)","Bash(tail *)","Bash(sort *)","Bash(echo *)","Bash(rm -f /tmp/pre-rebase.diff /tmp/post-rebase.diff)","Bash(make test *)","Bash(pytest *)","Bash(npm test *)","Read","Edit","[Truncated]"]
# Rebase Branch
Rebase a branch onto its upstream target.
- `$ARGUMENTS` is a **PR number** (all digits) → check out the PR branch and rebase
onto the PR's base branch.
- `$ARGUMENTS` is a **branch name** → rebase current branch onto that target.
- `$ARGUMENTS` is **empty** → rebase current branch onto the upstream default branch.
## Critical Rules
- **Never rebase without a backup.** Always create a backup ref before starting.
- **Never use `--force`.** Always use `--force-with-lease` when pushing.
- **Never rebase shared branches** without explicit user confirmation.
- **Don't rewrite history unless asked.** The goal is to replay commits onto a new
base — not to squash, fixup, or reword. Only offer commit cleanup if the user
explicitly requests it.
- **Verify the result.** Always diff the tree before and after.
- **Conflict sides are swapped from merge.** During rebase: `ours` = upstream,
`theirs` = your branch. The opposite of what you expect.
## Phase 1: Assess
### Step 1: Determine target and current state
If `$ARGUMENTS` is all digits, treat it as a PR number:
```bash
# Fetch PR metadata
gh pr view "$ARGUMENTS" --json headRefName,baseRefName,headRepository,headRepositoryOwner \
--jq '{head: .headRefName, base: .baseRefName, repo: .headRepository.name, owner: .headRepositoryOwner.login}'
```
Check out the PR branch locally and capture its tracking remote:
```bash
gh pr checkout "$ARGUMENTS"
CURRENT=$(git rev-parse --abbrev-ref HEAD)
TARGET="<baseRefName from above>"
REMOTE=$(git config "branch.$CURRENT.remote" 2>/dev/null || echo origin)
```
Otherwise, determine target from the argument or defaults:
```bash
TARGET="${ARGUMENTS:-$(git rev-parse --abbrev-ref origin/HEAD 2>/dev/null | sed 's|origin/||')}"
DEFAULT_BRANCH="${PRIMARY_BRANCH:-master}"
TARGET="${TARGET:-$DEFAULT_BRANCH}"
CURRENT=$(git rev-parse --abbrev-ref HEAD)
REMOTE="origin"
```
```bash
echo "Rebasing $CURRENT onto $TARGET"
git status --porcelain
```
If there are uncommitted changes, **stop**. Ask the user: stash, commit, or abort.
### Step 2: Measure complexity
```bash
git fetch "$REMOTE" "$TARGET"
# Commits to rebase
git rev-list --count "$REMOTE/$TARGET..HEAD"
# Files changed on our branch
git diff --stat "$REMOTE/$TARGET...HEAD" | tail -1
# How far behind upstream
git rev-list --count "HEAD..$REMOTE/$TARGET"
# Check for merge commits (affects strategy)
git log --merges --oneline "$REMOTE/$TARGET..HEAD"
# Count files changed on BOTH sides (conflict indicators)
comm -12 \
<(git diff --name-only "$(git merge-base HEAD $REMOTE/$TARGET)..HEAD" | sort) \
<(git diff --name-only "$(git merge-base HEAD $REMOTE/$TARGET)..$REMOTE/$TARGET" | sort)
```
If **merge commits** exist on the branch, warn the user: a plain rebase linearizes
them. Use `--rebase-merges` to preserve topology, or confirm linearizing is OK.
Report complexity class:
| Commits | Upstream divergence | Class | Strategy |
|---------|-------------------|-------|----------|
| 1–5 | <50 | Simple | Direct rebase |
| 6–20 | <200 | Moderate | Direct rebase |
| >20 or many overlapping files | any | Complex | Incremental rebase |
**Override to Complex** if >5 files changed on both sides, even with few commits.
Only use interactive rebase (`-i`) if the user explicitly asks for commit cleanup.
## Phase 2: Prepare
### Step 3: Safety net
```bash
git branch -f "backup/$CURRENT" HEAD
# Record pre-rebase diff for verification (three-dot: changes since merge base)
git diff "$REMOTE/$TARGET...HEAD" > /tmp/pre-rebase.diff
```
### Step 4: Enable rerere
```bash
git config rerere.enabled true
```
Rerere records conflict resolutions and auto-applies them if the same conflicts
recur. Essential for repeated rebases or incremental strategies.
## Phase 3: Execute
Choose strategy based on complexity class. Default is a straight replay — no
history rewriting.
### Direct rebase (simple / moderate)
```bash
git rebase "$REMOTE/$TARGET"
# If merge commits exist and user wants to preserve them:
# git rebase --rebase-merges "$REMOTE/$TARGET"
```
If conflicts occur, go to Phase 4.
### Interactive rebase (only if user requests commit cleanup)
Only use this when the user explicitly asks to squash, fixup, reword, or
reorder commits. Preview the commit list first:
```bash
git log --oneline --reverse "$REMOTE/$TARGET..HEAD"
```
Ask the user which commits to change. Apply via `GIT_SEQUENCE_EDITOR` to
avoid requiring an interactive terminal:
```bash
# Example: squash commits 2-3 into 1, fixup commit 5
GIT_SEQUENCE_EDITOR="sed -i -e '2s/^pick/squash/' -e '3s/^pick/squash/' -e '5s/^pick/fixup/'" \
git rebase -i "$REMOTE/$TARGET"
```
Adapt the sed expression to match the user's choices.
### Incremental rebase (complex)
When the branch is far behind or conflicts are expected across many commits,
rebase in stages to keep conflict sets small and let rerere learn resolutions:
```bash
# Find the merge base (where we branched off)
MERGE_BASE=$(git merge-base HEAD "$REMOTE/$TARGET")
# Count upstream commits to choose milestone spacing
BEHIND=$(git rev-list --count HEAD.."$REMOTE/$TARGET")
# Pick 2-3 milestones evenly spaced between merge base and tip
# For 90 commits behind: pick ~30 and ~60
git log --oneline --reverse "$MERGE_BASE..$REMOTE/$TARGET" | head -n 30 | tail -1
git log --oneline --reverse "$MERGE_BASE..$REMOTE/$TARGET" | head -n 60 | tail -1
```
Good milestones are commits that DON'T touch the same files as your branch.
```bash
# Rebase to oldest milestone first
git rebase <milestone-1>
# Resolve conflicts — rerere records them
# Advance to next milestone
git rebase <milestone-2>
# Rerere auto-resolves previously seen conflicts
# Final rebase to tip
git rebase "$REMOTE/$TARGET"
```
**Alternative for very complex cases — cherry-pick one at a time:**
```bash
COMMITS=$(git rev-list --reverse "$REMOTE/$TARGET..HEAD")
git checkout -b "rebase-wip/$CURRENT" "$REMOTE/$TARGET"
for SHA in $COMMITS; do
git cherry-pick "$SHA" || break
done
```
After all commits applied, point the original branch at the result (safe — the
backup ref preserves the original state):
```bash
git checkout "$CURRENT"
git reset --hard "rebase-wip/$CURRENT"
git branch -D "rebase-wip/$CURRENT"
```
## Phase 4: Resolve Conflicts
When a conflict occurs:
### Step 1: Identify
```bash
git diff --name-only --diff-filter=U
```
### Step 2: Resolve each file
1. Read the conflicted file
2. Understand both sides:
- **Ours** (upstream): what changed on the target branch
- **Theirs** (our commit): what we intended to change
3. Resolve by preserving our intent on top of upstream changes
4. For files where one side clearly wins:
```bash
git checkout --theirs <file> # keep our version
git checkout --ours <file> # keep upstream version
```
5. Stage:
```bash
git add <resolved-file>
```
### Step 3: Continue
```bash
git rebase --continue
```
If a conflict is ambiguous, ask the user before resolving.
### Abort if needed
```bash
git rebase --abort
# or restore from backup:
git reset --hard "backup/$CURRENT"
```
## Phase 5: Verify
### Step 1: Compare diffs
The rebase replays commits onto a new base — the net diff against the target
should be the same. Verify:
```bash
git diff "$REMOTE/$TARGET...HEAD" > /tmp/post-rebase.diff
diff /tmp/pre-rebase.diff /tmp/post-rebase.diff
```
If the diffs differ, investigate. Legitimate reasons:
- Conflict resolutions that accepted upstream changes over ours
- Code refactored on both sides
If unexpected differences exist, **stop** and show the user.
### Step 2: Check for lost commits
```bash
# Commits in backup not in current branch
git log --oneline HEAD.."backup/$CURRENT"
```
Should be empty unless commits were intentionally squashed or dropped.
### Step 3: Run tests if configured
```bash
make test 2>/dev/null || pytest 2>/dev/null || npm test 2>/dev/null
```
If tests fail, **stop** and report the failures to the user before pushing.
## Phase 6: Push
### Step 1: Confirm
Summarize for the user:
- Commits rebased (count)
- Conflicts resolved (count, files)
- Verification result
- Target branch
Ask the user to confirm before pushing.
### Step 2: Force-push with lease
```bash
git push --force-with-lease "$REMOTE" "$CURRENT"
```
If rejected (remote ref moved since last fetch), run `git fetch "$REMOTE" "$CURRENT"`
and assess whether someone else pushed to the branch. Ask the user before retrying.
### Cleanup
```bash
git branch -D "backup/$CURRENT"
rm -f /tmp/pre-rebase.diff /tmp/post-rebase.diff
```
## Recovery
If something went wrong after the rebase:
```bash
# Option 1: Backup branch
git reset --hard "backup/$CURRENT"
# Option 2: Reflog (backup already deleted)
git reflog
git reset --hard HEAD@{N} # N = pre-rebase entry
```
Ver en GitHub