Files
holocron/plugins/git/skills/git-commits/references/rewrite-history.md
Defame1297 20e5627fd7 fix(git): correct three command claims the plugin got wrong, and give rebase an owner
Three statements were false against the git in use (2.39.5), each verified by
running it.

`git rebase --autosquash HEAD~N` without `-i` is a silent no-op: git prints
"Successfully rebased", the `fixup!` commit survives with the same SHA, and
rewrite-history.md presented that as the preferred flow with `-i` as an optional
review step. The agent reports the squash as done and the `fixup!` subject then
trips this repo's own commit-msg gate. `-i` is now the command, not the alternative.

"Fetch never modifies a local branch, so it is always safe to run" — new text on
this branch — is false: `git fetch origin main:probe` fast-forwarded the local
branch, and a `+` prefix force-updates it, losing commits. The claim is scoped to
the no-refspec form.

`git worktree add --orphan` does not exist before Git 2.42; on 2.39.5 it is
`error: unknown option 'orphan'`, exit 129. The same file gives a version floor for
`--recurse-submodules` three sections earlier. Floor added, with a fallback that
was tested before being documented.

git-workflow claimed "every request resolves to exactly one of these six" while
rebase, reset and stash were owned by no skill — `git reset` appeared nowhere in
the plugin, old tree or new — so "undo my last commit" routed nowhere. The claim is
gone, the router gains rows for all three, and the procedures now exist: plain
rebase with `--onto` and conflict handling, a reset mode table gated on `--hard`,
and stash save/pop/list/drop. `--hard` gets an always-loaded Gotcha, matching the
register the force-push refusal already sets.

Cherry-pick had three claimants pointing at git-history while git-commits owned the
flow, and the two copies were not equivalent — git-history's lacked the destination
check, the rtk prefix and `--abort`. Resolved to git-commits per #112; the weaker
duplicate is replaced by a hand-off.

git-commits' metadata.version was deleted rather than bumped in 14af50b, leaving two
house-contract documents citing a worked v0.1.2 to v0.1.3 transition against a file
declaring no version. Restored to 0.1.3.

Also: the divergent-pull explanation stated a `--ff-only` default that does not
exist (it is a hard error); `--remote` "requires" a configured branch where it uses
one; `git push origin --delete` moved to git-remotes, which owns the remote-side
gates; seven retired `git:<name>` source slugs; git-orchestrate quoted a
git-workflow sentence that no longer exists; git-submodules regains the indirect
trigger that made "add a dependency repo" routable; and commit atomicity is a common
gate rather than reachable only from the create flow.

Refs: #112, #113
2026-09-01 12:38:38 +00:00

77 lines
4.8 KiB
Markdown

---
source_keys:
- org-commit-conventions
- context7-git-htmldocs
---
# Rewriting existing commits
Every flow on this page rewrites history. None of them runs before the caller has explicitly approved it, and none is followed by a force-push to `main`/`master` — refuse that and explain why instead.
## Amend the last commit
1. Stage the new changes, or the changes that undo something.
2. Run `rtk git commit --amend`, adding `--no-edit` when the message stays as it is.
3. If the message should change, show the current one and prompt for the replacement.
4. The branch has now diverged from its remote. Amending is safe only on a branch nobody else has based work on; on `main`/`master`, refuse the force-push and explain, rather than warning and proceeding.
## Fold a commit into an earlier one (autosquash — preferred)
Prefer this whenever a commit is written to be folded, because git does the marking:
1. `rtk git commit --fixup=<commit>` keeps the target's message; `rtk git commit --squash=<commit>` lets you edit the combined message later. Both prefix the message with `fixup!`/`squash!` and name the target commit.
2. Get explicit approval — the rebase still rewrites history.
3. Run `rtk git rebase -i --autosquash HEAD~N`. Git pre-fills the todo list with the tagged commits already reordered against their targets; save it unchanged to apply.
**`-i` is not optional here.** On Git 2.39.5, `rtk git rebase --autosquash HEAD~N` without `-i` prints `Successfully rebased and updated refs/heads/<branch>.` and exits 0 while leaving the `fixup!` commit in place at its original SHA — `--autosquash` is honoured only by the interactive machinery, and the false success is the trap: the fold is reported as done, and the surviving `fixup!` subject then fails the Conventional Commits `commit-msg` hook. Later Git versions taught the non-interactive rebase to honour the flag, but `-i --autosquash` is correct on every version, so always write that.
## Squash by hand (interactive rebase)
Use this when the commits were not tagged at commit time. **Interactive rebase has no undo once `rebase -i` starts — `git reflog` is the recovery path.**
1. Identify the commits to squash — typically the last N on the current branch.
2. Get explicit approval.
3. Run `rtk git rebase -i HEAD~N`, marking the older commits `squash` to keep their messages for editing, or `fixup` to discard them.
4. Compose the combined message when the rebase stops to ask. For a non-trivial combined message, follow the structure in `references/commit-template.md`.
## When a rebase halts on a conflict
Offer conflict resolution or `rtk git rebase --abort`. Do not resolve conflicts automatically without confirmation.
## Rebase the branch onto a new base
Replays this branch's commits on top of another branch's tip — bringing a feature branch up to
date without a merge commit.
1. Confirm nothing being replayed has been pushed anywhere someone else has based work on. A rebase
gives every replayed commit a new SHA, which breaks any clone that already has the old ones.
2. Get explicit approval — this rewrites history like every other flow on this page.
3. `rtk git fetch origin` first, so `<newbase>` is the real tip rather than a stale local copy.
4. `rtk git rebase <newbase>` — for example `rtk git rebase main`. Use
`rtk git rebase --onto <newbase> <upstream> <branch>` to replay only the commits after
`<upstream>`, which is how a branch started from the wrong base gets moved.
5. The branch has now diverged from its remote. It needs
`--force-with-lease --force-if-includes` to push, never a bare `--force`, and never on
`main`/`master` — refuse that and explain.
## Move the branch pointer back (`git reset`)
`reset` moves the current branch to another commit. The mode decides what survives:
| Mode | Branch pointer | Index | Working tree |
|---|---|---|---|
| `--soft` | moves | untouched — the changes stay staged | untouched |
| `--mixed` (default) | moves | reset — the changes become unstaged | untouched |
| `--hard` | moves | reset | **overwritten; uncommitted work is destroyed** |
- "Undo my last commit but keep the changes" is `rtk git reset --soft HEAD~1`. That is the default
answer to the request; reach for anything else only when the caller asked for it.
- `rtk git reset --mixed HEAD~1` when the staging should be redone from scratch too.
- `rtk git reset --hard <ref>` is gated like a force-push: state exactly which uncommitted changes
will be discarded, get approval for that specific reset, and offer `rtk git stash push -u` first.
The commits it drops stay reachable through `git reflog`; the uncommitted edits never entered git
at all and nothing recovers them.
Reset does not rewrite the commits it leaves behind, so no force-push is needed unless the branch
was already pushed at the newer commit.