# 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