Hacker News new | past | comments | ask | show | jobs | submit login
Aha Moments When Learning Git (betterexplained.com)
219 points by p4bl0 on June 12, 2011 | hide | past | favorite | 34 comments



Hi all, thanks for attention! My passion is finding the "aha!" moments when learning. In git:

  - Git has a staging area before you commit
  - Branching is "save as..." on a directory
  - Imagine virtual directories inside your physical one
  - Show the current physical & virtual directory in your prompt
  - Visualize your branching strategy
That's what I'd explain if I had 30 seconds to tutor git in an elevator -- the rest can be looked up =).

Shameless plug: I'm making a general platform to share these "aha" moments, described here:

http://betterexplained.com/articles/share-your-insights-aha-...

The goal is to find and filter the best insights that really helped when learning.


That's cool. Something I noticed - if you sort by 'most liked' it seems to be the most liked entries at the bottom, which probably wasn't the intention.


forehead slap Thanks, I'll be fixing that.


My Git aha! moment was when I discovered "git add -p".

I use it all the time now to break my work apart into multiple atomic commits.


Agreed: don't know about you, but I picked that up from Ryan Tomayko's "The Thing About Git"[1] - some previous HN discussion on that, if anyone's curious[2].

[1] http://tomayko.com/writings/the-thing-about-git

[2] http://news.ycombinator.com/item?id=158008


I also like to use "git checkout -p" for the reverse; discarding working directory changes.


This totally changed how I used git, wish I'd known about it from the start. Also emacs users should check out magit, which has a good 'add -p' and 'checkout -p' style of working


The problem is that if you do a partial commit, you have to test that it works. You may have forgotten to add one line, etc.

One way to do this is to use "git stash save --keep-index", after patching. Then you can test that your program works with only that patch. After committing you can "git stash pop" to patch again or commit the rest.

Maybe it's possible to make a hook to make this easier.


I use git add -p to split apart what I intend to eventually fully get into the repo. Usually, this means separating whitespace cleanup from meaningful commits. Sometimes I use it to make my commits "distinctly describable" -- instead of a big commit saying (in effect) "I did lots and lots of stuff generally to do with BigTask", I am able to describe various steps of that work, e.g. "Refactored models.", "Added supporting .js file for foo mechanism.", "Migration for bar process.", "Controller method and view for baz form."

Generally, I reserve the "make sure it works" step for just before _pushing_ (i.e. making public), rather than just before committing.

I always use either `git commit -av` or `git add -p`, so I can preview what I'm about to do. I never use the freewheeling `git commit -m`.


Problem with not testing your "micro-commits" is that when someone is looking at old revisions to figure out where something went wrong, there is a chance that it won't compile, or will crash because you didn't test that exact commit.


I prefer smaller commits to larger ones because it isolates things (both improvements and breakage). I use git bisect when needed. You can always squash small commits into larger ones if really necessary, but teasing apart large commits into small, logical steps or components is much harder. Smaller commits are more "mobile" in that they can be cherry picked (in or out).


After preparing the index with `git add -p` you can use `git stash --keep-index` before committing to help test your changes. This is a good practice to follow for exactly the reason you describe.


   git add -p
That's "patch" for others who don't want to Google. Apparently can use it to choose which lines will be stored as the commit.


> Apparently can use it to choose which lines will be stored as the commit.

The base UI is really for picking hunks (the sections you see in a `diff`), although git extended it to allow for splitting chunks into sub-hunks and ghetto-editing hunks.

Other VCS and more details:

* Darcs was probably the first VCS to implement this, it's the default behavior for `darcs record` (which is darcs's `commit` command). Much like git, darcs provides for full hunk edition (splitting hunks into sub-hunks, and removing or changing sections of hunks before recording them).

* Mercurial provides this function through the (built-in) `record` extension. It currently gives no way to split or edit hunks, only to record or ignore them. The crecord third-party extension has these features (and/but a more complex, curses-based, UI)

* `-p` is actually a subset of the much more complex `-i`/`--interactive`, which is a special staging-manipulation sub-shell.

Note however that this is a powerfully dangerous tool, as it lets you commit untested revisions (then again, so does file-selection at commit, which has been available since SVN). Revisions created this way should be tested one by one for breakage, and rewritten if needed.


The article describes a "parse_git_branch" shell function for use in a shell prompt. A much better function is included with git's completion script and is called "__git_ps1".

It can be found in /etc/bash_completion.d/git if you installed git using apt-get or something similar. It lives at contrib/completion/git-completion.bash in the git source tree:

https://raw.github.com/git/git/master/contrib/completion/git...

In addition to "__git_ps1", you also get amazing tab-completion for git commands when you source it. Here's how I use it in my prompt. There are simpler examples in the script itself.

    export GIT_PS1_SHOWDIRTYSTATE=yes
    export GIT_PS1_SHOWSTASHSTATE=yes
    export GIT_PS1_SHOWUNTRACKEDFILES=yes

    git_prompt='$(__git_ps1 " \[\033[0;34m\](\[\033[00m\]\[\033[01;32m\]%s\[\033[00m\]\[\033[0;34m\])\[\033[00m\]")'

    PS1="\n\[\033[0;34m\]# \[\033[1;32m\]\u\[\033[0;34m\]@\[\033[1;32m\]\h\[\033[0;34m\]:\[\033[1;32m\]\w\[\033[1;32m\]$git_prompt\n\[\033[1;36m\]%\[\033[0;39m\] "


This article is using "GUID" in an unusual way (for the general concept rather than the specific format). I think that's likely to confuse people. Better to be more precise: Git uses a sha1 hash as a unique id.


Worse, "GUID" suggests that git assigns random ids to blobs and trees, which is not true at all. Two files with the same contents are intentionally given colliding object ids, not unique ones. Only commits (and tags?) get ids which are effectively unique (because they're distinguishable at least by the embedded timestamp).


>>Why stage? Git’s flexible: if a, b and c are changed, you can commit them separately or together

This is the saver I would say. More often than not it so happens that once I am done with an implementation/issue, I get carried away (I am working on fixing this habit) and continue writing code in order to get as much out of me as possible and then suddenly realize that one of the earlier fixes should already be in the repository. Thats when git staging comes into picture.


The most common scenario for me is that while implementing one feature, I find a documentation error or a simple way to improve a component that is used by the new code. For example, I might be testing the new feature and have a bug in my new code produce a confusing error that could have been caught earlier by the existing component if its input validation was better. Usually I fix these issues on the spot because (a) they help me test the new feature and (b) otherwise I would be likely to forget where they are. I want to commit these changes independently. I almost always do this using egg (https://github.com/byplayer/egg) which has a very slick staging and commit interface within Emacs.


I find git-cola an excellent tool for separating a large commit into useful parts.


You can also use "git checkout -p" for this.


My Aha moments:

1)Discovering gitx for Mac Os. Specifically the brotherbard version. https://github.com/brotherbard/gitx Use this daily.

2)Learning about git rebase <branch> to make commit history more readable


Be careful with rebasing when working with other people. Rewriting branch history is frowned upon.


v0.7.1 is from September 2009, a bit old. Is this the best Git App for Mac OS X?


Plain old `git gui' is worth looking at. Better keyboard support than GitX (though this is not saying much) and the UI for committing lines works better. If I've made a big pile of little changes and I'm trying to split them into multiple commits, I've found git gui way better than GitX.

The history view of GitX is better than `gitk --all', though, and of course having both history and day-to-day duties in the same app is useful.

There is another app called GitY, which I hated immediately. It shows your unstaged changes and your staged changes and your untracked files all in the same window, which I found very confusing, and there doesn't appear to be any way to add individual lines into the index, which is something I do all the time.


No. Tower is, hands-down and no questions asked, but it's not free.


My 'Aha' Git moment:

Discovering that '-d', '-D', 'rm', and ':' are all inconsistent UI behaviors for removing things

Consistent behavior is "distributed" across multiple commands. Variety is indeed the spice of life!


Some good points in there but I think the section on branching strategy might be a throwback to the old, centralised paradigm. You can have master/dev repos, with feature branches off dev. Not sure if you need a release repo, but the main point is that access-controlled repos can replace the branching you'd use in older systems.


"Easily merge changes with the original (changes tracked and never applied twice)"

Heh, guess he never tried to merge two things that was a series of commits that both contained renaming of something to something else. Lets see if I can visualize the situation:

Original branch A with file foobar.foo and contents z. branch B from A with one commit with foobar.foo renamed to foobar.foo.in and contents changed to z'. branch C from A with two commits. The first commit changes the contents of foobar.foo to z''. The second changes the name of foobar.foo to foobar.foo.in.

z' and z'' generate a merge conflict.

Have fun merging...


Can you point to a version control system that won't fall all over itself in this case?

Also, what would an "ideal" VCS do in such a situation? Btw, have you tried this with git? What happens?


No, I was replying to the claim that merging works and that stuff is never applied twice. Which it was. Specifically git suggested that I had a deletion of the new file unmerged.

Thinking back on it, this doesn't make sense. I think the situation involved rebase as well. That is, rebasing a rename onto a branch that already has done the rename in question turns the second one into a deletion. (Or at least adds the deletion of the second file to the list of unmerged changes).


I just tried this btw,

If you merge B and C you will only have a conflict if the changes in B and C touch the same regions of the file.

If not the merge is made automatically and the changes from both branches are reflected in the new file.


> Have fun merging...

I just did. As long as the changes in branches B and C are to different regions of the file the merge is completed automatically. If not, conflict markers are places in foobar.foo.in for you to resolve there.

Not sure what more you could ask of your Version Control System...


I might have misrepresented the case a bit. It involved a rebase, and rebase + parallel renaming makes for strange things to happen.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: