Git needs a new interface
I’ve been a git advocate for a while, and I use git in two different projects. I think git is an impressive technical accomplishment, but I think its interface (”porcelain”) is not ready for prime-time. I really hope some UI-focused person will design a “v2″ for the git interface so that someday git can be the obvious choice for version control for any project.
Specific problems:
“checkout” is a destructive command
I seriously have no idea what Linus was thinking. It is insanity that:
$ git checkout foo.c
…will overwrite any local modifications you may have to foo.c without asking.
You can’t merge upstream changes into your local, uncommitted modifications
Suppose I cloned some repository and I started hacking it up. My changes are still hacky and not ready to be committed. Say a few days later I want to pull upstream changes, but without committing my hacky changes to my local repository:
$ git pull remote: Counting objects: 5, done. remote: Compressing objects: 100% (2/2), done. remote: Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. From /tmp/foo e726259..e20c51a master -> origin/master Updating e726259..e20c51a error: Entry 'foo.c' not uptodate. Cannot merge.
Git is refusing to perform a merge of my local modifications with the upstream changes. It wants me to commit my local changes first. This is annoying. Every version control system I have ever used supports this, except Git. Asking me to commit my hacky changes is unreasonable; they’re hacky and unfinished. They might not even compile!
Yes, I could do:
$ git stash $ git pull $ git stash apply
But why should I have to do this? CVS, SVN, and P4 don’t make me.
Git’s merge conflict resolution workflow is unintuitive
Continuing with the above example, now suppose I committed my local changes and then did a pull, but the changes were conflicting:
$ git pull Auto-merged foo.c CONFLICT (content): Merge conflict in foo.c Automatic merge failed; fix conflicts and then commit the result.
Ok, git is somewhat helpful here, I’ll fix the conflicts in foo.c and commit the result:
$ vim foo.c $ git commit foo.c: needs merge foo.c: unmerged (f388ef85dd65c39e4c76f5e597d3b67f7d1a0726) foo.c: unmerged (6f4bf54585ae256236c0d6cfa9f114affb94313f) foo.c: unmerged (06c974ebbfc04394f4fad8a6dcb31e64866fa1bf) error: Error building trees
Ok, maybe it’s obvious to experienced git users what my error is here, but git’s error message here is worse than unhelpful — it’s downright confusing. I think I’ve resolved the conflict, but all git can think to do is tell me is that “foo.c needs merge” and spit some SHA1’s at me. It gives me absolutely no help about what I need to do to fix the problem.
Suppose that I want to resolve the merge by using either my version or their version verbatim (”accept mine”/”accept theirs”):
$ git checkout foo.c
error: path 'foo.c' is unmergedAgain, unhelpful (and in this case, what I’m trying to say actually makes sense, git just won’t let me do it).
Interface for working with the index almost universally confusing
I understand the difference between the working directory, the index, and the committed tree pretty well. But I cannot for the life of me remember the difference between:
$ git reset --soft $ git reset --hard $ git reset --mixed
I can barely keep them straight while I’m reading the manpage. “Soft” resets HEAD but not the working directory or index. “Mixed” resets HEAD and the index, but not the working directory. “Hard” reset HEAD, the index, and the working directory.
In conclusion, this isn’t meant to be an exhaustive list of problems with git’s interface, it’s more meant to be a microcosm. Git’s interface is not intuitive or easy to learn, and its error messages are not helpful. Which is too bad, because as I said I think Git is solid technology. I just hope someone writes a better porcelain for it. I’m not talking about evolutionary changes, I think the suite of top-level commands (checkout, branch, merge, pull, reset, commit, etc) needs to be redesigned from scratch.
Git is what I’ll be using at work, but I’m continuing to use Mercurial for home projects. Perhaps not as flexible/powerful in some ways, but I quite like the interface thus far. Peepcode has an intro screencast on Hg, and Bryan O’Sullivan’s book is online for free.
I think the Git interface is great, but you are right about some of that details.
I think git checkout is ok to overwrite the file, it’s what its supposed to do (it’s the “svn revert” for Git, “svn revert” don’t ask for your permission either), but I don’t see why a merge couldn’t be performed when the working copy is dirty, that would be helpful.
About the merge conflict, I think its similar to svn too, you have to mark your conflicted files “resolved” before committing (in Git you do a git add). The error message can be a little better though. About checking out a file that’s not merged yet, I’m not sure =)
I have trouble remembering what the different flavors of reset do.
You should try to report all this stuff to the Git developers, they are very receptive and very concerned about usability (Git has made *huge* improvements to the usability).
You can also fill the Git Survey 2009 too to express yourself =)
http://www.survs.com/survey?id=2PIMZGU0&channel=Q0EKJ3NF54
> “But why should I have to do this? CVS, SVN, and P4 don’t make me.”
One reason is that this way you can confidently abort the merge. Git needs some state to which it can reset your working directory if you decide that the pull or the merging from the pull was a big mistake. You could make an argument that git ought to be able to do the equivalent of a “git stash” for you when merging, then apply the changes, and pop the stash with merging for you automatically. But it doesn’t do that. And I think that might actually be more confusing to some people, so I’m not sure it’s the right thing to do. My preference is almost always to fetch and then perform the merge or rebase as a separate step anyway.
As for the individual file merging, you should check out git-mergetool. It will pop you into a three way vimdiff (or whatever your preferred tool is) and then mark the file as resolved when you are done editing it. I find this much easier than actually figuring out which files are conflicting and editing them directly by hand, then resolving them by hand.
I agree about checkout and reset. They are too overloaded and the reset flavors are difficult to remember. This is probably one of the worst parts of the UI, especially since these commands can be so destructive.
@Luca: If it was called “revert”, I wouldn’t mind having it throw away my local modifications. In fact I’d expect it! But the name is important — “checkout” just doesn’t sound like a destructive command. “checkout” is not destructive in cvs or svn. And it also has a lot of non-destructive uses, like switching branches. It’s just a UI minefield to have this one particular mode of the “checkout” command act like “revert”.
I’m not too inspired to report this stuff to the Git developers, because my biggest gripes would require major changes to address (like renaming core commands). There’s no way they’d be receptive to that. Like I said, what I think Git’s UI needs is a revolutionary, not an evolutionary change.
@Dave: Your point is taken about being confident about the merge. But I like the Perforce model here (can’t remember if CVS and SVN do this too), where it prompts you for each file that has conflicting changes, and asks if you want to merge right now or not. If you get cold feed about it, you can always decline to do the merge, then do the stash+apply thing manually. But going ahead with the merge (especially if the changes don’t have conflicts) is nearly always what I want.
Good suggestion about git-mergetool — I should use that instead.
Have you heard of easy-git? It’s a porcelain with a much simpler interface. (http://www.gnome.org/~newren/eg/)
Mercurial has the same “You can’t merge upstream changes into your local, uncommitted modifications” issue, and it’s also annoying as hell. I have yet to find a workflow that doesn’t piss me off. If git offered one, I’d almost certainly switch.
Stashing is not a great solution, either – unless un-stashing lets you do a nice 3-way merge – with all the same flexibility as a normal merge. Mercurial’s equivalent (shelve) sucks, because you end up running some external tool like patch – which leaves little unapplied patches around when a patch fails, instead of the traditional merge – which lets you see both versions of the code.