Archive

Posts Tagged ‘hg’

Cloning a clone: Hg vs Git

May 12, 2010 13 comments

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
atto@zues:~$

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
atto@zues:~$

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"
atto@zues:~/my-hgsubversion$

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)
atto@zues:~/my-hgsubversion$

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
atto@zues:~/my-hgsubversion$

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
atto@zues:~$

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
atto@zues:~$ 

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'
atto@zues:~/my-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.
atto@zues:~/my-hg-git$ 

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:
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:
warning: To squelch this message, you can set it to 'warn'.
warning:
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
atto@zues:~/my-hg-git$

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.

Conclusions

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.

Best,

Atto

Categories: Uncategorized Tags: , , ,