|
root / skills / mercurial-history-rewriting
mercurial-history-rewriting Plain Text 129 lines 4.9 KB
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
---
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:

```sh
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:

```sh
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:

```sh
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

```sh
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.

```sh
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"

```sh
hg amend -m "the right message"
```

### "I committed to the wrong bookmark"

```sh
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:

```sh
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.