I think git is one of the best tools we have, but its UI is really bad:
checkout and reset do completely different things when given files or when not given files.
reset on files should really have been called unadd. reset on refspecs should really have been jumpto, moveto or something else indicative that the current branch ptr is moved to a new refspec. --soft and friends could have been --no-update-index or --no-update-files.
checkout on files should really have been called overwrite. checkout on branch names should have probably been switch, setcurrentbranch or a name indicative that the current branch is being changed.
pull and push are symmetric names for asymmetric behavior. pull could have been a flag for merge (-f meaning fetch first).
reset --hard was for a long time the only way to move a branch ptr to a new position along with the files, but it has the potentially unintended consequence of also irreversibly deleting working tree changes. If you use it to delete, that's fine, but since you had to use it to move the branch ptr, it is simply wrong to have irreversible damage as a side effect. Especially in an RCS which is used by many as the fail-safe against their own user mistakes.
There's no easy way to see which branches are tracking what. And until recently it was a big PITA to even make the current branch track a remote branch.
Deleting remote branches has awkward syntax (pushing an empty string to a branch name) and then you have to use a specialized command (remote prune) if you want the deletion to be propagated to other repositories.
Another annoyance: Git doesn't let you push a detached head to a new remote branch, so you have to create a temp branch ptr to the detached head position and later delete it.
Git also doesn't have good support for versioned sub-projects. submodule is sub-par, and requires a multitude of extra commands even in the cases that should have been seamless.
"checkout and reset do completely different things when given files or when not given files.
reset on files should really have been called unadd. reset on refspecs should really have been jumpto, moveto or something else indicative that the current branch ptr is moved to a new refspec. --soft and friends could have been --no-update-index or --no-update-files."
I can understand your confusion, given the seemingly separate use cases for reset, but in fact, it makes perfect sense. Reset always does what it says it does. Let's break it down:
git reset --mixed <commit> will make your current HEAD point to <commit>, reset the index to <commit>, and leave your working tree alone. This is useful for "uncommitting" the last commit, e.g. so you can split it up into smaller commits. Example:
git commit -am "lots of changes"
# realize you should really do better
git reset --mixed HEAD~1
git add myfile.py
git commit -m "implemented feature x"
git add yourfile.py
git commit -m "bugfix #3182"
Handy. Now let's look at the "unadd' scenario:
git add dontstage.py
git reset HEAD dontstage.py == git reset --mixed HEAD dontstage.py, since --mixed is the implicit default
git doesn't touch your commits, since you are already on HEAD. Git does reset the index to HEAD, which is before you added dontstage.py. If you had other changes that you added, it won't reset those, since you provided the limiter of dontstage.py. Git does not touch your working tree, so dontstage.py stays modified. The end result? Your working tree, index, and commits look exactly like before you ran git add dontstage.py.
Now, if someone (e.g. easy git: http://people.gnome.org/~newren/eg/) wants to make git reset HEAD to unadd, that's fine by me. I'm speculating here, but I imagine that the Linus/git dev point of view is, why call it anything other than exactly what it is? It's just nice and elegant that it happens to suffice multiple use cases.
The more you get into git, the more you start to realize why some of the commands that seemed arcane in the beginning are simple and elegantly named.
Even after your explanation, the name "reset" and "--mixed" make no sense to me. "reset" is not indicative of what's being reset. "--mixed" is almost meaningless. "--soft" and "--hard" are also mostly meaningless.
I'm OK with having a low-level primitive like "reset" that doesn't have a simple meaning so cannot have a meaningful name. But then, it should be wrapped with meaningful commands such as "moveto" with flags to avoid touching index or working tree, and "unadd" on top of "reset". Then, I don't think anyone would ever use reset directly, so it would probably be phased out :-)
Mercurial has the same ability to change history as git, it just doesn't make it the default workflow. The mq extension (Mercurial Queues) lets you pull existing changesets into a temporary queue, fold them, reorder them, etc. The rebase extension lets you rebase and the histedit extension lets you edit history in slightly different ways.
All of these ship with Mercurial, but are turned off be default. Enabling them is just a matter of adding
None of that says "really bad UI". Quirky, sure. Not as straightforward as others. Could definitely be improved. But not "really bad".
Though I will add that the index is a horribly named concept and it really bugs me that different commands use different names for it ("--cached", but sometimes "--index"). They need to rename it to "staging" and change all the command line options to --staging (keeping the old ones as hidden backward compatible options, of course... "diff --cached" is engrained in my memory at this point). I think that would make things more consistent and clear.
The good thing is: "how it works" is really simple.
You should treat it like a language (just like all system/unix tools), not an "app".