Home > Uncategorized > Cloning a clone: Hg vs Git

Cloning a clone: Hg vs Git

I’m in the progress of learning about git and mercurial (Hg), and so you’ll probably see a number of posts from me about this subject.

I know, I know, cue the “git rawks!”, “Hg is da bomb!”, and “they’re both lame, stick with SVN!” debates… anyways, I’m coming at this from a total newbie’s perspective. In the past I’ve written about TortoiseGit and some of the issues I had, so I decided to skip the GUI and go commando command line only.

The first experiment I’ll try where I pit one DVCS against the other is the “cloning a clone” scenario. In the mercurial tutorial, this is described as the “blessed” way of doing things – see the section Making and Reviewing Changes. Whenever you have an experimental change to make, you should clone your local repo (which is itself a clone of someone else’s) and push changes up to your primary cloned repo when they’re stable. Then push them up to the place you cloned them from later. Also, this is described/required by the Integrator workflow in the Pro Git book, specifically Chapter 5.1 – Distributed workflows.

Cloning a Clone: Hg

In the spirit of brevity, I’ll just stick to a terse command line dump. I’m working on Ubuntu 9.10 with Hg 1.3.1, which is installed via

sudo apt-get install hg

First things first… let’s clone someone’s public repo. Heck, how about hgsubversion?

atto@zues:~$ hg clone http://bitbucket.org/durin42/hgsubversion
destination directory: hgsubversion
requesting all changes
adding changesets
adding manifests
adding file changes
added 608 changesets with 1432 changes to 181 files
updating working directory
144 files updated, 0 files merged, 0 files removed, 0 files unresolved

So far, so good. I now have a clone of hgsubversion. Now, let’s clone the clone.

atto@zues:~$ hg clone hgsubversion my-hgsubversion
updating working directory
144 files updated, 0 files merged, 0 files removed, 0 files unresolved

Yawn. This works so flawlessly, it is actually boring me to sleep.

What happens if I now make conflicting changes in each of the repos, then try to mash those conflicts together into an unholy mess?

Edit setup.py line 42 in both repos, commit, etc.

atto@zues:~/hgsubversion$ hg status
M setup.py
atto@zues:~/hgsubversion$ hg commit -m "Conflict in remote"
atto@zues:~/hgsubversion$ cd ../my-hgsubversion
atto@zues:~/my-hgsubversion$ gvim setup.py (make changes)
atto@zues:~/my-hgsubversion$ hg commit -m "Conflict in clone clone"

Ok, it took longer to type this post than it took to make these changes. Boring so far…

At this point, I have two conflicting changes – one in my clone of hgsubversion, and one in my clone-clone of hg-subversion. Now, a sensible thing to do would be to push changes from the clone-clone up to the clone (pretend it’s an integration branch in SVN speak, or a testing playground of sorts).

atto@zues:~/my-hgsubversion$ hg outgoing
comparing with /home/atto/hgsubversion
searching for changes
changeset:   608:5776ac5c7b12
tag:         tip
user:        Foo Bar
date:        Wed May 12 21:25:09 2010 -0700
summary:     Conflict in clone clone

atto@zues:~/my-hgsubversion$ hg push
pushing to /home/atto/hgsubversion
searching for changes
abort: push creates new remote heads!
(did you forget to merge? use push -f to force)
atto@zues:~/my-hgsubversion$ hg incoming
comparing with /home/atto/hgsubversion
searching for changes
changeset:   608:45a0d7d3e796
tag:         tip
user:        Foo Bar
date:        Wed May 12 21:24:40 2010 -0700
summary:     Conflict in remote
atto@zues:~/my-hgsubversion$ hg pull
pulling from /home/atto/hgsubversion
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files (+1 heads)
(run 'hg heads' to see heads, 'hg merge' to merge)

So, I tried to push up some changes, but doing so would’ve created a two-headed monster, so Hg told me “did you forget to merge? use push -f to force”. That’s actually pretty friendly, maybe I should do a merge.

atto@zues:~/my-hgsubversion$ hg merge
merging setup.py
QFSFileEngine::open: No file name specified
0 files updated, 1 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
atto@zues:~/my-hgsubversion$ hg stat
M setup.py
atto@zues:~/my-hgsubversion$ hg diff
diff -r 5776ac5c7b12 setup.py
(cut diff)
atto@zues:~/my-hgsubversion$ hg commit -m "Fix merge issues. Bad developer, no pizza"

The odd message about QFSFileEngine::open: was the point in time where Kdiff3 popped up to help me resolve the merge. No surprises here, the merge went off flawlessly as expected, cool.

Now I can push my changes (dumb as they are) up to the original clone:

atto@zues:~/my-hgsubversion$ hg outgoing
comparing with /home/atto/hgsubversion
searching for changes
changeset:   608:5776ac5c7b12
user:        Foo Bar
date:        Wed May 12 21:25:09 2010 -0700
summary:     Conflict in clone clone

changeset:   610:b333c539af3d
tag:         tip
parent:      608:5776ac5c7b12
parent:      609:45a0d7d3e796
user:        Foo Bar
date:        Wed May 12 21:29:59 2010 -0700
summary:     Fix merge issues. Bad developer, no pizza

atto@zues:~/my-hgsubversion$ hg push
pushing to /home/atto/hgsubversion
searching for changes
adding changesets
adding manifests
adding file changes
added 2 changesets with 2 changes to 1 files

Cool. That just… worked. I don’t think I was confused for even a second, things just worked as I would expect them to. I created a conflict I knew Hg couldn’t automatically resolve, it told me what to do, and it took <5 min total to run the experiment.

How about git?

Cloning a Clone: Git

Here I’m installing git-core and git-svn, git-svn isn’t strictly necessary but if I get time I’d like to experiment with pulling (and hopefully pushing) to an SVN repo. If it works, then I can use git all day long and seamlessly push to SVN when my changes are stable.

sudo apt-get install git-core git-svn

Now, let’s clone a public repo so we can experiment and have a little fun.

atto@zues:~$ git clone http://github.com/schacon/hg-git.git
Initialized empty Git repository in /home/atto/hg-git/.git/
got f5493088320f5587bcbcf701bd82ce6625a50e27
walk f5493088320f5587bcbcf701bd82ce6625a50e27
(cut 200+ lines of debug-type info)
walk 01bddec68dab48693af40968567ce4cff535f269

OK, it’s a little verbose – I mean, come on, do I really need to care about each and every changeset and it’s hash? But hey, at least it worked with no issues.

Now let’s clone the clone…

atto@zues:~$ git clone hg-git my-hg-git
Initialized empty Git repository in /home/atto/my-hg-git/.git

Fast forward a bit, I’m gonna skip the part where I make conflicting changes and commit them to the two different clones. BTW, don’t forget to do a “git add” before committing, which is annoying but probably a useful feature somehow.

Now, how do we tell what changes are ready to go out without actually pushing them?

There is no “git outgoing”, but after fishing around a bit I discovered that “git status” has some useful information:

atto@zues:~/my-hg-git$ git status
# On branch master
# Your branch is ahead of 'origin/master' by 1 commit.
# Untracked files:
#   (use "git add ..." to include in what will be committed)
#	setup.py~
nothing added to commit but untracked files present (use "git add" to track)

Hmmm OK so I’ve got one commit that needs pushing, still not sure which one but I’ll leave that be for now.

Of course, doing a “git push” isn’t smart enough to figure out where to push to or what branch to push… so I finally ended up with

atto@zues:~/my-hg-git$ git push origin master
To /home/atto/hg-git
 ! [rejected]        master -> master (non-fast forward)
error: failed to push some refs to '/home/atto/hg-git'

Well, this error mesage looks annoyingly familiar… is this git’s unique way of telling me that I need to pull and merge first?

atto@zues:~/my-hg-git$ git pull origin master
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 2), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /home/atto/hg-git
 * branch            master     -> FETCH_HEAD
Auto-merging setup.py
CONFLICT (content): Merge conflict in setup.py
Automatic merge failed; fix conflicts and then commit the result.

Cool, that did something useful-ish. It didn’t pop up kdiff3, or any editor for that matter – but perhaps that’s a matter of configuration? Anyways, off I go to gvim and resolve the conflict by hand.

atto@zues:~/my-hg-git$ git add setup.py
atto@zues:~/my-hg-git$ git commit -m "Fix for conflict in clone clone"
[master fabff96] Fix for conflict in clone clone

With any luck, now I can finally push my two changesets (the conflict and the conflict fix) to origin master:

atto@zues:~/my-hg-git$ git push origin master
Counting objects: 10, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 730 bytes, done.
Total 6 (delta 4), reused 0 (delta 0)
Unpacking objects: 100% (6/6), done.
warning: updating the current branch
warning: Updating the currently checked out branch may cause confusion,
warning: as the index and work tree do not reflect changes that are in HEAD.
warning: As a result, you may see the changes you just pushed into it
warning: reverted when you run 'git diff' over there, and you may want
warning: to run 'git reset --hard' before starting to work to recover.
warning: You can set 'receive.denyCurrentBranch' configuration variable to
warning: 'refuse' in the remote repository to forbid pushing into its
warning: current branch.
warning: To allow pushing into the current branch, you can set it to 'ignore';
warning: but this is not recommended unless you arranged to update its work
warning: tree to match what you pushed in some other way.
warning: To squelch this message, you can set it to 'warn'.
warning: Note that the default will change in a future version of git
warning: to refuse updating the current branch unless you have the
warning: configuration variable set to either 'ignore' or 'warn'.
To /home/atto/hg-git
   be73d6a..fabff96  master -> master

Well, that’s downright confusing, but at the end of the barf message, it looks like something happened. Trotting over to the original clone and checking the log, I can see that I now have three new commits – one in the clone, two in the clone-clone.

So… I think it worked.

Just to be sure, I opened setup.py and checked the line in question… and it’s not been updated.

Well, in SVN and Hg you do an “update” command to bring the working copy into latest state… does that work for git?

atto@zues:~/hg-git$ git update
atto@zues:~/hg-git$ git help
(nothing useful)

Argh. I know this one. It’s somewhere in my brain…. processing…. processing… the lights are on but nobody’s home.

Oh yeah, the log above said something about running “git reset –hard”. That sounds a bit scary, but these are dummy test repos so who cares if I blow them away by accident?

atto@zues:~/hg-git$ git reset --hard
HEAD is now at fabff96 Fix for conflict in clone clone

Oooooooh yeah, now that’s what I’m talking about… suddenly, setup.py has the new, updated, correct information.

Why does that take a 2-page error/warning message?

So, at this point, I’ve successfully cloned a clone, and pushed conflicting changes up from the clone-clone to the clone – just the same as in Hg.


It doesn’t take a genius to see two things right off the bat:

  1. git’s error mesages range from confusing to overly verbose to completely useless
  2. git required more time reading help files and guessing which commands to run

Sure, #2 could be explained quite simply by my lack of understanding, my complete idiot self not reading enough documentation and manuals, me being a typical user. And… guess what? That’s correct.

I didn’t read any documents, manuals, tutorials, or howtos. And I shouldn’t have to, not for simple things like conflict resolution and cloning (aka checkouts in SVN speak).

Either way, git’s apparent lack of useful error messages makes it critically apparent that git wasn’t designed for ease of use or to ease the transition from any other SCM. Git was designed by Linus as a change/patch management tool for serious kernel hackers, and even now, years later… it’s just not easy to switch.

And Hg?

Well, there are other issues with Hg (like HTTPS behind a corporate proxy, for example) that are well known and documented. But for now, as I sit here writing this incredibly long and boring post… Hg just works for me.

I didn’t have to read the manual, I just started typing commands. What little bit I remembered about Hg is floating around from reading the Hg book several months ago, and so I’m starting basically at ground level.

I want to like git… I’ve read of good things about git… but the barriers to entry are significantly higher, such that I’m having a hard time learning how to use it. Trying to teach a team of other developers to use git? Wow, I’m not even sure I want to think about that. My head is spinning just imagining how to explain to someone why they have to “git add” before a “git commit”, much less what “origin” and “master” are and why you have to “git reset -hard” whenever you push.

So I guess that’s the bottom line… as a newbie idiot user, git confuses the heck out of me, and Hg just works.



Categories: Uncategorized Tags: , , ,
  1. May 13, 2010 at 12:33 am

    To be honest, “cloning the clone” is not “the real git way”. In git, you just have multiple local branches in ONE repository, there is no need for the second one at all. *Then*, working with local branches and switching between them in milliseconds, merges, etc, would be much more productive.

    While in Hg, dealing with multiple branches in the same repo is much more awkward and confusing. So, Hg ends up recommending “clone the clone”, where it behaves better than git, and git ends up recommending “local branches” where it behaves much better than Hg. Depending on which style you prefer, you could giv very different marks to hg and git. 🙂

    • May 12, 2010 at 11:39 pm

      Yes, I remember reading that somewhere now. Thanks for the reminder. However, the Pro Git book mentions the integrator workflow where each developer pushes to their public repo (a clone of blessed) from their private repo (also a clone of blessed). So I wanted to test this out. Any experience/recommendations?

  2. May 13, 2010 at 1:59 am

    I’ve basically used that workflow on a recent project of mine[1]. My public repository was essentially the “canonical” version, everyone did work on their own (branched) repositories and pushed to their public repos as they went along. When a feature was ready to merge in, the person would tell me (in git parlance, a “pull request”), and I’d merge their remote into my repo, then push back up to my public repo.

    FWIW, though I never used a SVN GUI, I spent my first weeks of git *heavily* using git-gui until I figured out how to *really* handle branches & remotes. I don’t use the GUI anymore, but I still rely on Github’s network visualization, or the gitk utility to get a commit-to-commit view when I’m watching several branches or remotes.

    Git’s ability to juggle different remotes and different local branches within one actual local repository is amazing once you get used to it.

    [1]: http://github.com/mtigas/cs4970_capstone/network

  3. baczek
    May 13, 2010 at 2:05 am

    the public repo you speak about is usually a bare repo which wouldn’t cause ‘updating the currently checked out branch’ problem.

  4. blah
    May 13, 2010 at 4:56 am

    You’ve misinterpreted the integrator workflow I think, if you think what you did is somehow necessary for it.

    Note the “blessed repository” is the public (at least shared with dev team), bare repo (i.e. presumably http://github.com/schacon/hg-git.git in your example). As integrator, you just clone that _once_ to a local repo A, and push to it as necessary from the integration branch in your local repo A (you’d probably just use master for integration). In your local repo A, you’ll have also fetched the public branches from other developer’s public bare repos (mirrors of their local repos) as remotes for merging.

    As integrator, you certainly don’t need a crazy two-local-cloned-repo-monte dance. There’s nothing actually stopping you making multiple local repos with git either, of course.

    Buyt the way you did it, one of your local repos is downstream from the other local repo, as far as I can make out. Again, strange thing to do. Even if you wanted to keep your integration and development work in separate local repos for some reason (and there’s relatively little point doing that when you could just use two branches in the one repo. If you just want two simultaneous working trees, learn about /usr/share/doc/git/contrib/workdir/git-new-workdir), you’d probably want them both to be downstream from the blessed repo, and have another public bare repo B’ that mirrors your local dev repo B that you push to from B when you’ve got your developer rather than integrator hat on. That way, someone else could take over as integration manager with a mininum of hassle.

    The more you know about lesser source control systems, the harder git will seem, because’s stuff to unlearn.

    • May 13, 2010 at 4:53 am

      Not sure I follow the “lesser source control systems” balone, but you’re right I must have misread the integrator flow. Either way, I see nothing strange about wanting to clone a clone, there are probably legitimate reasons for doing so. Thanks for the advice and I appreciate the feedback.

    • May 14, 2010 at 8:13 am

      Spent some more time with git yesterday, and now re-reading your post I realized how wrong I was about the whole thing. You’re right, this clone-clone stuff is nonsense, definitely not required by the integrator workflow. Look for a (hopefully less wrong) follow up post soon.

  5. asdasd
    June 9, 2010 at 2:06 pm

    > BTW, don’t forget to do a “git add” before committing, which is annoying but probably a useful feature somehow.

    I admit at first it is annoying, but it’s also pretty useful sometimes when you have 2 modified files and you want to commit only one of them.

    also, “git gui” opens up a window that makes it easy to select which files to commit, to review the diffs, to write the commit message and to push. the gui is pretty limited (for example, it doesn’t have pull) but it’s pretty comfty for such things

    • June 9, 2010 at 1:21 pm

      Thanks for your comment. Yes, I can see how that is useful – do you know if there’s a way to commit part of a file? For example, sometimes I get deep into debugging an issue and solve an unrelated bug. I’d like to commit twice, without having to save off a patch or unapply/reapply changes.

      • Pesto
        July 28, 2010 at 2:46 pm

        There is a way to add part of a file… sort of.

        * edit foo.src
        * git add foo.src
        * edit foo.src, again

        If you run git commit now, only those changes that existed at the time you ran git add will be committed. This is nice, as it allows you to incrementally build up a single commit as you work.

      • jason
        August 14, 2010 at 12:52 am

        In git gui, when you are looking at the changes, you can right-click on lines and just add the line, or just add the patch. This lets you just commit the exact lines that you want.

      • August 29, 2010 at 7:37 pm

        That is fetching awesome. I need to start using this. Thanks for the comment!

  1. January 2, 2011 at 8:13 pm

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s

%d bloggers like this: