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
77 lines
4.8 KiB
Markdown
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.
|