I've grown frustrated with CVS; specifically, I want to be able to have local branches where I can checkpoint large patches, I want to be able to do fast/disconnected diffs, and I want some notion of an atomic changeset instead of random sets of files.
I experimented with a bunch of version control systems (svk, arch/baz, darcs) and so far monotone seems to be a good fit for a VCS to use locally in conjunction with CVS. This page describes the particular setup that I use; some familiarity with the monotone tutorial and documentation at the above URL would be useful.
The basic idea is to set up a pristine tree that you use for CVS pulls and checkins, and that you also check in to your local monotone repository. This tree/branch becomes the gateway between CVS and monotone.
Start a fresh Mozilla tree pull somewhere; for this document we'll use <tt>pristine-cvs/mozilla</tt>. Include all the MOZ_CO_PROJECTs you're likely to use (it's easy to add new ones later); I'd suggest "mail,browser,calendar,xulrunner". Let that run while you do the next setup step. I have a script called <tt>pull_all</tt> that I place the options in as well as the make -f client.mk bit, because you'll be running this command often to update the CVS tree.
This section will go over the initial setup of monotone, getting you to a point where you can create local working trees from your monotone repository.
Download and install monotone from the monotone web site. You really want 0.21 or newer, as it includes patches to the diff output that make it recognizable by bugzilla.
Note that for this document, I'll assume you have an <tt>alias mtn=monotone</tt> set up, because the long version is 5 characters too many to type often.
Set up a monotone database to hold your Mozilla repository:
% mtn --db=~/monotone/mozilla.db db init
You can use any path you'd like for the .db file.
Generate a key you'll use for yourself in this database:
% mtn --db=~/monotone/mozilla.db genkey email@example.com monotone: generating key-pair 'firstname.lastname@example.org' enter passphrase for key ID [email@example.com]: confirm passphrase for key ID [firstname.lastname@example.org]: monotone: storing key-pair 'email@example.com' in database
Don't use a very secure passphrase, since to aovid having to type it every time you do something to the repository, you'll add it to <tt>~/.monotone/monotonerc</tt>. Add the following lines to that file:
function get_passphrase(keypair_id) return "myphrase" end function use_inodeprints() return true end
The <tt>use_inodeprints</tt> bit tells monotone to use a faster way of deciding whether a file may have been modified, instead of always computing the full SHA1 hash for each file. (Note that <tt>monotonerc</tt> is a lua file; see the monotone documentation for more things that you can use.)
By this point, your CVS pull should be finished; if it isn't, wait for it to finish before continuing. We're about to put the entire tree you just pulled into monotone. First, set up that directory for use as a monotone working copy:
% cd ~/pristine-cvs/mozilla % mtn --db=~/monotone/mozilla.db setup --branch="org.mozilla.cvs"
Here --branch specifies a branch in the database where to hold CVS syncs. I suggest you use <tt>org.mozilla.cvs</tt> as the branch name to hold the trunk of Mozilla's CVS.
Now we need to add everything into the monotone repository. Note that after a working copy is set up, you no longer need to tell monotone the --db; it's stored inside the MT directory at the top level of your repository. Monotone will ignore CVS directories, so we just want to add everything in this directory recursively
% mtn add -R . >& /dev/null
It'll print a line for every file added, so you'll want to redirect both stdout and stderr to either a file that you can examine for errors or to /dev/null; the way to do that is dependant upon your shell (the above is the zsh syntax).
Now we need to commit this data;
% mtn ci -m "CVS sync" monotone: beginning commit on branch 'org.mozilla.cvs' monotone: committed revision d0b53ac023f57f97cd6d86e21438c05e4c3485ef
Setting up a working copy
Now you need to pull from the <tt>org.mozilla.cvs</tt> branch you just created to another location; we'll use <tt>~/work/mozilla</tt>
% cd ~/work % mtn --db=~/monotone/mozilla.db co --branch=org.mozilla.cvs mozilla ... % cd mozilla
You'll notice now that the mozilla/ directory has a MT directory and no CVS directories. This is where you'll do your development. Note that this tree is still on the org.mozilla.cvs branch; we'll want to change that to another branch name. There isn't any way to commit a branch with no changes right now, so you'll have to make some change in your tree first (this is a bit annoying, yes). When you do, commit to a new branch.
I suggest that you use <tt>org.mozilla.cvs.username</tt> so that if people ever set up a monotone server, their branch names won't collide (other than org.mozilla.cvs, which everyone should be pulling from the same source anyway), For any other branches, use <tt>org.mozilla.cvs.username.feature1</tt> and similar.
% mtn ci --branch=org.mozilla.cvs.vladimir -m "Branch start" monotone: beginning commit on branch 'org.mozilla.cvs.vladimir' monotone: committed revision da7404c9545106691d9e35df66795579b3a71a12
To update from CVS, you'll follow a similar pattern as the initial pristine tree import. First, you need to update your tree from CVS via <tt>pull_all</tt>. However, as some files may have been deleted in CVS, you need to tell monotone about those files. After that, you need to tell monotone about any newly-added files, and finally commit:
% cd ~/pristine-cvs/mozilla % pull_all % mtn drop `mtn list missing` % mtn add . % mtn ci -m "CVS sync"
Now your CVS tree and the <tt>org.mozilla.cvs</tt> branch are up to date, but your working branches don't have the new information. You need to propagate the new changes to your working branch(es):
% mtn propagate org.mozilla.cvs org.mozilla.cvs.username
That'll churn for a while, and prompt you to resolve conflicts between the two branches. For resolving conflicts, monotone will try to execute meld, xxdiff, and then either emacs ediff merge mode or vim merge mode, in that order. I suggest you install either meld or xxdiff (or try both -- meld lets you edit files in-place, but it doesn't let monotone specify the display name to use for the files, so it gets a little confusing which one you need to edit/save; xxdiff seems a bit more robust, but it doesn't let you edit files).
Note that this operates on data already in the repository; it does not merge with uncommitted work in your working copy. To do that, you need to run update:
% cd ~/work/mozilla % mtn update
This will bring your tree up to date with respect to the new <tt>org.mozilla.cvs.username</tt> branch, which has the new changes from <tt>org.mozilla.cvs</tt>.
I suggest that before you do this, you pull from CVS, to avoid any out-of-date errors when doing the CVS commit.
Moving your changes the other way is essentially the inverse of the other operation. First, make sure that all the changes that you want are committed to your org.mozilla.cvs.username branch. Note that this will propagate the entire branch, which may not be what you want if you have multiple independent changes in one tree:
% mtn propagate org.mozilla.cvs.username org.mozilla.cvs % cd ~/pristine-cvs/mozilla % mtn update
If you want to move just a small change, it may be simpler to just make a diff and patch it into your CVS tree. Since monotone works with file contents and not some attached version, you won't confuse monotone in doing this. Note that this won't preserve files added or dropped; I hope to have some patches to monotone to do this at some point.
% cd ~/work/mozilla % mtn diff layout > ../layout.patch % cd ~/pristine-cvs/mozilla % patch -p0 < ~/work/layout.patch % mtn ci -m "added layout.patch"
Committing to CVS
This gets a bit convoluted; if you didn't delete or add any files, then you can just cvs commit at this point. If you did add files, then you need to tell cvs about those. mtn2cvs does the opposite of cvs2mtn (albeit much more slowly -- it does one cvs add or remove per file, which can be really slow; it should combine its arguments into one large add or remove command). As input, it wants a monotone revision file. You can obtain one in this manner:
% cd ~/pristine-cvs/mozilla % mtn cat revision new_manifest [ca1ea8d5db9e7d127614e0c039d4102199b36d6d] old_revision [29b1e06227cf384754459fe8be3ad1c785f9f4cf] old_manifest [ca1ea8d5db9e7d127614e0c039d4102199b36d6d]
Note that this is the revision file for the current working copy with respect to the <tt>org.mozilla.cvs</tt> branch. There are no changes in the working copy. We want the old_revision:
% mtn cat revision 29b1 monotone: expanded selector '29b' -> 'i:29b' monotone: expanding selection '29b' monotone: expanded to '29b1e06227cf384754459fe8be3ad1c785f9f4cf' new_manifest [ca1ea8d5db9e7d127614e0c039d4102199b36d6d] old_revision [da7404c9545106691d9e35df66795579b3a71a12] old_manifest [fdc6d008e635a2bf60d15f8e6f30a783ef6c5bd9] delete_file "def" add_file "foo" patch "foo" from  to [da39a3ee5e6b4b0d3255bfef95601890afd80709]
So, you can run:
% mtn cat revision 29b1 | mtn2cvs
% cvs commit -m "b=12345, fix layout stuff, r=lumpy"
There's an experimental <tt>cvssync</tt> branch of monotone that can sync directly to/from CVS repositories. It can't be used with the mozilla repository yet because of the way we merge together a dozen different subdirectories into the mozilla dir (unless you want to check out the entire "mozilla" directory). I haven't done much with it yet, but it could be a good solution.
The <tt>cvs2mtn</tt> and <tt>mtn2cvs</tt> scripts could use a lot of enhancements; monotone has an automation facility that can provide the information that it needs (e.g. mtn2cvs could automatically fetch the old revision) in a less error-prone way.