Inspired by Dave Miller’s discovery of git-stash, I decided to make an effort to learn more about git, instead of learning just the bare minimum to get work done and holding the rest of it out at arm’s length like a dead skunk. I found that git actually does a lot of the things I have been doing by hand with patch and diff on top of git, sometimes even in a more convenient and safer way! I still have a wish-list of features, but I feel confident that (a) they exist, and (b) someone (perhaps you!) will tell me how to use them.

I’ve found that my natural mental model for revisions is a stack of patches, and that I like the parts of git that just map directly to what I used to do by hand with patches or quilt (e.g., git-stash). This seems like a pointless tautology (“git is like automated patch management! Because source control is automated patch management!”), but it is meaningful because when I’ve used other source control systems, my mental model looked more like branches and sequential changesets and merges, whereas patches can be broken up, applied in different orders, etc.. Another handy patch-related feature I like (but haven’t used yet) is git-add in interactive mode, which lets you mark only certain chunks of patches for commit instead of all the changes in a file. I can’t say I’ll miss hand-editing patches.

git-rebase is another feature I really, really needed, since I was going back and fixing in bugs in old commits so that I could then figure out where I’d introduced other bugs. Rebasing involves several different commands, all of which I simply copy verbatim from some random git HOWTO page. Something that causes me major stress during a rebase is that the elapsed time between the initial checkout and the rebase command is fairly long, and the only way I know what commit ID to start the rebase with is by searching my bash history (via Ctrl-R) for my last checkout command (and hoping that I am checking the right shell instance).

Another thing I couldn’t figure out how to do efficiently during my commit grooming is to checkout the next patch ahead in the branch. So say you have commits A, B, C, D on this branch, where D is the HEAD, and you checkout B and run your regression test on it and it’s fine, so then you want to checkout C next. The way I ended up doing this is checking out HEAD again, looking through the commit log, finding the commit I’d just checked out, finding the commit following, and checking out that commit id again. At one point, I thought I would be all smart and keep the output of a git-log in another window so I wouldn’t have to do the checkout-HEAD part, but then I did a rebase and checked out a commit from the unrebased tree and spent a few minutes panicking over where my changes had disappeared to. Anyway, what I want is “git-checkout NEXT” – all the HEAD^^^ business wasn’t working for me because I was about 10 patches deep, and then I have to count carets in my head, when I don’t want to count, I just want the next patch in the sequence. (This is where I hope someone will leave a comment telling me how to do this while implying that I am a moron for not knowing already. Thanks in advance!) (Update: Yes, I know about the HEAD~5 syntax. No, it doesn’t do what I want, I want NEXT.)

While I’m at it, WTF is with git-checkout and git-branch? git-checkout deals with checking out files, except when it deals with branches, and git-branch doesn’t do everything you want to do with branches (in particular, checking them out). I still type “git-branch “, get an error, and just automatically retry with “git-checkout “, and vice versa.

After all this, I have to report that in general, using git still feels like wielding a 50-hp chain saw with no blade guard, except not as safe. And of course, this speaks for itself:

evilcat:~ val$ git-[TAB][TAB]
Display all 137 possibilities? (y or n)


22 thoughts on “

  1. I’ll leave a more reasoned reply to this later when I’ve had a chance to read/process it all, but Git 1.6 fixes the 137-possibilities thing; the only things installed in your path are

    git, git-receive-pack, git-upload-archive, git-upload-pack, gitk.

    I’ve rather grown to like git these days. It’s made version control fun and enjoyable, as opposed to the horror of working with CVS.

    Ninja edit: You might also want git rebase --interactive commit-ish instead of working with the raw rebase commands.

  2. The git-137 thing is not about the thing where people are whining about git polluting /usr/bin or whatever, it’s about the fact that there *are* 137 commands.

  3. Simulating git checkout NEXT

    You need to know the name of the branch tip you want to move towards (since “git checkout” to a non-tip hash doesn’t leave you on any branch and a particular commit could have multiple children). Once you have that, git can tell you the hash of the next commit to check out:

    git log --reverse -n1 --pretty=format:"%H" HEAD..master

    (to move towards the tip of “master”). It’s almost easier than this, in that “git rev-list --reverse -n1 HEAD..master” looks like it should do the right thing. Sadly, git applies the “-n1” before the “–reverse” so you always get the parent of the tip, not the child of HEAD.

    Passing that to a “git checkout” call gives you what you want. Still in ^R country due to the length, though, unless you write a script wrapper for it.

  4. stgit

    stgit == stacked git. It is the merger of quilt and git. stgit is a layer on top of git.

    You can push and pop patches in a stack, rearrange the stack, etc.

    to update to linus…
    git fetch linus
    stg rebase linus/master

    It will do the rebase and adjust all of the patches, plus tell you which ones are empty now because the made it upstream.

  5. Re: Simulating git checkout NEXT

    cat >> ~/.gitconfig <<EOM
        findnext = log --reverse -n1 --pretty=format:"%H" HEAD..master
        checkoutnext = !git checkout `git findnext`
  6. It sounds like instead of “git checkout NEXT”, you actually want to be using “git bisect”, which is an awesome way of binary searching to find a particular bug. You tell git which revision worked, which failed, and then it picks the midpoint for you to test. If you’re especially keen, you can even give it a program to run to do the test for you. It’s not so good for going over every patch in a sequence, but for bug-hunting it’s awesome.

    Also, if you want to reassure yourself a bit, you should check out “git reflog” sometime; it keeps a history of things you did, even if those things otherwise would destroy history. It can make recovering from mistakes way easier.

  7. Counting carets

    One of the convenient ways to reference linear revisions is ~n. As in, HEAD^^^ == HEAD~3. Hopefully that helps when you’re really 10 commits deep in your patch queue.

  8. git has a pretty terrible UI, so don’t expect too much. Here are some conceptual pointers rather than specific commands:

    There are tools to make git work like a stack of patches, as the other commenters note. I played with them briefly but I find my mental model is now the tree-snapshots one. I found recently (after months of using git and even studying its source) I have a new perception about revision history: it’s less about “this commit directly follows that one” and more about “this tree relates to that one”.

    From that I now think git-rebase is usually the wrong way to go, because it’s throwing away information that could have been useful. That’s why it’s dangerous, like you mention. (Though sometimes “git reflog” can save you.) I guess it’s still important if you’re trying to maintain a big stack of patches against a moving upstream. One tip for doing a rebase without fear: (1) git branch backup [e.g., tag your current HEAD with a second name] (2) git rebase –crazy-commands (3) “oh no, everything is hosed! oh yeah, I can just delete my hosed branch and rename backup to it and try again”.

    For the checkout/branch thing, it is weird. One way to think that may help is that all checkout commands are “make my working copy like [x]” while branch modifies data in your .git/refs. In practical terms, checkout commands will affect what “git status” says, but branch commands just futz with branches that aren’t currently visible. It’s no real excuse, though: I still think the UI is not good. (Checkout is also used for reverting files.)

    I think the huge number of commands isn’t too harmful; most of them are grungy internals to make shell scripting easier. (Consider that git clone is a shell script.) And there are a bunch of other neat ones: my new favorite is “git clean”.

  9. Thanks for the mental model help. The tree-snapshot/relationship point of view is interesting, though too high-level for the way I work. My little accidental experiment with checking out commits not part of any existing branch changed my mental model significantly too – oh, hey, there are all these commit thingies floating around out there in space! Those must be the things they talk about pruning all the time!

    I think part of the rebasing fear is that, really, I feel like I could easily accidentally corrupt my git tree at the git internals level of destruction, to the point where even my backup branches are broken. It’s as if “cp” could not only copy files recursively, it could also directly write bytes to the hard drive with a single character option. Aaaaaaaaah. Sometimes I just clone entire new trees before I do something that’s not straight-forward branch management.

    I agree that git-rebase is usually the wrong thing to do, but in this case I really needed to get my commits cleaned up and in some kind of known working state, so that I could then track down which commits introduced which other bugs. (See earlier comment on why git-bisect was the wrong tool.) It’s all very lovely now, although my fingernails are significantly shorter.

  10. Re: stgit

    Or “guilt”. If you’re already used to patch management with “quilt”, guilt is the git-integrated version :-).

    I think guilt is simpler, easier to play with, and a bit more reliable. Though I haven’t actually used stg to compare them. I get the impression stg is more git-like in it’s power, complexity and danger. Whereas guilt seems to be just like quilt, but using git for the configuration (e.g. editor, email sending), and to avoid the problem where you edit a file and forget to do “quilt add” before-hand.

  11. “oh, hey, there are all these commit thingies floating around out there in space! Those must be the things they talk about pruning all the time!”.

    Indeed. The very act of adding new state to a git tree is additive to the data repository so existing state isn’t modified. Existing objects don’t disappear until poked with aggressive git garbage collection.

    git-reflog is the tools that remembers where you where at any point and you can use that magic commit id to create a new branch at will even if all your “backup” branches got deleted.

    About the only reason I have multiple git-clones now is if I’m timeslicing between two developments (e.g. compile one while editing another).

  12. Re: Simulating git checkout NEXT

    Slight correction to escape the backticks:

    findnext = log –reverse -n1 –pretty=format:”%H” HEAD..master
    checkoutnext = !git checkout \`git findnext\`

  13. git checkout NEXT

    This may or may not help you.

    Anyway, to cycle through commits A, B, C, D, E where A is earliest and E latest, on branch X, while on branch X at commit E, you can do this:

    $ git checkout HEAD~4

    This will move you to a detached head. Now, checkout the next commit on branch X,

    $ git checkout X~3

    Decrement the number until you end at 0 which should be the HEAD of X.


  14. Re: git checkout NEXT

    I think I’ve got a real solution this time. Try this sequence,

    $ git checkout HEAD~4 # no it’s not what you’re thinking
    $ git checkout $(git rev-list –reverse ..master | head -1)

    So my previous post would have looked like,

    $ git checkout HEAD~4
    $ git checkout $(git rev-list –reverse ..master | head -1)

    Currently, the problem with aliasing this is that master is hardcoded. I’m not sure how to give it a branch name yet.

  15. Re: Simulating git checkout NEXT

    Well, of course nothing in the world stops your from writing these two as bash shell scripts (git-findnext and git-checkoutnext respectively) and put them somewhere in the $PATH. They work as well as the original ones (i.e., there will be git findnext and git checkoutnext commands).

Comments are closed.

%d bloggers like this: