Hi All,
Here's a bit of a work in progress draft taken from a
document I'm working on that will form a "Contribution Guide" for
Dedalus. Eventually, in the not at all distant future, I hope this will
end up in the documentation. However, as of right now, it's a disjointed
mess of notes sitting on my laptop. Below is the relevant section on
rebasing changes; attached are the two scripts it references.
Please let me know if you have any questions
In
short, we would like a cleanish history, but we are not interested in
going crazy rewriting history to preserve a perfectly linear commit log
(some projects do this). We just want to minimize noise in the commit
log (e.g. "fixed typo!"; "oops, nope it was right the first time"; "no
wait..."). I'm pretty guilty of these kinds of commits, and I don't
necessarily see them as inherently bad. However, when proposing new
features to the core of the code, we should make sure everything is as
easy to debug as possible. That said, here is the first cut at
explaining rebasing and phases in hg.
* Readying your work for Pull Requests
There are (at least) two basic ways you can use hg. You can use it as
a "work log", in which you make many many commits, some of which do
not work, some of which will be entirely reverted, one of which
(hopefully the last!) is a correct, bug-free, new feature. The other
main way is to only commit when the feature you're working on passes
all tests.
If you (like me) use the "work log" approach, when it comes time to
issue a pull request to have your completed new feature accepted into
Dedalus, we ask that you clean up the changelog a bit. This is
especially true if you have a complete rewrite of the feature during
the pull request peer review process.
There is a simple way to "collapse" many changesets into a single one
that implements only the final version of the code you want to have
pulled into Dedalus. However, there are a few subtleties that we have
to address. The first thing we need to do is understand "phases" in
hg.
** hg phases
Internal to hg are three phases, which are ordered as follows:
public < draft < secret
hen changes are shared (by push/pull), the phase marker is lowered to
the lowest common setting, typically, this is "public".
In a typical Dedalus workflow, one develops changesets on some local
clone of the repo. These will begin as "draft" changesets. However, if
you push those changesets to your fork of Dedalus on Bitbucket, they
become public.
** Rebasing
For the purposes of future debugging, we would like the master Dedalus
repository history to be as clean as possible. In order to maintain
this cleanliness, it is good practice to "collapse" many of the small,
unimportant, incremental changesets into a few important
changesets. You can use the rebase tool in hg to do this.
The simplest way to do this is if all the changesets you've made are
still draft: that is, you haven't pushed to your bitbucket fork of
Dedalus. Let's say you've just added changesets D and E, where D is an
incremental change you don't want to keep (there can be arbitrary
numbers of changesets between C and E; I've just used one to keep it
simple). If everything is local, and $ID_X is the changeset hash of
change X, you can simply do
$ hg rebase -r $ID_D::$ID_E -d $ID_C --collapse
This will destructively rewrite the history of your local working copy
so that changeset D never happened. The commit log that rebase
generates by default will keep all of the log messages from the previous commits
and append them to the final log message. You can give rebase -m
"message here." of whatever message you'd like, just as with commit.
However, let's say you've already pushed changeset D and E to
bitbucket. Now, D's phase has been changed to public, even on your
local copy. You can't just destructively rewrite history. So, instead,
you'll do the same thing, but use the --keep option to rebase:
hg rebase -r $ID_D::$ID_E -d $ID_C --collapse --keep
If you forget --keep, you'll get an error message like this:
abort: can't rebase immutable changeset <$ID_D>
What this will do is create a new head with the collapsed changeset,
while keeping the old, messy history. To push this to bitbucket,
you'll have to --force
$ hg push --force
because it will create a new remote head on your bitbucket fork. You
can just update the pull request to this head, and then the final
changes will be one single commit.
In order to demonstrate this, attached are two simple bash scripts you
can run that will create a sample repository, a fake "bitbucket" clone
that lives right next to it in the parent directory, and then runs all
of the above workflows. rebase_example.sh gives the typical Dedalus
workflow when you have already pushed your commits to your fork on
bitbucket. rebase_nokeep.sh gives the simpler case you may want to use
going forward, where you eliminate any incremental changesets BEFORE
you push to bitbucket. In real life, you'll need to use a combination
of both of these workflows, since pull request review will likely lead
to additional changes, some quite large.