---
name: mercurial-bookmarks-and-prs
description: Use when starting feature work, switching between in-progress work, or pushing a feature for review in a Mercurial repo. Bookmarks are the unit of feature work and the unit of pull requests in this workflow.
---

# Mercurial bookmarks and pull requests

## When this applies

Use when:
- Starting a new feature or fix that will become a pull request.
- Switching between multiple in-progress features.
- About to push work for review.
- A bookmark is in the wrong place and needs fixing.

Do not use for:
- Long-lived branches like `default`, `stable` — those are named branches, see "Bookmark vs named branch" below for the distinction.
- Pulling/merging upstream changes → see `mercurial-sync-and-merge`.
- Amending commits before push → see `mercurial-history-rewriting`.

## Bookmark vs named branch

| | Bookmark | Named branch |
|---|---|---|
| Purpose | Feature/PR pointer | Long-lived line of development |
| Lifetime | Days to weeks; deleted after merge | Months to years |
| Examples | `add-language-stats`, `fix-nil-reviewer` | `default`, `stable`, `release-2.0` |
| Moves on commit? | Yes (the active one moves) | The new commit stays on the branch |
| Can be deleted? | Yes (`hg bookmark -d`) | Effectively no |

**Rule of thumb:** if you'd open a PR for it, it's a bookmark. If it's a maintained release line, it's a named branch.

### When the user says "branch"

Most users come to Mercurial from Git, where "branch" means a lightweight pointer that can be deleted. In Mercurial that concept is a **bookmark**, not a named branch. Map their words accordingly:

- **"create a feature branch", "start a branch", "switch branches", "branch off X"** → bookmark, every time.
- **"create a release branch", "branch for v2.0"** → named branch (long-lived release line).
- **Ambiguous** → ask, but default to bookmark for anything PR-shaped.

**Override repo precedent.** If you find existing named branches that look like feature work (e.g. an `add-language-stats` named branch alongside `default`), do *not* infer that the convention is to use named branches. That precedent is almost always a previous mistake (often by an LLM that didn't load this skill). Use a bookmark for the new work and surface the inconsistency:

> "Heads up: this repo has named branches (`add-language-stats`, `fix-nil-reviewer`) that look like feature work, which is unusual — feature work in Mercurial is typically a bookmark. I'm using a bookmark for the new feature; let me know if you'd prefer to match the existing pattern."

The only time existing named branches *should* steer you toward a named branch is when they are clearly long-lived release lines (`stable`, `release-1.0`, `release-2.0`).

## Starting a feature

```sh
hg pull -u                  # sync to latest default
hg bookmark my-feature      # create bookmark at current rev
# ... edit files ...
hg add new-file.go
hg commit -m "feat: add new thing"
hg push -B my-feature       # push the bookmark to the remote
```

After `hg commit`, the bookmark `my-feature` advances to the new changeset automatically (it's the active bookmark).

## Listing bookmarks

```sh
hg bookmarks
```

Output marks the active bookmark with `*`:

```
   add-language-stats        42:a1b2c3d4e5f6
 * my-feature                47:b9c8d7e6f5a4
   work-in-progress          39:c5d4e3f2a1b0
```

## Switching bookmarks

```sh
hg update my-feature        # switch to the bookmark and make it active
```

`hg update` checks out the changeset the bookmark points to AND activates the bookmark (so future commits move it forward).

If you only want to look at a revision without activating any bookmark there:

```sh
hg update -r <revision>
```

## Pushing for review

The Isurus convention (non-publishing repo + bookmarks = PRs):

```sh
hg push -B my-feature
```

`-B my-feature` pushes the bookmark by name. Without `-B`, `hg push` pushes the current head's changesets but does not push the bookmark itself, which means the remote won't know to associate them with a PR.

After pushing, open the PR in the Isurus web UI (or the equivalent forge UI). See `isurus-conventions` for Isurus specifics.

## Updating a PR after review feedback

In a non-publishing repo, pushed changesets stay `draft` until the PR is merged. That means amending after a push is **safe**:

```sh
# ... make the requested changes ...
hg amend                    # rewrites the tip changeset in place
hg push -B my-feature -f    # -f because the rewrite changed the hash
```

The `-f` is required because the new amended changeset has a different hash than what's on the remote; without `-f` the push is rejected. This is the *intended* workflow — not a sign you're doing something dangerous, because the changesets are still draft.

## Deleting a bookmark after merge

Once the PR is merged into `default`:

```sh
hg pull -u                  # pull in the merged changes
hg bookmark -d my-feature   # delete locally
hg push --delete my-feature # delete on the remote (if not already gone)
```

## Recovering a misplaced bookmark

If the bookmark is on the wrong changeset (e.g., you forgot it was active and committed something unrelated):

```sh
hg bookmarks                          # see where it is
hg bookmark my-feature -r <correct>   # move it
```

The `-r` flag moves an existing bookmark to a specific revision without checking out anything.

## Common pitfalls

- **Forgetting `-B` on push:** the changesets land but no bookmark = no PR association. Re-push with `hg push -B <name>`.
- **Forgetting `-f` after amend:** push is rejected with "remote has changesets you don't have." Verify it's your own amended changeset (not a real upstream change), then `-f`.
- **Active bookmark surprise:** `hg update -r <rev>` *without* a bookmark name leaves no bookmark active; subsequent commits create anonymous heads. Always activate a bookmark you intend to commit to.
