1. Weinan Ma
  2. kick-start_fork

Source

kick-start_fork / src / hgsubversion / index.txt

The default branch has multiple heads

.. -*- rst -*-

.. Mercurial Kick Start: http://mercurial.aragost.com/kick-start/
..
.. Copyright 2010 aragost Trifork
..
.. This software may be used and distributed according to the terms of
.. the GNU General Public License version 2 or any later version.

.. include:: ../common.rst

===========================
Interacting with Subversion
===========================

The `hgsubversion extension`__ turns Mercurial into a Subversion
client. This gives you offline commits and all the other nice features
of Mercurial while letting you push changesets back to Subversion.

.. contents::

Installation
============

.. __: http://bitbucket.org/durin42/hgsubversion/

.. shelltest::
   :name: alice

   $ ##echo "[ui]" >> $HGRCPATH
   $ ##echo "username = Alice <alice@example.net>" >> $HGRCPATH
   $ ##echo "[extensions]"                                  >> $HGRCPATH
   $ ##echo "graphlog ="                                    >> $HGRCPATH
   $ ##echo "hgsubversion = $SRCDIR/../../deps/hgsubversion/hgsubversion" >> $HGRCPATH

The extension requires the Python Subversion bindings. The bindings
are bundled with TortoiseHg on Windows. On Linux you want to search
for a package called ``python-subversion`` in your package manager.

Install the extension itself by running::

   $ hg clone https://hgsubversion.googlecode.com/hg/ hgsubversion

You can leave out the version number if you want the very latest
version, the version number shown above is the one used for this
guide. Now put::

   [extensions]
   hgsubversion = path/to/hgsubversion

into your configuration file to load the extension. If you have
installed the extension using your package manager, then it has most
likely been put in your ``PYTHONPATH`` and so you just need to add::

   [extensions]
   hgsubversion =

Use `hg help hgsubversion` to double-check that the extension is
enabled.

Cloning Subversion
==================

We have prepared a Subversion repository with a small Hello World
program. There is a repository for each group, named ``hello1``,
``hello2``, …, ``hello5``. Please clone the one corresponding to your
group number:

.. shelltest::
   :name: alice

   $ ## svnadmin create hello
   $ ## ln -s /bin/true hello/hooks/pre-revprop-change
   $ ## svnadmin --quiet load hello < $SRCDIR/hello.svndump

.. shelltest::
   :name: alice

   $ hg clone http://mercurial.aragost.com/svn/helloX hello-hg \
     ## -h > /dev/null; hg clone file://$PWD/hello hello-hg
   [r1] mg: Created initial layout.
   [r2] mg: First known version by Brian Kernighan, 1974.
   [r3] mg: Include stdio.h.
   [r4] mg: Output newline.
   [r5] mg: Version 1.0.
   [r6] mg: Capitalized!
   [r7] mg: Comments are good.
   [r8] mg: Added README.
   [r9] mg: Added Makefile.
   [r10] mg: Ignore compiled program.
   pulled 9 revisions
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd hello-hg
   $ ## snap-tortoisehg "$DSTDIR/clone.png"

She inspects the repository with `thg log` to see that the tags were
defined properly:

.. image:: clone.png
   :align: center

The final Subversion revision sets the ``svn:ignore`` property to
``hello`` in order to ignore the compiled binary. In Mercurial, ignore
patterns are controlled in the top-level ``.hgignore`` file. This file
can be generated with the command:

.. shelltest::
   :name: alice

   $ hg svn genignore

Normally, one would commit the ``.hgignore`` file in order to share it
with everybody who uses the repository. However, since Subversion is
still the master in this setup, you might not want to push the
``.hgignore`` file back there. For that reason, the generated
``.hgignore`` file ignores itself. You can regenerate it from time to
time with `hg svn genignore --force`.

Pushing to Subversion
=====================

Alice can now edit the files and commit them with Mercurial:

.. shelltest::
   :name: alice

   $ echo "/* The End. */" >> hello.c
   $ hg diff## | sed -e 's|\+\+\+ b/hello\.c.*'\
                         '|+++ b/hello.c   Fri Mar 12 21:20:10 2010 +0000|'
   diff -r e3314036fa9f hello.c
   --- a/hello.c   Fri Mar 12 10:20:45 2010 +0000
   +++ b/hello.c   Fri Mar 12 21:20:10 2010 +0000
   @@ -7,3 +7,4 @@
           printf("Hello, World!\n");
           return 0;
    }
   +/* The End. */
   $ hg commit -m "Added footer." ##-d "2010-03-12 21:20:15"

The change is not yet on the Subversion server --- like normal, `hg
push` is needed. Pushing the changeset involves rewriting it. This is
because the Subversion server is the master and when you commit to a
Subversion server, it will determine the username and timestamp that
goes into the revision. On the Mercurial side, the changeset is
rewritten to reflect the updated meta data:

.. shelltest::
   :name: alice

   $ hg outgoing \
     ## | sed -e 's|file://.*|http://mercurial.aragost.com/svn/helloX|'
   comparing with http://mercurial.aragost.com/svn/helloX
   changeset:   9:2dc661f0cdc8
   tag:         tip
   user:        Alice <alice@example.net>
   date:        Fri Mar 12 21:20:15 2010 +0000
   summary:     Added footer.
   $ hg push \
     ## | sed -e 's|file://.*|http://mercurial.aragost.com/svn/helloX|' \
              -e 's|\[r11\] .\+:|[r11] alice:|'
   pushing to http://mercurial.aragost.com/svn/helloX
   searching for changes
   [r11] alice: Added footer.
   pulled 1 revisions
   saved backup bundle to $HOME/hello-hg/.hg/strip-backup/2dc661f0cdc8-backup.hg

You can see the updated changeset like this:

.. shelltest::
   :name: alice

   $ ## svn -q propset --revprop -r HEAD svn:date \
            "2010-03-12T21:25:00.0Z" \
            file://$HOME/hello
   $ ## svn -q propset --revprop -r HEAD svn:author \
            "alice" \
            file://$HOME/hello
   $ ## cd ..
   $ ## rm -r hello-hg
   $ ## hg -q clone file://$PWD/hello hello-hg
   $ ## cd hello-hg
   $ hg tip
   changeset:   9:a545b3d1c21d
   tag:         tip
   user:        alice@66d5687c-2042-4ccc-9f0a-7e74790c38a1
   date:        Fri Mar 12 21:25:00 2010 +0000
   summary:     Added footer.

Notice how the author and timestamp changed because the Subversion
server updated the meta data. Because changesets are rewritten, you
should not make further Mercurial clones of your original clone. If
you do so, you will have to manually strip the old versions of
changesets that are pushed to Subversion.

Pulling from Subversion
=======================

Alice makes a couple of more local changes in her Mercurial clone:

.. shelltest::
   :name: alice

   $ ## sed -e "s|Hello, World|Hello, Wonderful World|" -i hello.c
   $ ## hg commit -m "Express greater joy!" -d "2010-03-12 21:26:00"
   $ ## echo "/*" > tmp
   $ ## echo "Copyright (c) 2010 Alice"                                           >> tmp
   $ ## echo                                                                      >> tmp
   $ ## echo " Permission is hereby granted, free of charge, to any person"       >> tmp
   $ ## echo " obtaining a copy of this software and associated documentation"    >> tmp
   $ ## echo " files (the \"Software\"), to deal in the Software without"         >> tmp
   $ ## echo " restriction, including without limitation the rights to use,"      >> tmp
   $ ## echo " copy, modify, merge, publish, distribute, sublicense, and/or sell" >> tmp
   $ ## echo " copies of the Software, and to permit persons to whom the"         >> tmp
   $ ## echo " Software is furnished to do so, subject to the following"          >> tmp
   $ ## echo " conditions:"                                                       >> tmp
   $ ## echo                                                                      >> tmp
   $ ## echo " The above copyright notice and this permission notice shall be"    >> tmp
   $ ## echo " included in all copies or substantial portions of the Software."   >> tmp
   $ ## echo                                                                      >> tmp
   $ ## echo " THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND," >> tmp
   $ ## echo " EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES"   >> tmp
   $ ## echo " OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND"          >> tmp
   $ ## echo " NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT"       >> tmp
   $ ## echo " HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,"      >> tmp
   $ ## echo " WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING"      >> tmp
   $ ## echo " FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR"     >> tmp
   $ ## echo " OTHER DEALINGS IN THE SOFTWARE."                                   >> tmp
   $ ## echo "*/"                                                                 >> tmp
   $ ## cat hello.c >> tmp
   $ ## mv tmp hello.c
   $ ## hg commit -m "Added simple MIT license header." -d "2010-03-12 21:27:00"
   $ hg outgoing \
     ## | sed -e "s|file://.*|http://mercurial.aragost.com/svn/helloX|"
   comparing with http://mercurial.aragost.com/svn/helloX
   changeset:   10:9a6e43d86b16
   user:        Alice <alice@example.net>
   date:        Fri Mar 12 21:26:00 2010 +0000
   summary:     Express greater joy!

   changeset:   11:f3bd093438f1
   tag:         tip
   user:        Alice <alice@example.net>
   date:        Fri Mar 12 21:27:00 2010 +0000
   summary:     Added simple MIT license header.

Meanwhile, the Subversion repository has been changed:

.. shelltest::
   :name: alice

   $ ## cd ..
   $ ## svn -q checkout file://$HOME/hello hello-svn
   $ ## cd hello-svn
   $ ## echo >> trunk/README
   $ ## echo "Please report bugs to <mg@lazybytes.net>." >> trunk/README
   $ ## svn -q commit -m "List email address for bug reports."
   $ ## svn -q propset --revprop -r HEAD svn:date \
            "2010-03-12T21:26:30.0Z" file://$HOME/hello
   $ ## svn -q propset --revprop -r HEAD svn:author \
            "alice" file://$HOME/hello
   $ ## cd ../hello-hg
   $ hg incoming \
     ## | sed -e "s|file://.*|http://mercurial.aragost.com/svn/helloX|"
   incoming changes from http://mercurial.aragost.com/svn/helloX

   revision:    12
   user:        alice
   date:        2010-03-12T21:26:30.0Z
   message:     List email address for bug reports.
   $ hg pull \
     ## | sed -e "s|file://.*|http://mercurial.aragost.com/svn/helloX|"
   pulling from http://mercurial.aragost.com/svn/helloX
   [r12] alice: List email address for bug reports.
   pulled 1 revisions
   (run 'hg update' to get a working copy)
   $ ## snap-tortoisehg "$DSTDIR/pull.png"

The new revision causes a fork in the history:

.. image:: pull.png
   :align: center

When Alice push, her two changesets (``7d780c6154fb`` and
``12445668eefc``) will automatically be rebased on top of the revision
she just pulled from Subversion (``2099057558d2``):

.. shelltest::
   :name: alice

   $ hg push ## 2>& 1 \
      | sed -e 's|............-backup.hg|xxxxxxxxxxxx-backup.hg|' \
            -e 's|file://.*|http://mercurial.aragost.com/svn/helloX|' \
	    -e 's|\(\[r1[34]\]\) .\+:|\1 alice:|'
   pushing to http://mercurial.aragost.com/svn/helloX
   searching for changes
   [r13] alice: Express greater joy!
   pulled 1 revisions
   saved backup bundle to $HOME/hello-hg/.hg/strip-backup/xxxxxxxxxxxx-backup.hg
   [r14] alice: Added simple MIT license header.
   pulled 1 revisions
   saved backup bundle to $HOME/hello-hg/.hg/strip-backup/xxxxxxxxxxxx-backup.hg
   $ ## svn -q propset --revprop -r 13 svn:date \
               "2010-03-12T21:28:00.0Z" \
            file://$HOME/hello
   $ ## svn -q propset --revprop -r 13 svn:author \
               "alice" \
            file://$HOME/hello
   $ ## svn -q propset --revprop -r 14 svn:date \
               "2010-03-12T21:28:00.0Z" \
            file://$HOME/hello
   $ ## svn -q propset --revprop -r 14 svn:author \
               "alice" \
            file://$HOME/hello
   $ ## cd ..
   $ ## rm -r hello-hg
   $ ## hg -q clone file://$PWD/hello hello-hg
   $ ## cd hello-hg
   $ ## snap-tortoisehg "$DSTDIR/pushed.png"

Again, this is necessary since Subversion is a strictly linear system.
The new history looks like this:

.. image:: pushed.png
   :align: center


Summary
=======

The hgsubversion extension lets you use Mercurial as a Subversion
client. This immediately gives you the following advantages compared
to the normal Subversion client:

* Full history on client. This enables the fast bug-hunting techniques
  offered by Mercurial, be it simply searching the history for the
  introduction of a particular string with `hg grep` or `hg annotate`,
  or be it the more advanced binary search done with `hg bisect`.

  People simply begin working differently with their tools when they
  become sufficiently fast. Mercurial is designed to make local access
  fast.

* Local (offline) commits. Version control is all about tracking the
  development of the code. In particular, version control should let
  people experiment with new ideas and features. To do this, people
  need to commit often --- maybe every five minutes when they think they
  have come a little closer to a working solution.

  Subversion forces people to publish these revisions immediately.
  People will therefore tend to hold off on committing, especially to
  the trunk, out of fear of breaking the build. If people work on a
  branch, all these commits will pollute the history and make
  reviewing hard.

  Mercurial lets people commit as often as they feel necessary. The
  changes can later be refined into fewer, and more meaningful commits
  that are easy to review and which avoids a lot of false starts.
  These refined commits are then pushed to the Subversion server.

* Distributed development. You saw above how changesets are rebased
  when pushed to Subversion. This makes it tedious to collaborate with
  others if the changesets on which you are working are continuously
  changed under your feet.

  However, it is possible to work in a distributed fashion on a new
  feature. It merely requires that the collaborators wait with pushing
  to Subversion until they are done. So you can make as many clones as
  you want and push/pull between them as you like. When the feature is
  done, you integrate it in Subversion and ask the rest of your team
  to ditch their local clones and pull the rebased changesets from the
  Subversion server.

The hgsubversion extension can also be used as a way to convert to
Mercurial. Mercurial already ships with a convert extension, but
hgsubversion is known to convert some repositories better and more
accurately.

The main problem in converting from Subversion (or really, any legacy
system) to Mercurial is the lack of rigor in the old systems.
Subversion has no tags and no branches, it only has a (very) strong
convention that says that a copy from ``trunk/`` to ``branches/foo``
indicates the creation of a branch named "foo". But nothing prevents
someone from copying half of ``trunk/`` to ``branches/foo``, or from
making a commit that modifies files in both ``trunk/`` and
``branches/foo`` simultaneously.

This kind of "cruft" cannot always be represented in a meaningful way
in Mercurial, but hgsubversion still does a good job at interpreting
the developers' intentions.

..  LocalWords:  hgsubversion TortoiseHg hg thg svn hgignore