Note: This document is specific to the FocusVision development environment, however it's mostly applicable elsewhere.
Mercurial branches are not "cheap". Unlike Git, to create or destroy a branch, you actually have to commit a changeset to the repository. Branches are great for…something…but whatever that is, we're not doing it.
Enter Bookmarks.
Bookmarks work similarly to the Git notion of branches. To create a bookmark, you do not create a changeset. A bookmark is simply a reference to a changeset. It ires repository meta-data, and by default stays "local" (in your working copy).
You can push and pull bookmarks themselves from remote repositories, if you wish. In effect, you can create one remote repository that everyone works from this way (instead of stable
, dev
, rc
etc.).
Let's get started.
Since version 1.8 of Mercurial, the bookmark
command is part of core, and no longer an extension. If you do not have Mercurial >=2.9 installed, however, upgrade for crying out loud. beacon-sdk
(or our Vagrant box) now includes a new package.
-
Clone a repository (and save the path):
$ hg clone ssh://xxx//home/branches/core/stable beacon destination directory: beacon requesting all changes adding changesets adding manifests adding file changes added 59238 changesets with 101360 changes to 13523 files updating to branch default 6797 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd beacon $ echo "stable = ssh://xxx//home/branches/core/stable" >> .hg/hgrc
-
Create a bookmark:
$ cd beacon $ hg bookmark -i core/stable
We've created a bookmark at our current changeset, which is the tip of our clone of stable. The
-i
(--inactive
) flag tells Mercurial not to move this bookmark automatically unless it is explicitly updated to. You'll see why next step.You can give bookmarks any unique name you want. There is only one namespace for bookmarks, so if you want to organize them in some manner, you can put a
/
in the bookmark name. I recommend using a prefix so you do not confuse your remotes with bookmarks. -
Pull a different repository, and bookmark that:
$ echo "dev = ssh://xxx//home/branches/core/dev" >> .hg/hgrc $ hg pull dev pulling from ssh://xxx//home/branches/core/dev searching for changes adding changesets adding manifests adding file changes added 122 changesets with 155 changes to 59 files (run 'hg update' to get a working copy) $ hg update 54 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg bookmark -i core/dev
You can only have one active bookmark at any time. When you use update to navigate to a bookmark, that bookmark is considered active. When a bookmark is active, any descendant changeset applied on top of that bookmark will cause the bookmark to move. That means: when you commit over an active bookmark or update to a descendant of an active bookmark, the bookmark will reference your new changeset.
Because we automatically merge
stable
intodev
(and other automatic merges),dev
will always be a descendant ofstable
. If you update from an activecore/stable
bookmark to thetip
ofdev
, yourcore/stable
bookmark will move, which is undesirable.In this case, if we had not originally used the
-i
(--inactive
) flag, ourcore/stable
bookmark would be automatically updated to thetip
ofdev
, which is bad. It may help to think of these two commands as equivalent; they both create an active bookmark:$ hg bookmark -i foo && hg update -r foo $ hg bookmark foo
TL;DR: use the
-i
or--inactive
flag when usinghg bookmark
unless you plan to commit over that bookmark right away. -
Bask in the glory of your bookmarks:
$ hg bookmark core/dev 59359:89f7168f314d core/stable 59237:d7dbf9f25270
We now have two bookmarks.
Unlike Git, Mercurial does not "track" remote repositories with bookmarks. The bookmark represents the
tip
of yourstable
repository, not the remote. If you lose your place somehow (i.e. screw up), you can force a "reset" of a bookmark to a remote's tip:$ hg bookmark core/stable -f -r $(hg identify stable)
-
First, understand where you are:
$ hg identify 89f7168f314d tip core/dev
-
Oops. We need to work on an FT ticket right now, so let's go ahead and update to the
core/stable
bookmark.$ hg update -r core/stable 49 files updated, 0 files merged, 5 files removed, 0 files unresolved
-
Make changes as per usual:
$ touch butts $ hg add butts $ hg commit -m 'nothing to do w/ butts' created new head
OMG a new head! New heads are bad; doesn't Mercurial warn me about them all the time??
Yes, you now have a new head. And no, they are not bad. We have two heads because we made a commit on top of
core/stable
, which is not thetip
; whenever you commit on top of anything that's nottip
, you get a new head.Do not fear; this is how it's supposed to work.
-
OK, ready to push, right?
$ hg push stable pushing to ssh://xxx//home/branches/core/stable searching for changes abort: push creates new remote head 14b723cad087! (merge or see "hg help push" for details about pushing new heads)
Yeah, you don't want to do that. When you
hg push
without a revision, you're essentially telling Mercurial to "push this entire repository including all heads".To do this properly, you need to tell Mercurial what revision to push. And since bookmarks are really just aliases to revisions, you simply issue:
$ hg push -r core/stable stable
I'm not going to actually do this because I don't want an empty file named
butts
anywhere on our cloud servers, but that's how it works.Sometimes creating a remote head is what you want to do, but not yet. If we decide to cut back on the number of remote repositories we work with, we may have repo(s) with bookmarks, and likely many heads. For example, each "alpha branch" would have its own bookmark and head. When it's time to merge the "alpha" back into the "main" bookmark, the second head would be destroyed, but the bookmark would remain.
-
I did what you said and it still aborted with a warning about new remote heads.
That means you probably just need to pull again.
$ hg pull --rebase stable
To those of you unfamiliar with rebase, know this: fetch is abhorred by the Mercurial community (and me too), in part because it creates a bunch of godawful "automatic merge" changesets. It fills your repo up with cruft. Using --rebase, "merge" changesets only happen when they absolutely must.
WTF is a
pull --rebase
?In a nutshell: You take your new, local changesets, set them aside, then grab some new changesets, then put your changesets back on top of the ones you pulled. It will attempt to automatically merge, but w/o a new "automatic merge" changeset. If there are merge conflicts, they will be resolved just like any other merge.
You can use
pull --rebase
in any situation where you'd use fetch, except one: you already pushed the changesets you're trying to rebase. Like this:hg pull
from devhg commit
hg commit
hg push
to some alphahg pull --rebase
from dev BZZZZT nope.
You will receive an immutable changeset error. At this point, you'll have to merge and likely manually commit. This is for your own good; if someone had pulled from your alpha before you rebased, then you pushed your rebased changes to that alpha, their repo would be hosed, because the history would be different.
-
Now I need to work in
dev
.$ hg up -r core/dev 54 files updated, 0 files merged, 1 files removed, 0 files unresolved
Commit commit commit.
-
I need to pull in some alpha or something.
$ hg pull ssh://xxx//home/branches/alpha/xtabs-refactor pulling from ssh://xxx//home/branches/alpha/xtabs-refactor searching for changes adding changesets adding manifests adding file changes added 188 changesets with 835 changes to 301 files (+1 heads) (run 'hg heads .' to see heads, 'hg merge' to merge)
If you see this:
(run 'hg heads .' to see heads, 'hg merge' to merge)
, it means you can't just update without a merge first. In pratical terms,dev
needs to be merged intoxtabs-refactor
:$ hg merge -r tip merging hermes/plugins/report/styles/config.style 163 files updated, 1 files merged, 113 files removed, 0 files unresolved (branch merge, don't forget to commit) $ hg commit -m 'merging dev -> xtabs-refactor'
-
I really f'd something up.
hg strip
is great, as long as you didn't push your filth. In that case, you'll probably have tohg backout
.
deviant
hates bookmarks and expects you to always be deploying tip
. This will not always be the case. Until we have some reasonable solution, if you need to deploy and you are not at the tip
(find out by issuing a hg id
), then pass the -S
flag to deviant deploy
.
hg update --clean