If I understand them correctly (and I'm far from an expert on each) they fundamentally each have a different philosophy. I first used mercurial for 9 months. Now I've used git for 6.
hg is version control software. It's main goal is to track versions of a piece of software.
git is a time based file system. It's goal is to add another dimension to a file system. Most have files and folders, git adds time. That it happens to work awesome as a VCS is a byproduct of its design.
In hg, there's a history of the entire project it's always trying to maintain. By default I believe hg wants all changes to all objects by all users when pushing and pulling.
In git there's just a pool of objects and these tracking files (branches/heads) that determine which set of those objects represent the tree of files in a particular state. When pushing or pulling git only sends the objects needed for the the particular branches you are pushing or pulling, which is a small subset of all objects.
As far as git is concerned there is no "1 project". You could have 50 projects all in the same repo and git wouldn't care. Each one could be managed separately in the same repo and live fine.
Hg's concept of branches is branches off the main project or branches off branches etc. Git has no such concept. A branch in git is just a state of the tree, everything is a branch in git. Which branch is official, current, or newest has no meaning in git.
I don't know if that made any sense. If I could draw pictures hg might look like this where each commit is a o
o---o---o
/
o---o---o---o---o---o---o---o
\ /
o---o---o
A tree with a single root and branches coming off of it. While git can do that and often people use it that way that's not enforced. A git picture, if there is such a thing, could easily look like this
o---o---o---o---o
o---o---o---o
\
o---o
o---o---o---o
In fact in some ways it doesn't even make sense to show branches in git.
One thing that is very confusing for the discussion, git and mercurial both have something called a "branch" but they are not remotely the same things. A branch in mercurial comes about when there are conflicts between different repos. A branch in git is apparently similar to a clone in hg. But a clone, while it might give similar behavior is most definitely not the same. Consider me trying these in git vs hg using the chromium repo which is rather large.
$ time git checkout -b some-new-branch
Switched to new branch 'some-new-branch'
real 0m1.759s
user 0m1.596s
sys 0m0.144s
And now in hg using clone
$ time hg clone project/ some-clone/
updating to branch default
29387 files updated, 0 files merged, 0 files removed, 0 files unresolved.
real 0m58.196s
user 0m19.901s
sys 0m8.957
Both of those are hot runs. Ie, I ran them twice and this is the second run. hg clone is the actually the same as git-new-workdir. Both of those make an entirely new working dir almost as though you had typed cp -r project project-clone
. That's not the same as making a new branch in git. It's far more heavy weight. If there is true equivalent of git's branching in hg I don't know what it is.
I understand at some level hg and git might be able to do similar things. If so then there is a still a huge difference in the workflow they lead you to. In git, the typical workflow is to create a branch for every feature.
git checkout master
git checkout -b add-2nd-joypad-support
git checkout master
git checkout -b fix-game-save-bug
git checkout master
git checkout -b add-a-star-support
That just created 3 branches, each based off a branch called master.
(I'm sure there's some way in git to make those 1 line each instead of 2)
Now to work on one I just do
git checkout fix-game-save-bug
and start working. Commit things, etc. Switching between branches even in a project as big as chrome is nearly instantaneous. I actually don't know how to do that in hg. It's not part of any tutorials I've read.
One other big difference. Git's stage.
Git has this idea of a stage. You can think of it as a hidden folder. When you commit you only commit what's on the stage, not the changes in your working tree. That might sound strange. If you want to commit all the changes in your working tree you'd do git commit -a
which adds all the modified files to the stage and then commits them.
What's the point of the stage then? You can easily separate your commits. Imagine you edited joypad.cpp and gamesave.cpp and you want to commit them separately
git add joypad.cpp // copies to stage
git commit -m "added 2nd joypad support"
git add gamesave.cpp // copies to stage
git commit -m "fixed game save bug"
Git even has commands to decide which particular lines in the same file you want to copy to the stage so you can split up those commits separately as well. Why would you want to do that? Because as separate commits others can pull only the ones they want or if there was an issue they can revert just the commit that had the issue.