Hi all,
In IRC the idea came up of how to have multiple heads in a repo, and
how to issue multiple pull requests from a single repo. So I thought
I'd send out my workflow, which has a single repository on my desktop
machine, from which I can push/pull to multiple endpoints.
The fun thing about hg is that you can specify changesets (or revsets,
which are fancy and easily cooler than sliced bread) for push and pull
operations. What happens when you don't specify a revset/changeset
when you push or pull is that the set of all changesets that are on
remote and not on local (or the reverse) are pushed over the wire.
This includes any other sections of the entire tree of changesets. So
if I'm doing a bunch of work and I just want to push a subset, I can
do "push -r something" for instance, and what happens is that the
changeset identified by "something" and everything necessary to
reconstruct it from the remote gets pushed. This means that the
greatest common ancestor of the remote and the local is identified,
and the shortest backward-walk of the tree is passed upstream.
So here's a workflow example, where I have a bunch of local changes, a
single fork, and multiple heads.
I go to
bitbucket.org/enzo/enzo-dev/ and I fork. This gives me a
repository, also called "enzo-dev". I clone my repo in my home
directory, and I edit the .hg/hgrc in that new clone so that [paths]
looks like this:
[paths]
enzo =
https://bitbucket.org/enzo/enzo-dev
default =
https://bitbucket.org/MatthewTurk/enzo-dev
Now I can call the main Enzo repository "enzo", and my repository is
the default. So for instance I can do:
hg pull enzo
hg push
to update *my* fork to the latest state of the enzo-dev repository. I
can also do:
hg pull enzo
hg up
to update to the latest tip, which may or may not be in enzo-dev. Now
let's say that I'm doing some work:
hg add my_new_file.c
hg commit -m "Adding my new file"
and I notice there's an error with something that's in the main
enzo-dev tip. I want to correct that, but I don't want to push my new
file yet, as it's not ready. So what I really want is a bookmark to
remember where I am, and my new line of development, and I want to
issue a pull request for the bug fix. So what I have to do is update
to whatever the tip is of enzo-dev, fix it, and then return to where I
was, with the new fix in tow. Here's one way of doing that (this uses
the Bookmarks feature, which you can find more about here:
http://mercurial.selenic.com/wiki/Bookmarks and here:
http://stevelosh.com/blog/2009/08/a-guide-to-branching-in-mercurial/#branching-with-bookmarks
along with revsets, which as noted above are pure liquid awesome:
http://www.selenic.com/hg/help/revsets )
hg bookmark my_new_feature
hg pull enzo
hg up -r "remote(tip, enzo)"
vim some_broken_file.C
hg ci
hg push -r .
Now I've pushed the new fix (which is a new head, so you may have to
push -f !) to my *personal* fork. I now go to the web interface and
issue a pull request for it. Note that I've updated to a quoted
revision; this uses revsets, which is a domain-specific language
designed to evaluate and decide on a changeset or a set of changesets.
Here I've asked for whatever the tip of the remote repository "enzo"
is. Note that if instead of issuing the PR for a fix committed to
tip, if your current fork is way out of date, you can swap
"remote(tip, enzo)" for "ancestor(remote(tip, enzo), .)" to get the
most recent common ancestor of the tip of enzo-dev and your current
working directory.
But now I'm stuck in the bug fix, without my bookmark'd my_new_file.c.
So I update:
hg up my_new_feature
hg merge tip
hg commit
This merges the bug fix into my bookmark, and it commits it. If you
used "remote" and not the ancestor example for the revset above, then
you will also have a bunch of new changes coming from whatever
happened in enzo-dev since you forked off to do your development.
Bookmarks are a nice way to manage multiple heads. I use them with
Enzo and yt a lot, and they help me track multiple features. For
instance, if I'm developing a long-term bug fix (i.e., for clump
finding) and also something else that doesn't need it (like the
volume_refactor bookmark) I can update between the two branches easily
with this. Bookmarks are preferable to named branches in many ways,
but primarily for shorter-term development.
Bitbucket can have multiple PRs going from a single repository to
upstream, so with this you could issue three or four -- just like
this:
hg pull enzo
hg up -r "remote(tip, enzo)"
...do your changes for PR #1...
hg push
...issue new PR...
hg up -r "remote(tip, enzo)"
...do your changes for PR #2...
hg push
...issue new PR...
hg up -r "remote(tip, enzo)"
...do your changes for PR #3...
hg push
...issue new PR...
Anyway, this might not be useful to all of you (or any!) but it works
really well for me, and helps keep my number of forks (and thus the
number of things I have to keep track of) down.
Incidentally, as long as I'm singing hg's praises, mercurial 2.1
introduced phases, which are a pretty awesome concept:
http://www.selenic.com/hg/help/phases
http://planet.logilab.fr/index.php?post_id=225
http://planet.logilab.fr/index.php?post_id=226
http://planet.logilab.fr/index.php?post_id=227
Basically, if you want to be extra-careful that "secret" changesets
never get pushed until you're absolutely ready, you can use phases.
And, if you want to check if something is safe to rewrite in history
(boo!) then you can check if it's still in "draft" or in "public"
form. Once hg 2.2 comes out (it's in rc) I'll be updating the yt
install script to it, but if you have hg installed privately you can
probably get an upgrade to 2.1 with "pip install -U mercurial".
-Matt