Here are some directions for how one would use Mercurial after a NetBSD transition from CVS to Mercurial. Most of this is pretty trivial (especially for anyone who's used Mercurial before...)
There is a lot of FUD circulating about using a distributed version control system, what with talk of "workflows" and "topic branches" and other unfamiliar terms. Most of this arises from git user communities and git advocates; because git is screwy, using git is more complicated than using other better designed tools. Also, those suffering from Stockholm syndrome with respect to git tend to believe that the complexities of git are inherent to distributed version control, which is not the case; and many other people have been alarmed (or scared, or confused) by things such people have told them.
First, NetBSD will go on using a central master repository. There is nothing to be gained by changing this; we have the project infrastructure to support it, and ultimately there has to be some tree somewhere that constitutes the master copy regardless.
Therefore, the basic usage is almost entirely unchanged:
in CVS: | in Mercurial: | ||
---|---|---|---|
cvs checkout | hg clone | ||
cvs update -dP | hg pull && hg update | ||
cvs -n update | hg status | ||
cvs log file | hg log file [or just hg log] | ||
cvs update -p file | hg cat file | ||
cvs annotate | hg annotate | ||
cvs diff -u | hg diff | ||
cvs add | hg add | ||
cvs rm | hg rm | ||
[no can do] | hg cp | ||
[no can do] | hg mv | ||
cvs commit | hg commit && hg push | ||
cvs tag | hg tag |
You will notice that CVS's update and commit have been divided into two now-separable actions: in Mercurial, pull fetches changes from a remote repository but doesn't affect your working tree, and update updates your working tree to (by default) the latest new changes. Similarly, commit integrates changes from your working tree, but locally only; push publishes those changes to the remote repository.
This means that you can commit many times before pushing; this is often desirable if you're working on something nontrivial and you want to wait until it's ready before shipping it out.
There is one catch, which is that other people can commit (and push to the master repository) while you're working. You can mostly avoid this, if you haven't committed anything locally yet, by doing "hg pull && hg update" before committing, which will merge into your uncommitted changes and works exactly like updating before committing in CVS. However, if you've committed a number of changes, or someone got a new change in right between when you last pulled and when you committed, you need to do an explicit merge instead, and then you can push.
In the simple case, you do an explicit merge as follows:
in Mercurial: | ||
---|---|---|
hg pull hg merge hg commit |
When you get a merge conflict, you first need to resolve it (in the usual way by editing) and then you must tag it resolved in hg before hg will let you commit, like this:
in Mercurial: | |||
---|---|---|---|
hg resolve -m file |
in Mercurial: | |||
---|---|---|---|
hg resolve -l |
Note that even with the explicit merge this is almost exactly equivalent to the CVS behavior when someone commits ahead of you. The chief difference is that because Mercurial does whole-tree commits, *any* change ahead of you needs to be merged, not just one that touches the same files you've edited.
There is one gotcha, which is that you can't do explicit merges in a tree with uncommitted changes. The best way around this is to stash your changes:
in Mercurial: | |||
---|---|---|---|
hg stash hg merge ...whatever merge stuff... hg unstash |
You can also do the merge in another tree; because Mercurial is a distributed tool, you can create a temporary copy of your tree, or push the changes you need to another tree you already have, do the work there, and push the results back. Let's suppose you have two trees, "src" and "scratch", where you never keep uncommitted changes in "scratch" so it can be used for this kind of thing. Then you can do the following (starting at the top of src):
in Mercurial: | |||
---|---|---|---|
hg push ../scratch cd ../scratch hg update hg merge ...whatever merge stuff, including commit... cd ../src hg pull ../scratch hg update |
Mercurial is a distributed system, and works by cloning the entire history into each tree you create. This has its downsides; but it means that you get disconnected operation for free. The only operations that need to contact the master repository are push, pull, incoming, and outgoing.
A commit with no descendents (that is, the most recent commit on any line of development) is called a "head". You can list these as follows:
hg headsThis will include commits that have descendents only on other branches, e.g. the last commit on a development branch that's been merged but not closed. Use "-t" ("topological heads") to hide these.
You can see what "hg pull" and "hg push" are going to do via "hg incoming" and "hg outgoing" respectively. (FWIW, git can't do this.)
If you interrupt Mercurial (or Mercurial gets interrupted, e.g. by a system crash) you want to do this afterwards:
hg recoverand if you have reason to think the repository might be corrupt you can check it like this:
hg verify
A development branch is one where you're working on some new feature and you expect to merge the branch into the trunk later. Unlike in CVS, this is very cheap in Mercurial. The following are the operations you need, using "libc13" as an example branch name.
Note that even if you're working on something nontrivial that will take a number of commits, if you aren't intending to push the changes out before they're done you don't need to make a branch and there's nothing gained by doing so. However, if you expect to be working over a long period of time on a major effort (such as the mythical libc version bump), and/or you want or expect other developers to contribute or at least test your changes before they're done, go ahead and create a branch.
Create a new branch:
in CVS: | in Mercurial: | ||
---|---|---|---|
cvs update -dP | hg pull && hg update | (if needed) | |
update doc/BRANCHES | update doc/BRANCHES | (if appropriate) | |
cvs commit doc/BRANCHES | hg commit doc/BRANCHES | (if needed) | |
cvs tag libc13-base | hg tag libc13-base | ||
cvs ph'tagn | hg branch libc13 | ||
[make first change] | [make first change] | ||
cvs commit | hg commit hg push |
Mercurial warns you that branches are permanent and expensive; this warning is aimed at git users who ought to be creating bookmarks instead and not something you need to be concerned about.
Check out a new tree on a branch:
in CVS: | in Mercurial: | ||
---|---|---|---|
cvs co -P -rlibc13 | hg clone [url] cd src hg update -r libc13 |
||
Switch to a new tree on a branch:
in CVS: | in Mercurial: | ||
---|---|---|---|
cvs up -dP -A -rlibc13 |
hg pull hg update -r libc13 |
(if needed) |
Note that if you have uncommitted changes, Mercurial will balk at crossing from one branch to another because it doesn't know how to merge them. In that case do this:
in CVS: | in Mercurial: | ||
---|---|---|---|
hg update -r libc13-base [resolve conflicts if needed] hg update -r libc13 [resolve conflicts if needed] |
Check which branch you're currently on:
in CVS: | in Mercurial: | ||
---|---|---|---|
cat CVS/Tag | hg branch |
See list of branches:
in CVS: | in Mercurial: | ||
---|---|---|---|
[no can do reliably] | hg branches |
Note that unlike with CVS there's no version-control-related reason to get a new tree just to work on a branch. (Although of course it's still possible to commit to the wrong branch by accident.) Get a new tree if and only if you want to have a different tree for administrative reasons.
Sync your branch with the trunk ("HEAD" in CVS):
in CVS: | in Mercurial: | ||
---|---|---|---|
cvs ph'tagn | hg merge default | ||
[resolve conflicts] | [resolve conflicts] | ||
cvs commit | hg commit hg push |
When you're done with your branch, in Mercurial you can "close" it so it's no longer active. This causes it to disappear from some reports, reduces some internal management overheads, and prevents accidental commits on it.
in CVS: | in Mercurial: | ||
---|---|---|---|
[no can do] | hg commit --close-branch |
A vendor branch is one where code from a third party is committed in unmodified state, so it can be updated easily from upstream later.
Note that in CVS vendor branches are magic (in a bad way); in Mercurial we'll just use an ordinary branch. We'll start it from the empty revision so it doesn't contain any unwanted rubbish.
To start a new vendor branch for the upstream package "frobozz", assuming you've already written frobozz2netbsd if one's needed:
in CVS: | in Mercurial: | ||
---|---|---|---|
mkdir tmp cd tmp | |||
hg update -r null mkdir external && cd external mkdir bsd && cd bsd mkdir frobozz && cd frobozz |
|||
tar -xvzf frobozz-1.0.tgz | tar -xvzf frobozz-1.0.tgz | ||
mv frobozz-1.0 dist | mv frobozz-1.0 dist | ||
cp .../frobozz2netbsd . | cp .../frobozz2netbsd . | ||
./frobozz2netbsd | ./frobozz2netbsd | (if needed) | |
cvs import src/distrib/bsd/frobozz FROBOZZ frobozz-1-0 | |||
hg add hg branch FROBOZZ hg commit hg tag frobozz-1-0 |
|||
cd ../src cvs update -dP |
|||
hg update -r default hg merge FROBOZZ hg commit |
|||
[hack as needed] | [hack as needed] | ||
cvs commit | hg commit hg push | ||
cd .. rm -r tmp |
Note that in both cases this imports the frobozz2netbsd script on the branch; this seems the most convenient but I'm not sure if it's been our standard procedure.
To update "frobozz" to 1.1:
in CVS: | in Mercurial: | ||
---|---|---|---|
mkdir tmp cd tmp |
|||
hg update -rFROBOZZ cd external/bsd/frobozz |
|||
tar -xvzf frobozz-1.1.tgz | tar -xvzf frobozz-1.1.tgz | ||
rm -r dist | |||
mv frobozz-1.1 dist | mv frobozz-1.1 dist | ||
./frobozz2netbsd | ./frobozz2netbsd | ||
cvs import src/distrib/bsd/frobozz FROBOZZ frobozz-1-0 | |||
hg addremove hg commit hg tag frobozz-1-1 |
|||
cd .. mkdir tmp2 && cd tmp2 cvs ph'tagn |
|||
hg update -r default hg merge FROBOZZ |
|||
[resolve conflicts] | [resolve conflicts] | ||
cvs commit | hg commit | ||
cd ../src cvs update -dP |
|||
[hack as needed] | [hack as needed] | ||
cvs commit | hg commit hg push |
||
cd .. rm -r tmp tmp2 |
A release branch is one that diverges from the main branch and is not expected to be merged back into it. However, changes from the main branch are (individually) merged into it after review.
Creating a release branch in Mercurial is the same as creating a feature branch; see above. So is checking it out. Committing a change to a release branch is no different from committing to the default branch or any other branch.
TODO: we should probably use the Mercurial cherrypick extension for at least some release branch pullups; I don't know how to do that offhand without looking it up.
Tagging a release:
in CVS: | in Mercurial: | ||
---|---|---|---|
cvs rtag -r netbsd-7 netbsd-7-0-RELEASE | hg tag -r netbsd-7 netbsd-7-0-RELEASE |
Viewing the changes on a branch:
in CVS: | in Mercurial: | ||
---|---|---|---|
cvs log > file [page through and curse] |
hg log -b netbsd-7 |
Extracting tarballs:
in CVS: | in Mercurial: | ||
---|---|---|---|
mkdir tmp cd tmp |
|||
cvs export -r netbsd-7-0-RELEASE src | hg archive -r netbsd-7-0-RELEASE ../netbsd-7.0.tar.gz | ||
mv src netbsd-7.0 tar -cvzf ../netbsd-7.0.tar.gz netbsd-7.0 cd .. rm -r tmp |
Sometimes somebody commits something that needs to be unwound later. In CVS you have to track down each per-file change and undo each one separately, then commit them all. In Mercurial, because Mercurial has whole-tree commits, you can do it with a single command.
in CVS: | in Mercurial: | ||
---|---|---|---|
cvs update -j1.6 -j1.5 foo.c cvs update -j1.9 -j1.8 bar.c cvs update -j1.15 -j1.14 baz.c |
|||
hg backout -r 101abcde | |||
[resolve conflicts] | [resolve conflicts] | ||
cvs commit | hg commit hg push |
Note that apparently if you use hg backout to back out the most recent commit, it auto-commits. (This seems to me like a UI bug.)
In CVS you can keep uncommitted changes in your tree indefinitely with no ill effects. (Or at least, no ill effects until you want to commit other changes to the same files, run into merge conflicts, or hit PR 42961.)
In Mercurial having uncommitted changes keeps you from doing explicit merges, which you need to do much more often than in CVS. There are several ways around this:
In CVS you can use "cvs update" to pin a subtree down to a specific point in history, where it will stay while you update the rest of the tree around it. (Accidental engagement of this feature is probably as common as intentional use...)
There is no direct equivalent in Mercurial. However, you can easily alter a file or subtree to roll it back to a specific point in history, and then carry the resulting diff as a local modification until whatever issue prompted you to do this gets sorted out.
To revert to a specific version:
in Mercurial: | |||
---|---|---|---|
hg revert -r rev subtree |
in Mercurial: | |||
---|---|---|---|
hg revert -d date subtree |
Have I forgotten anything? Email me questions...