| name | rebase-branch |
| description | This skill should be used when the user asks to "rebase this", "rebase my branch", "rebase onto main", "pull latest and rebase", "sync my branch with main/default", or otherwise wants their branch updated with the latest remote state and replayed on top of the latest default branch. Resolves conflicts automatically when safe, otherwise asks the user. After it completes, the push-changes skill can safely force-push the result. Also used internally by the ship-it skill as its second step, between commit and push. |
| version | 0.1.0 |
Rebase Branch
Pull the latest remote state into both the current branch and the local default branch, then rebase the current branch's commits on top of the latest default branch. Any uncommitted local changes are always stashed before pulling and reapplied afterward, so the working tree carries forward across the rebase. Resolve conflicts automatically when the resolution is unambiguous; ask the user when it isn't.
Fast path: run the script first
Run scripts/rebase-branch.sh before doing any of Steps 1-4 by hand. It performs the entire mechanical sequence (stash, determine default branch, fetch, fast-forward/force the local default, rebase, reapply the stash) in one call instead of ~8-10 separate git commands, and stops cleanly the moment something needs judgment. Read its last output block for a STATUS= line:
STATUS=OK โ done. Skip straight to Step 5 (Report) using the commits it already printed.
STATUS=NOOP_DEFAULT โ current branch is the default branch; nothing to do. Report this to the user.
STATUS=DIVERGED_CURRENT โ the current branch has remote-only commits not present locally (Step 2.3's case), printed after the --- marker. Stop and surface this; don't guess which side is authoritative.
STATUS=REBASE_CONFLICT โ the rebase (Step 3) stopped on a real conflict. The working tree is left mid-rebase with conflict markers; resolve using Step 3's judgment rules, then continue by hand (git add, git rebase --continue).
STATUS=STASH_CONFLICT โ the rebase succeeded but reapplying the autostash (Step 4) conflicted. Resolve using Step 4's judgment rules.
STATUS=ERROR โ an unexpected git failure; the MESSAGE= line has the raw error. Fall back to the manual steps below to diagnose.
Only fall back to running Steps 1-4 by hand if the script is missing, or STATUS=ERROR needs manual diagnosis. The step-by-step instructions below are the authoritative description of what the script does and how to resolve each stop point โ read them regardless of whether you run the script.
Step 1: Pre-flight
- Run
git status to check for uncommitted changes (staged, unstaged, or untracked).
- If there are any, always stash them rather than asking:
git stash push -u -m "rebase-branch: autostash". Note that a stash was created โ it must be popped in Step 4.
- If there are none, skip the stash; there's nothing to reapply later.
- Determine the default branch name (e.g.
main): git remote show origin | grep 'HEAD branch' or gh repo view --json defaultBranchRef.
- Confirm the current branch is not the default branch itself โ rebasing it onto itself is a no-op. If they're the same, stop and report that (but pop the stash from Step 1.1 first, if one was created, so the user's working tree isn't left stashed).
Step 2: Pull latest into both branches
- Fetch both remote-tracking refs in one go:
git fetch origin <default-branch> <current-branch>.
- Update the local default branch to match its remote without checking it out:
git fetch origin <default-branch>:<default-branch>.
- This only succeeds as a fast-forward. If it's rejected, the local default branch has commits the remote doesn't (unusual โ likely stray local work on that branch). The local default branch should always mirror the remote, so force it to match:
git fetch origin +<default-branch>:<default-branch>. This only rewrites the local default branch ref (never the current branch), so it's safe to do without asking โ any stray local commits on the default branch are recoverable via reflog if the user actually wanted them.
- Update the current branch to match its remote counterpart, if one exists:
git merge --ff-only origin/<current-branch>.
- If the current branch has no upstream (never pushed), there's nothing to fast-forward โ skip this.
- If the ff-only merge fails, that alone isn't proof of a genuine divergence โ it also fails when the branch was already rebased locally and not yet re-pushed, which just leaves stale copies of your own pre-rebase commits on the remote (common in this skill's own workflow). Check whether the remote actually has commits not already present locally:
git log --cherry-pick --right-only --no-merges --oneline HEAD...origin/<current-branch>.
- Non-empty output means real remote-only work (e.g. someone else pushed to it). Stop and surface this rather than guessing which side is authoritative (pop the Step 1.1 stash first, if any).
- Empty output means the remote only holds stale, already-superseded copies of local commits. Proceed to Step 3 โ the eventual force-push (via
push-changes) overwrites them.
Step 3: Rebase
- Rebase the current branch onto the freshly-updated default branch:
git rebase <default-branch>.
- This must happen while the working tree is still clean from Step 1's stash โ
git rebase refuses outright to even start if there are any uncommitted changes (staged or not), tracked-file conflict or not. Reapplying the stash before rebasing (as opposed to after) would make every stashed run fail for this reason, not because of an actual conflict.
- If it completes cleanly, proceed to Step 4.
- If it stops on a conflict:
- Inspect the conflicting hunks (
git diff shows conflict markers).
- Resolve automatically only when the resolution is unambiguous โ e.g. identical changes on both sides, a clean addition on one side with no overlapping change on the other, or whitespace-only differences. Base the resolution on the actual surrounding code, not a guess.
- When the correct resolution isn't obvious โ both sides changed the same logic differently, a semantic conflict that doesn't show up as textual markers, or a deleted-vs-modified file โ stop and ask the user, showing the specific conflicting hunks, rather than picking a side.
- After resolving a hunk,
git add the file(s) and continue with git rebase --continue.
- Only use
git rebase --skip if the user confirms a commit's changes are now fully redundant. Never use git rebase --abort unless the user asks for it โ that would discard conflict-resolution work already done.
Step 4: Reapply stashed changes
- If Step 1.1 created a stash, reapply it now, after the rebase has completed:
git stash pop.
- If it applies cleanly, proceed to Step 5.
- If it conflicts, resolve using the same judgment as rebase conflicts in Step 3: auto-resolve only unambiguous cases (e.g. the stash and the rebased commits touch unrelated lines), otherwise stop and ask the user, showing the specific conflicting hunks. After resolving,
git add the affected files โ the stash entry is dropped automatically once stash pop finishes applying, but conflicted pops leave the stash entry in place, so drop it explicitly with git stash drop once resolution is confirmed correct.
Step 5: Report
- Run
git log <default-branch>..HEAD --oneline to show the user the rebased commits now sitting on top of the default branch.
- Note that this rewrote the branch's history, so pushing it now requires a plain force push (
git push --force, per user preference โ some repos change too often for --force-with-lease's staleness check to be practical). Because plain --force has no built-in check against clobbering someone else's concurrent push, the push-changes skill still confirms with the user before force-pushing, even for a rebase performed earlier in the same session.
Notes
- Never skip hooks (
--no-verify) or bypass signing.
- If uncommitted changes, a diverged branch, or an unresolvable conflict blocks progress, stop and surface the actual state rather than working around it silently.