# Git - Rebase, Stash, and Recovery
Rebase hides sharp semantics: silently dropped commits, inverted `--ours` and `--theirs` conflict labels, and `--onto` for moving partial history precisely. Stashes are real commits with special structure, so recovery is safe when you know where each kind of content lives.
## Key points
- `git rebase --onto A B C` replays the commits after B up to C onto A, the surgical form for moving commits between branches without touching the working state.
- Use `--onto` when stray commits sit at the base of a branch. `git reset --hard` suffices when they sit at the tip.
- After the parent of a stacked pair is squash-merged, `git rebase --onto main <old-parent-tip>` replays only the child's unique commits. A plain `git rebase main` replays the squashed content and explodes the diff.
- A worktree cut from local `main` can drag in an unpushed commit. A plain rebase onto `origin/main` keeps it because the branch looks already based. `git rebase --onto origin/main <local-commit> <branch>` drops it.
- Rebase silently drops commits whose patch already exists on the target. This is expected, not data loss. `git rebase --verbose` labels them "already applied".
- In a rebase, `--ours` is the upstream base and `--theirs` is the replayed commit, the inverse of merge semantics. After `git stash pop` conflicts, `--theirs` is the pre-rebase working state.
- When the remote branch advances by a merge commit during local work, rebase onto the new remote tip. `merge-base == local HEAD~1` proves the local branch is strictly behind. A force-push would destroy the merge.
- With `pull.rebase=true` on a merge-based repository, `git pull` after divergence replays every local commit and re-hits conflicts on append-only files per commit. `git rebase --abort` restores the pre-pull state, and `git config pull.rebase false` is the permanent switch.
- `git rebase --quit` clears stale rebase state without moving HEAD. `--abort` resets the branch to the pre-rebase commit.
- `git rerere` replays a recorded conflict resolution. Verify the replayed result by compiling, because it is correct only when upstream already absorbed the same change.
- After a failed rebase, "irrelevant changes" can be drift from an index left at the wrong commit, not real edits. Check the index before you discard work.
- A fast-forward `git pull` still aborts with "would overwrite untracked file" when untracked local files exist in the incoming tree, a different failure from a tracked-file conflict.
- `git stash -u` stores untracked files in the stash's third parent. `git show stash@{0}^3:<path>` recovers them when the pop hits the same untracked collision.
- `git stash apply` performs a real three-way merge against the stash's base commit. A raw `git apply` of the stash diff does not. Use apply to recover old stashes.
- Never `git stash pop` blind. A push on a clean tree creates no entry, and the pop then grabs another session's stash.
- `git rebase --quit` on stale state can turn a mismatched autostash from an earlier interrupted rebase into a real stash entry. Audit every stash before you drop it.
- `git stash push --staged` carries a staged refresh off one worktree into another without a commit.
- Before `git rebase --abort`, save the staged resolution with `git diff --cached > resolution.patch`. The abort is lossless when the branch still points at the pre-rebase commit.
- To merge a branch into dirty `main` with uncommitted edits to the same file: rebase the branch onto the new `main` tip, `git stash`, `git merge --ff-only`, `git stash pop`. History stays linear and the foreign edits stay untouched.
## How to apply
1. Move commits between branches: `git rebase --onto <new-base> <old-base> <branch>`.
2. Drop an unpushed local commit from a branch cut from local `main`: `git rebase --onto origin/main <local-commit> <branch>`.
3. Replay only a child branch's unique commits after a squash-merge of the parent: `git rebase --onto main <old-parent-tip>`.
4. Read rebase conflict labels as inverted: `--ours` is the base, `--theirs` is the replayed commit.
5. Before an abort, save the staged resolution: `git diff --cached > resolution.patch`.
6. Clear stale rebase state without moving HEAD: `git rebase --quit`, then audit `git stash list` for stranded autostashes.
7. Recover untracked stash files with `git show stash@{0}^3:<path>`, and old stashes with `git stash apply`.
8. Carry a staged change between worktrees: `git stash push --staged`, then pop in the target worktree.
9. On a merge-based repository, set `git config pull.rebase false` when pulls replay local commits into per-commit conflicts.
## Observations
- [git] `--onto` replays the commits after the old base onto a new base. #git (source: agent sessions, 2026-03)
- [recovery] A fast-forward pull still fails on untracked-file collisions. The stash third parent holds the blocked files. #git (source: agent sessions, 2026-07)
- [recovery] An interrupted rebase can strand an autostash. Audit every stash before you drop it. #git (source: agent sessions, 2026-09)
- [git] With `pull.rebase=true` on a merge-based repository, `git rebase --abort` restores the state before the failed pull. #git (source: agent sessions, 2026-10)
## Relations
- relates_to [[resources/documentation/Git Configuration]] (pull.rebase and rerere are repository configuration switches)
- relates_to [[resources/documentation/GitHub - Pull Request Workflow]] (squash-merged parents change how stacked child branches rebase)
- relates_to [[resources/documentation/Agents/Agents - Worktree Workflows and Pre-PR Review]] (worktrees cut from local main can drag unpushed commits into PR diffs)
## Sources
Personal agent-session notes, March 2026 to November 2026