
Git koans - mathias
http://stevelosh.com/blog/2013/04/git-koans/
======
js2
Git commands are not consistent. This is a result of it growing organically,
and having a maintainer who strongly values backwards compatibility. But you
know, having used git for years that doesn't bother me. Let me explain.

Git is conceptually straightforward[1]. It is difficult if not impossible to
discern those concepts from its baroque CLI. So learning git from its built-in
help and/or man pages is suboptimal if not a complete waste of time. Either
read the Pro Git book[2] or pop the hood[3]. Then the inconsistent CLI is no
longer a big deal. If it were, someone would have come up with a new porcelain
(high-level CLI) that would have taken hold by now. And no one really has.
There is Easy Git[4] but I don't know anyone who uses it.

And really, I see git as not much different than Unix in this regard. Unix
commands vary widely in their usage. Does knowing awk help with sed? cpio and
tar? wget vs curl? Eventually you understand concepts such as pipes, regular
expressions, file descriptors, etc and you become familiar with the commands
where you are able to apply these concepts, at which point does strict
interface consistency between them matter? I dunno, but I started with both
git and hg around the same time and ended up happier with git. Mercurial had
the "better" CLI, but I found git was better at doing what I wanted. Before
that I used clearcase, which has a very consistent CLI and yet I was
constantly cursing at it.

Or maybe I just have stockholm syndrome and I'd feel differently if I'd ever
used plan 9. :)

1\. [http://tom.preston-werner.com/2009/05/19/the-git-
parable.htm...](http://tom.preston-werner.com/2009/05/19/the-git-parable.html)

2\. <http://git-scm.com/book>

3\. <http://newartisans.com/2008/04/git-from-the-bottom-up/>

4\. <http://people.gnome.org/~newren/eg/>

------
etjossem
Context

\---

Master Git and a novice API developer stood together in a crowded office.

"I am trying to merge two massive XML files," explained the API developer,
"and I am getting many tedious conflicts. You must have a strategy for making
the lines match up in a human-readable manner. What is my best option?"

"Patience," said Master Git, turning away from the monitor.

"But I have been manually resolving for hours!" protested the API developer.
"My mergetool ought to be able to imply context and match lines properly!"

Master Git sighed and left the room without a word. Hours later, he returned
to find the office empty and silent. Only the API developer remained, his head
bowed in defeat before the monitor.

"git merge --strategy-option=patience," whispered Master Git.

Upon hearing the same word in explicit context, the novice was enlightened.

~~~
develop7
Create a pull request to <https://bitbucket.org/sjl/stevelosh>

------
peterwwillis
Sometimes I feel like Git is an elaborate troll by Torvalds. Doing my taxes is
more intuitive.

~~~
imjared
Wanna trade? I'll do your git if you do my taxes?

~~~
oxtopus
I think you're on to something, there. If only I could manage my taxes with
git...

------
kevingadd
The 'Only the Gods' one seems weird to me; maybe I just don't understand Git
well enough, but it seems like that one's just a case of failing to understand
what a branch _is_.

A particular commit isn't made 'on' a branch, so to speak; it's just a SHA
hash that has a particular SHA as a parent. And then a branch is a pointer to
a particular SHA. So there's no way to know which branch a commit originated
'on', merely which branches happen to contain a commit at present (because
their current commit has the desired commit in its history).

So, if you merge two branches together, of course you can not tell which
branch a given commit originated on just by looking at history. That
information was never in the history in the first place.

I could see it being useful to track this in the merge somehow, though.

~~~
aidenn0
What you described is how _git_ thinks of branches.

Most developers think of branches as something they commit to. Therefore they
are surprised when later they can't tell which branch they committed to.

This could trivially be fixed with porcelain that puts the name of the branch
the commit was made to at the end of the commit message.

The issue of branches not really being branches is not unique to git; most VCs
I've ever used have had significant differences between what you would think
of as a branch and actual branches (SVN doesn't even have branches, except as
a convention).

~~~
philwelch
Git is predicated on the notion that it's actually quite simple and elegant
how Git works, and programmers are clever enough to understand how their
software works, so not much porcelain is necessary. There's a refreshing
honesty to that.

~~~
aidenn0
That's not my understanding of it's history; it was originally planned to
have, potentially, multiple porcelains, but that idea died of a couple years
back.

~~~
qznc
I believe that is the root problem of git. Originally, the assumption was:
porcelains will provide a nice UI, so core git can leak implementation
details. However, it turns out the implementation details are so simple and
intuitive for many developers that core git is now the dominant UI.

For example, the multiple use cases of 'checkout' or 'reset' are quite
intuitive, if you have the implementation in mind.

------
ctdonath
Short of me downing distressing amounts of coffee, could someone explain these
please for those of us still trying to grok git?

~~~
austintaylor
I'm not sure what Silence is about. My git-config-fu fails me.

One Thing Well demonstrates how one command (git checkout), while it does do
'one thing well' (changing your working directory to match something in the
index), can handle a wide variety of seemingly unrelated use cases.

Only the Gods demonstrates how history can mean different things: commit
parentage, which is usually immutable, and branch history, which is ephemeral.

The Hobgoblin probably refers to the Emerson quote "A foolish consistency is
the hobgoblin of little minds."

The Long and the Short of It is about how git accepts `git --help command`,
`git command --help`, and `git command -h` but `git -h command` returns an
argument error.

A theme here is that git does not try to anticipate your workflow. Its
commands and options are named based on what they do, rather than how you
should probably use them. This makes git a very powerful tool, but with an
interface that is often counter-intuitive.

~~~
kzrdude
Each git command is a designed tool itself. Others expect it to be 'git' to be
the wholly designed tool as one unit, but it's a toolbox.

------
the_mitsuhiko
A lot of these are just plain wrong though or have a good reason and it makes
me sad because I know Steve Losh uses a lot of Mercurial which I think has a
much worse UI.

1\. Silence: you can't alias built-in commands — for good reason. You don't
change the defaults of commands, that's just going to cause problems with
scripts. Why is it ignored silently? For starters because new git versions can
add a command that would conflict with your config that came from an older
version. You can trivially see that an alias is ignored by just adding
``--help`` to the command and it won't show you the alias.

2\. One Thing Well: I fail to see the problem with that? All of these do the
same thing. They check out a revision. With the exception of branch creation
which is actually not a feature of `git checkout` but `git branch`. There is
just a convenience operation on `git checkout` that also changes branches. The
better question is why branches in mercurial are called bookmarks :P

3\. Only the Gods: thankfully you don't know which branch something came from.
Mercurial fucked me over more than once where I accidentally pulled a patch
that came from a named branch that was conflicting with one I already had (you
would not believe how many people name their branches "bugfix"). Mercurial's
named branches are among the stupidest idea in software maintenance.

4\. The Hobgoblin: these are all different commands. "How can I view a list of
all tags": that's not what all tags is, that is "what's the list of tags I
know locally". In mercurial you can't even figure that out properly because
depending on the branch you're on you get different defined tags since tags
are stored in the tree. Git's tag system is much superior to that.

"How can I view a list of all branches": that's a question you will never ask
because it's the wrong question. A branch could show up multiple times. The
correct question to ask is "How can I view a list of all local branches?" `git
branch`, the second question is "how can I view a list of all remote
branches?" And for that the answer is `git branch -r` or `git branch --remote`
which makes a lot of sense in my mind.

"And how can I view the current branch": Same way you show all branches, just
`git branch`. Shows you your current branch as well as all other branches and
neatly colorizes it. `git rev-parse` is a command you really never have to use
unless you script something.

Yeah, some could probably more consistent. The odd one out are `git submodule`
and `git remote` which are sub commands instead of using parameters to
remove/modify them. That being said, you rarely need to use those.

In direct comparison with hg, git's UI and functionality is a present from
god. I'm pretty sure hg only has a better UI reputation because git's UI used
to be pretty bad. Now however? Pretty sure it wins over hg hands down.

~~~
stevelosh
1\. If a new version of git adds a command that shadows one of your aliases,
you'd really prefer "git foo" to silently run the new command? That's better
than telling you "hey this isn't going to do what you think any more"? Do you
have `try: foo except: pass` in your Python code too? Come on. There's no
reason git shouldn't at _least_ warn you on stderr here.

2\. `git checkout -- somefile.py` does not have anything to do with checking
out a revision. It changes a file's contents, but doesn't change the parent of
your working tree at all. Unlike `git checkout -b` which is inconsistent for
convenience like you mentioned, the "make file x look like it was in rev y"
function here is unique to git checkout as far as I can tell. Then again this
_is_ git, so there's probably at least four other ways to do this with cryptic
plumbing commands.

3\. I like Mercurial's named branches because I don't need to create a branch
for every single commit. But even if you don't like them, the "record which
branch X was made on" and "use that recorded information to group branches and
such in the UI" are two different things. The second requires the first, but
git _could_ record which branch was checked out when each commit was made as
some metadata in the commit, instead of dropping it on the floor. It would be
a godsend when trying to figure out what actually happened during a merge.

4\. `git branch` doesn't show you the current branch. It shows you all the
local branches, with colors and padding spaces and asterisks and crap. What if
you want the current branch in your prompt? Or just want to grab it in a
script? Your choices are "pipe to grep and sed" or "use this ugly-ass rev-
parse thing" instead of "git branch --current".

Git's UI is still abysmal. Mercurial preserves more data (branches), gives you
the same power as Git and has a UI where "--help x", "-h x", "help x", "x -h",
and "x --help" all do the same thing (pop quiz: how many different things does
git do for those? hint: it's more than two).

~~~
the_mitsuhiko
> 1\. If a new version of git adds a command that shadows one of your aliases,
> you'd really prefer "git foo" to silently run the new command?

Yes. Otherwise command line scripts will start doing horrible things. It was
never intended that you can alias builtins, the only reason you might think
you can is because it is allowed in mercurial which I think is a horrible
idea.

> `git checkout -- somefile.py` does not have anything to do with checking out
> a revision.

Of course not. `git checkout` checks out things. You can check out a lot of
things. Just because it works differently in hg does not mean it's wrong in
git. git checkout does what it says on the tin: it checks out a branch or
paths to the working tree.

> I like Mercurial's named branches because I don't need to create a branch
> for every single commit.

I think the concept of named branches in mercurial are hugely flawed and were
the final nail in the coffin of why I migrated over to git. And I never looked
back.

> What if you want the current branch in your prompt?

Then you use the underlying low-level commands or even better, you cat
`.git/HEAD` which will be even faster. Not sure why for your prompt to look
nice you need to have non-cryptic commands. Exactly for things like prompts
there is the plumbing.

> gives you the same power as Git and has a UI where "--help x", "-h x", "help
> x", "x -h", and "x --help" all do the same thing

How is this different from mercurial? If anything mercurial has even more ways
to show help: `man hg` which shows the manpage for _all the builtin commands_.
`hg help` which shows a short version or `hg -v help` which shows a long
version (which is not the man page). git only has two help pages: man pages
for long and short text for short.

> gives you the same power as Git

I have yet to see this.

~~~
micampe
_> Yes. Otherwise command line scripts will start doing horrible things. It
was never intended that you can alias builtins, the only reason you might
think you can is because it is allowed in mercurial which I think is a
horrible idea._

You should understand what you are responding to before typing your answer.

Say today I create an alias “boom = <whatever>” and next week git introduces a
boom command.

Now my alias is ignored, my scripts are broken and I might destroy my working
copy or kill my cat by using it without knowing it now means something else.

Git gives some very stupid hints “branhc is not a git command, did you mean
branch?” it should also warn me when I defined an invalid alias.

Maybe that alias was valid two versions ago or I just don’t know that command
exists, since there are eleventy thousand of them.

~~~
the_mitsuhiko
> Now my alias is ignored, my scripts are broken and I might destroy my
> working copy or kill my cat by using it without knowing it now means
> something else.

Your scripts should not rely on aliases you have in your config. Scripts are
intended to be used by different people.

~~~
micampe
I decide who is intended to use my scripts, thank you. I have many scripts
where I am the only user.

Also, the issue of having an alias changing behavior without warning on my
command line still exists.

------
giberson
One Thing Well: The command of all these seemingly different action is the
same, because all of these seemingly different actions are actually the same.

The Long and the Short of It: -h is not a valid argument. Passing -h therefore
results in providing usage information.

------
colinhowe
Has anyone made an alternative interface for git that tidies this up without
drastically changing how you use git? Or would this condemn someone's sanity
to the graveyard?

~~~
syncsynchalt
Mercurial is essentially git with a clean interface and drastically fewer
workflows. If it weren't for github's existence I'd recommend hg to anyone.

That said hg is the better choice for big software shops IMO, because of the
lower training cost and because they probably won't use github anyway.

~~~
the_mitsuhiko
> That said hg is the better choice for big software shops IMO, because of the
> lower training cost and because they probably won't use github anyway.

Considering more people know git I would assume the training cost for hg is
higher.

~~~
aidenn0
More people _use_ git; I'm becoming increasingly convinced that few of them
actually know git.

~~~
the_mitsuhiko
> More people use git; I'm becoming increasingly convinced that few of them
> actually know git.

And yet I have noticed that people find merging and branching much easier than
in mercurial. Few people understand the implications of named branches in hg.

~~~
aidenn0
Yeah, named branches in hg are just weird.

~~~
reinhardt
Not as weird as hijacking the term "branch" to mean a symlink to a ref that is
not part of the history. Then again why make it easier by naming it to
something more intuitive, like say, "bookmark".

~~~
qznc
Because a bookmark is usually immutable. If I bookmark
"<http://news.ycombinator.com> has "Hacker News" it will be same URL forever.

In contrast, git branches symlink to a different ref, whenever you commit to
it.

A git tag could be called bookmark intuitively.

------
philsnow
I don't use a complicated .gitconfig (just user.name and user.email), so this
is all new to me.

Something that leaps out to me as horribly wrong is that aliasing a builtin
doesn't cause the config file to be "invalid". Doing anything else is just
doing spooky things that are explained only several hundred lines into the man
page by a single sentence.

Maybe better would be to choke on config lines that alias a builtin, and to
print error messages before or after the output of each git command while the
offending line(s) are present.

Further,

    
    
      git config alias.pull "pull --ff-only"
    

should definitely be an error. Instead, git gladly makes the (no-op) confusing
edit to your config file.

------
LVB
'git' alone tells me to 'git help <command>' for help, which I've done
dutifully ever since. That is, until I too was enlightened a few minutes ago
with 'git <command> -h'.

Thanks, Master Git!

------
Aga
Even if this was meant as an critique to Git, I (some kind of a Git-
evangelist) enjoyed it a lot. I even learned something new!

The critique is quite well established. One needs to understand these quirks
to be an enlightened Git user.

However we make up with them, as the overall gain from Git's approach to
version control is so big.

Some things could be improved though, like introduce something like "git
branch --current" to avoid the Hobgoblin...

------
speeder
I don't understood the suicidal git thing...

Also of course I had to test the help commands!

git branch --help opens man

git branch -h throws those short commandline help summaries.

Interestingly, -h does not mention -h itself or --help, so the only way to
know that --help exists, is someone else telling you, you will never find on
your own.

~~~
damncabbage
-h is not a valid option. You could put --floogahbar in there and it'll give you the help by way of response.

~~~
__david__
Actually, "-h" is not a valid option on "git", but it is on "git branch"
(somewhat confusingly). Try "git branch -j" to see what happens when you pass
a bad option. In particular, try that when your cwd isn't a git repository.

------
ozh
The best part of this article IMO is the scrolling header on the left hand.
Nice touch.

~~~
josso
Really annoying on an iPad while zooming in on the text.

------
edem
I'm a relatively new git user. I'm rebasing a log when other developers commit
unrelated changes. What is the problem with git rebase?

~~~
qznc
I believe lots of people do not understand that you should treat remote
branches differently than local branches. More precisely, you treat
remote/global history differently than local history.

Using rebase to clean you local history is ok. Maybe even recommended or
mandatory depending on the project. Changing history, which is already in
other people's repos, will lead confusion and should really be avoided.

