19 Jul 2010

Git vs Mercurial (hg)

Lately I'm playing with Mercurial (hg) and Git but I couldn't tell the difference between the two. Actually it is quite thin but noticable. Here's a comparison of hg vs git and my conclusion

Sorry for the format but these are notes I took while playing with hg and git. I 'm too lazy to format them in html ;)

# this comparison assume that you have 
# a ~/.gitconfig and a ~/.hgrc configured 
# with your username (otherwise you need to 
# add your username at every commit)
mkdir /tmp/scm-tests
cd /tmp/scm-tests

# create a central repo
mkdir [scm]-central
git: 
  git init --bare git-central 
# bare: you need to look at the man page to 
# find this - needed otherwise you not be 
# able to do push (you will need to go to central and pull)
hg:
  hg init hg-central 
# on the contrary hg assume by default 
# that you can push (might be seen as 
# a security issue - that doesn't bother me)

# user1 clone central 
mkdir [scm]-user1
git:
  git clone /tmp/scm-tests/git-central/ git-user1/ 
# you will get a 'warning: You appear to 
# have cloned an empty repository.' I don't 
# understand why they putted that ?! what's the problem here ?
hg:
  hg clone /tmp/scm-tests/hg-central/ hg-user1/

# add and commit 2 initial files
$ cd [scm]-user1
$ ls 
OTHER.TXT  README.TXT
$ cat OTHER.TXT 
other file
$ cat README.TXT 
readme file

git:
  git add README.TXT OTHER.TXT
  git commit -a -m "user1 modif1" 
# using the 'a' option or not 
# could be a post on itself 
hg:
  hg add README.TXT OTHER.TXT
  hg commit -m "user1 modif1"
# pushing modification to central repo
git:
  git push origin master 
# here you get some obscure 
# message in the output like 
# 'Delta compression using up to 2 threads'
hg:
  hg push default

# creating user2 and cloning central
$ cd /tmp/scm-tests
mkdir [scm]-user2
git:
  git clone /tmp/scm-tests/git-central/ git-user2/ 
hg:
  hg clone /tmp/scm-tests/hg-central/ hg-user2/
  
# - - - - - - - - - - - - - - - - - - - -
# concurrent modification of different files
# - - - - - - - - - - - - - - - - - - - -
modify [scm]-user1/README.TXT
modify [scm]-user2/OTHER.TXT

git:
  cd git-user1
  git commit -a -m "user1 modif"
  cd ../git-user2  
  git commit -a -m "user2 modif"
hg:
  cd git-user1
  hg commit -m "user1 modif"
  cd ../git-user2
  hg commit -m "user2 modif"

# push user2 modif to central
git:
  git push origin
hg:
  hg push default

# pull from user1
cd ../[scm]-user1
git:
  git pull origin master 
# I always have a doubt if it's 
# origin master or the opposite
hg:
  hg pull default 
# notice that you don't need to tell 
# hg where you want to send the changes 
# it assume it's the current dir
...
(run 'hg heads' to see heads, 'hg merge' to merge)
  # this message is important because it means you 
  # have to issue 2 extra command to merge otherwise you can't push
  # the merge will be easy but in this case git just does it !
  hg merge # we don't specify any revision, 
  # hg is smart enough to find the right one
  hg commit -m "Merge" # by default in tortoisehg the 
  # commit message is already configured - don't know if it's 
  # a tortoisehg feature...

# push user1 modif to central
git:
  git push origin
hg:
  hg push default

# pull user1 modif in user2 folder
cd ../[scm]-user2
git:
  git pull origin master
hg:
  hg pull default
...
(run 'hg update' to get a working copy)
  # again this message is important, hg fetched the central 
  # repo info, but didn't moved you automatically on the latest revision
  # we could have done this by using the -update option, 
  # but I wanted to show you this situation
  hg update # another extra command compare to git

# diplay graph of what we made
git:
  git log --graph --color # notice the commit 'Merge branch...' was done 
  # automatically, we did nothing (except push and pull)
*   commit 817f6e11a14530c6b09502edd069895ab19d8aea
|\  Merge: b0647d4 c0a4f25
| | 
| |     Merge branch 'master' of /tmp/scm-tests/git-central
| |   
| * commit c0a4f259c33f8d8876088b08703776bb2739bb4a
| | 
| |     user2 modif
| |   
* | commit b0647d415b30a407f0367ff96e5a417956d83d93
|/  
|   
|       user1 modif
|  
* commit 6ffe857f8ece5ea7df051975b36c4384bdbe1383

hg: # didn't found any text based graphical tool 
# at first but apparently there is an 
# extension off by default: 
# http://mercurial.selenic.com/wiki/GraphlogExtension
  hg log
hangeset:   3:2e322ec563c5
tag:         tip
parent:      2:995f574cea3d
parent:      1:4d258b644a75
summary:     Merge

changeset:   2:995f574cea3d
parent:      0:a3ebb2490ce5
summary:     user1 modif

changeset:   1:4d258b644a75
summary:     user2 modif

changeset:   0:a3ebb2490ce5
summary:     user1 modif1

# git and hg: same number of revisions

# - - - - - - - - - - - - - - - - - - - -
# concurrent modification of the same file (with conflict)
# - - - - - - - - - - - - - - - - - - - -
modify [scm]-user1/README.TXT
modify [scm]-user2/README.TXT
the same line
commit code omitted

# push user1 to central
git:
  git push origin
hg:
  hg push default

# pull central to user2
cd ../[scm]-user2
git:
  git pull origin master
...
Auto-merging README.TXT
CONFLICT (content): Merge conflict in README.TXT
Automatic merge failed; fix conflicts and then commit the result.
hg:
  hg pull default
...
(run 'hg heads' to see heads, 'hg merge' to merge)
  hg merge # extra command 
merging README.TXT
warning: conflicts during merge.
merging README.TXT failed!
...
use 'hg resolve' to retry unresolved file merges or 'hg update -C' to abandon

# fix conflict by editing file
vi README.TXT

# mark file as merged
git:
  git add README.TXT
  git commit -a -m "fixing conflict"
hg:
  hg resolve -m README.TXT # this m option was hard to find
  hg commit -m "fixing conflict"
  rm README.TXT.orig # I don't know if it's me not 
  # using hg properly or hg behavior but we need to 
  # cleanup manualy some backup file :(
  
# push user2 conflict fix to central
git:
  git push origin
hg: 
  hg push default

# update user1
cd ../[scm]-user1
git:
  git pull origin master
hg:
  hg pull default
...
run 'hg update' to get a working copy)
  hg update # extra command

# diplay graph of what we made
git:
  git log --graph --color
 # notice that this time there is no automatic merge message but ours
*   commit 58e0ec598f272ac5d4dff02b7a9d0e3842a08c81
|\  Merge: 7c4b4dc e176f07
| | 
| |     fixing conflict
| |   
| * commit e176f07da2db925528c7a068af5f558484af6f56
| | 
| |     user1 modif conflict
| |   
* | commit 7c4b4dc8cbbb49138312df41fe19f2b2dee90d2b
|/  
|   
|       user2 modif conflict
|    
*   commit 817f6e11a14530c6b09502edd069895ab19d8aea
...

hg:
  hg log
changeset:   6:6f8dceccba2d
tag:         tip
parent:      5:32085fee1b07
parent:      4:7417be83b483
summary:     fixing conflict

changeset:   5:32085fee1b07
parent:      3:2e322ec563c5
summary:     user2 modif2

changeset:   4:7417be83b483
summary:     user1 modif2

changeset:   3:2e322ec563c5
...

# git and hg: same number of revision: 3

# - - - - - - - - - - - - - - - - - - - -
# conclusion
# - - - - - - - - - - - - - - - - - - - -
learning curve (if you already know svn):
git:  not easy, need regular practice and at least 
one week to become familiar with command line args
hg:   1 day, just feels like svn

commands:
git:  21 commands with (obscure) command line arguments
hg:   50 (obvious) commands

branches:
git:  merging from branch is done with one command line
hg:   you need to type extra (useless) command to merge in obvious (non conflicting changes) case. 
Apparently the extra (useless) commands behavior can be change by tuning some conf parameter: 
http://hgbook.red-bean.com/read/a-tour-of-mercurial-merging-work.html#sec:tour-merge:fetch

vocabulary:
git:  origin
hg:   default

windows support:
git:  lame
hg:   good (tortoisehg)

linux support:
git:  good (git help are man pages)
hg:   good enough

merge conflict:
git: easy 
hg:  less obvious / elegant

me:
I like both
hg:  because it's so easy to use 
git: for it's elegant way of handling merge and branches