File:  [NetBSD Developer Wiki] / wikisrc / users / dholland / hgnb.mdwn
Revision 1.7: download - view: text, annotated - select for diffs
Sun Dec 14 03:01:49 2014 UTC (6 years, 11 months ago) by dholland
Branches: MAIN
CVS tags: HEAD
fail.fail.fail

## Mercurial usage for NetBSD
(hypothetically assuming the repositories have been converted)

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.

### Basic usage

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:

   CVS				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:
				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:
				hg resolve -m file

You can list unresolved conflicts thus:
				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:
				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):

				hg push ../scratch
				cd ../scratch
				hg update
				hg merge
				...whatever merge stuff, including commit...
				cd ../src
				hg pull ../scratch
				hg update

### Disconnected operation

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.

### Some other bits

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 heads

This 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 recover

and if you have reason to think the repository might be corrupt you
can check it like this:
				hg verify

### Development branches

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:

	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:
	cvs co -P -rlibc13	hg clone [url]
				cd src
				hg update -r libc13

Switch to a new tree on a branch:
	cvs up -dP -A -rlibc13	hg pull			(if needed)
				hg update -r libc13

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:
				hg update -r libc13-base
				[resolve conflicts if needed]
				hg update -r libc13
				[resolve conflicts if needed]

Check which branch you're currently on:

	cat CVS/Tag		hg branch

See list of branches:
	[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):

	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.

	[no can do]		hg commit --close-branch

Don't forget to update doc/BRANCHES too.

### Vendor branches

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:

	mkdir tmp
	cd tmp
				hg update -r0000
				mkdir external && cd external
				mkdir bsd && cd bsd
				mkdir frobozz && cd frobozz
	tar -xvzf \		tar -xvzf \
	  frobozz-1.0.tgz	  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 frobozz2netbsd 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:

	mkdir tmp
	cd tmp
				hg update -rFROBOZZ
				cd external/bsd/frobozz
	tar -xvzf \		tar -xvzf \
	  frobozz-1.1.tgz	  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

### Release branches

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:

	cvs rtag -r netbsd-7 \	hg tag -r netbsd-7 \
	  netbsd-7-0-RELEASE	  netbsd-7-0-RELEASE

Viewing the changes on a branch:

	cvs log > file		hg log -b netbsd-7
	[page through & curse]

Extracting tarballs:

				cd src
	hg export -r \		hg archive -r \
	  netbsd-7-0-RELEASE \	  netbsd-7-0-RELEASE \
	  src			  .../netbsd-7.0.tar.gz
	mv src netbsd-7.0
	tar -cvzf \
	  netbsd-7.0.tar.gz \
	  netbsd-7.0

### Reverting a bad commit

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.

	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.)

### Carrying local changes

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:

* You can stash your uncommitted changes any time you need to merge.
This works fine but it quickly becomes a nuisance.
* You can use different trees for hacking and for building the system
for install, since presumably you only need the local changes in
the latter case.
This works fine until you need to shift partially-completed hacking to
the installable tree, and then becomes painful.
* You can commit your local changes as "secret" using the evolve
extension (I recommend reading the docs for the evolve extension);
then they're committed and can be merged and so on, but won't get
pushed back to the master repository.
The downside of this is that you can't readily distribute your local
changes among your own repositories.
* You can use the mq patch queue extension and store your local
changes as patches against the tree; then they can be popped off
easily for other work.
The downside of this is that merging stuff into your local changes
becomes awkward.
* You can finish your local changes so they can be committed upstream :-)

None of these solutions is perfect, but one or the other of these
approaches is probably good enough in most cases.

### Reverting stuff locally

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:
				hg revert -r rev subtree
To revert to a specific date:
				hg revert -d date subtree


### Other stuff

Have I forgotten anything?
Email me questions...

CVSweb for NetBSD wikisrc <wikimaster@NetBSD.org> software: FreeBSD-CVSweb