|
root / skills / mercurial-history-rewriting / SKILL.md
SKILL.md markdown 129 lines 4.9 KB

name: mercurial-history-rewriting
description: Use when rewriting Mercurial history — amending commits, uncommitting, splitting commits, or recovering from a mistake. Critical: only safe on draft changesets, never on public ones.

Mercurial history rewriting

When this applies

Use when:

  • About to amend a commit (fix typo, add forgotten file, edit the message).
  • Need to uncommit / split a changeset.
  • Recovering from a mistaken commit.

Do not use for:

  • Routine commits → see mercurial-basics.
  • Bookmark/PR push flow → see mercurial-bookmarks-and-prs.
  • Conflicts after merging → see mercurial-sync-and-merge.

The fundamental rule: draft only

Only rewrite changesets in draft phase. Never rewrite public ones.

Check before rewriting:

hg phase -r tip                 # or any specific revision

If the phase is public, stop and surface the situation to the user. Rewriting public history breaks every other clone of the repo. Mercurial will refuse by default with: cannot amend public changesets.

In Isurus the repo is non-publishing, so pushed changesets stay draft until merged into the canonical branch. That makes hg amend safe during code review. After merge, the changesets become public and are off-limits.

hg amend — fix the tip changeset in place

The most common rewrite. Edit files, then:

hg amend                        # fold working-dir changes into tip; keeps message
hg amend -m "new message"       # also rewrite the message
hg amend file1 file2            # only fold those files

After amend, the new changeset has a different hash. If it was already pushed, you'll need -f next push:

hg push -B my-feature -f

This is normal in a non-publishing repo. See mercurial-bookmarks-and-prs for the post-amend push pattern.

hg uncommit — undo a commit, keeping changes in working dir

hg uncommit                     # the entire tip changeset
hg uncommit file1 file2         # only those files (rest stay committed)

Use cases:

  • The commit message is wrong and you want to redo it from scratch.
  • You committed too much and want to split.

After uncommit, the files are back in the working dir as uncommitted changes. Re-commit selectively.

Splitting a commit

Goal: turn one commit into two (or more) more focused ones.

hg uncommit                                # uncommit everything
hg commit path/to/group-1 -m "feat: part 1"
hg commit path/to/group-2 -m "feat: part 2"

If groups overlap by line within a single file, you may need to use a partial-add tool — that's beyond v1 scope. Manually patch and recommit, or use hg commit -i (interactive) if available.

Recovering from a mistake

"I committed too soon / wrong message"

hg amend -m "the right message"

"I committed to the wrong bookmark"

hg bookmarks                            # see what's where
hg bookmark wrong-name -r .~1           # move the wrong bookmark back one
hg bookmark right-name -r .             # put the right bookmark on tip

"I committed something I shouldn't have"

If only the most recent commit is wrong and changes haven't been pushed:

hg uncommit                             # changes return to working dir
# discard them (CAREFUL — destructive):
hg revert --all --no-backup             # discard all working-dir changes

"I want to undo the last several local commits"

This is harder in vanilla Mercurial without evolve. Options:

  1. hg strip <rev> — removes a changeset and all descendants. Requires the strip extension ([extensions] strip = in ~/.hgrc). Destructive; backups are saved in .hg/strip-backup/ by default.
  2. Re-clone from the remote, lose local changes.

In Isurus today, prefer option 1 with the strip extension. For complex multi-commit rewrites, this is exactly the case where evolve + topics would help (deferred to phase 2).

What hg rollback is and isn't

hg rollback undoes the most recent transaction (commit, pull, push). It's mostly deprecated because it operates on the transaction, not the changeset, and quietly loses information. Prefer hg amend / hg uncommit / hg strip instead. The repo's [ui] rollback = false is recommended (and is the default in modern Mercurial).

Public-changeset attempts

If an hg amend returns:

abort: cannot amend public changesets: <hash>

Do not use --force to override. Stop and surface the situation: someone (or the workflow) has marked this changeset public, which means it's been integrated. The right answer is a new commit on top, not a rewrite.

A note on evolve / topics

Modern Mercurial (with the evolve extension and topics) supports rich history rewriting workflows: stable obsolescence markers, topic branches, automatic descendant rebasing. Isurus does not enable these today; if it does in a future version, this skill will grow a sibling (mercurial-evolve) or be expanded.