The Fugue Counterpoint by Hans Fugal

6Oct/100

Git shutup already

So you have a bunch of local files you don't want to check in and you also don't want to add to the project's official .gitignore file(s)? Solution: edit .git/info/exclude. I like to make a symlink e.g. "ln -s .git/info/exclude .gitignore.local".

Thanks GitHub. http://help.github.com/git-ignore/

24Aug/097

Terminal Merge Conflict Resolution

A very important tool in the toolbox of any collaborating developer is a merge conflict resolution tool. OS X has the fantastic FileMerge, there are various graphical tools for linux like kdiff3, but I have yet to hear of one for the terminal. There's vimdiff, but it is really not up to the task of merge conflict resolution (doesn't handle 3-way diffs). There's probably something in emacs, just because there's always something for emacs. Emacs users please enlighten me, I'm not above using emacs for merge-conflict resolution. Might even be the gateway drug.

It doesn't seem overly hard (at least, no harder than writing kdiff3 or FileMerge) to make an ncurses tool that will take a 3-way merge and let you efficiently choose A, B, or edit for each diff section. Can it really be that nobody has done it yet?

18Feb/091

git GUIs

One of the nice things about git is due to its UNIXy design and its massive and ever-growing popularity, there are a lot of really nice bells and whistles, and I think we can expect to see even more. For example, GitHub.

While most git interaction is with simple commands in the terminal, it often pays to be able to get a birds-eye view of the revision history, or what I will call the DAG. The original tool for this is gitk. Gitk is functional, but it's really really unpleasant. It's written in Tcl/Tk—what did you expect? Some of us have higher standards for usability.

I tried out a few git GUIs and I have settled on two that I think are best of breed. The first is tig. Tig is an ncurses program, so it excels for remote operation over ssh, for quick dives into the repository without reaching for the mouse, and in keyboard use. Think of it as mutt for git. It's a fantastic program and I use it most frequently.

I have customized my tig setup slightly:
$ cat /Users/fugalh/.tigrc
set show-rev-graph = yes
color cursor white blue
$ alias | grep tig
alias tiga='tig --all'

The second is GitX. It's a mac app in every good sense, and it's an excellent git GUI. As you can tell from the screenshot, it's a bit easier on the eyes for visualizing complicated DAGs (not that this screenshot is of a complicated DAG).

If you use GitX be sure to "Enable Terminal Usage…" so you can start it on the current repository on the terminal by typing gitx.

13Feb/091

Why vimdiff?

I love Vim and vimdiff, but vimdiff is pitiful for doing 3-way merges. So I wonder why git's search for a merge tool prefers vimdiff over opendiff (which launches OS X's fantastic FileMerge program).

git config --global merge.tool opendiff

12Feb/098

Initialize a remote git repository

So you create a git repository (repo) on your local machine (foo) and begin hacking away. Then, you want to push a backup copy of that repo to another box (bar). What's the best way to do that? There are many ways to do it, but some ways are dangerous and some are just cumbersome.

Ideally, git would have an option to push that instructs it to initialize the remote repository and just do the right thing. I hope this is in our future, but for now we have to do some dancing around.

The canonical way is
bar:~/repo$ git clone --mirror local:repo
...
foo:~/repo$ git push bar:repo --mirror

but that's not always feasible due to firewalls and nasty NATs.

Unwise methods include copying the working tree with rsync or scp, doing something like the above without --bare or --mirror (which implies --bare), and other methods that would have you pushing to a non-bare repository.

The best method I've found is this:
bar:~$ git --git-dir=repo init --bare
foo:~/repo$ git remote add --mirror bar bar:repo
foo:~/repo$ git push bar
...

We set up a bare repository on bar, then set up a "remote" for bar which automatically mirrors (you could do that with the canonical method described above too), so it sends the whole shebang. After that, we push as we normally would.

12Feb/090

Why I switched to Git

I love it when someone else writes what I've been meaning to write, so I don't have to write it.

This article covers the reasons why I switched from mercurial to git, about as well as he could possibly hope to without consulting me, reading my mind, or being me. It's a bit creepy.

He explains it very well (probably better than I would) and has more insight into the technical details than I do.

While we're on the subject, you should all go read git for computer scientists so you can think like a git. If you already have a CS background, it's quite painless I assure you.

10Nov/0840

git-push is worse than worthless

Ugh

Let's say you are a web developer, and you do development on your laptop, then when things are nice and shiny you want to push those changes to the webserver. Seems natural enough, right?

git clone server:/var/www/foo
# ...
git pull

# edit stuff
git commit -a -m 'i edited stuff'

Now, let's say you're a (possibly former) darcs/bzr/mercurial user and this time you're using git. Git has git-push. You read the man page like a good little code monkey, and it seems like it does the same or similar thing to darcs push or hg push. It seems like if you want to push your changes to the server, you'd do this:

git push

Am I off in left field or does this not seem 100% rational? But wo be unto the code monkey that utters this unfortunate incantation. Observe:

$ mkdir foo
$ cd foo
$ git init
Initialized empty Git repository in /private/tmp/foo/.git/
$ echo hello > foo.txt
$ git add foo.txt
$ git commit -m 'hello'
Created initial commit bee50da: hello
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 foo.txt
$ cd ..
$ git clone foo bar
Initialized empty Git repository in /private/tmp/bar/.git/
$ cd bar
$ echo goodbye >> foo.txt
$ git commit -a -m goodbye
Created commit 99c13c1: goodbye
 1 files changed, 1 insertions(+), 0 deletions(-)
$ git push ../foo
Counting objects: 5, done.
Writing objects: 100% (3/3), 248 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
To ../foo
   bee50da..99c13c1  master -> master
$ cd ../foo
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   foo.txt
#
$ git diff
$ git diff --cache
error: invalid option: --cache
$ git diff --cached
diff --git a/foo.txt b/foo.txt
index a32119c..ce01362 100644
--- a/foo.txt
+++ b/foo.txt
@@ -1,2 +1 @@
 hello
-goodbye
$ git log
commit 99c13c1e60888ae2c0e221898411e1cd52ad3815
Author: Hans Fugal <hans@fugal.net>
Date:   Mon Nov 10 17:11:57 2008 -0700

    goodbye

commit bee50da72798edc47ddc36dbc4f559f141b1e28b
Author: Hans Fugal <hans@fugal.net>
Date:   Mon Nov 10 17:11:34 2008 -0700

    hello

I promise I didn't fake that. Yes, you saw that correctly—git wants to undo the changes you just committed. If you happen to have a clean working directory, all you need to do to return to sanity is git reset HEAD. If not, heaven help you.

This is totally unacceptable. It's unforgivable on so many levels. At the very least, the manpage should warn you to not push to repositories with working copies. Git should warn you before you push and screw up your repo that it has a working copy checked out. Ideally, git would behave like darcs and update the working copy. Suboptimally, it would behave like mercurial and make it a new revision that you have to manually checkout. But this is simply ridiculous.

So what is the solution? They tell you to use pull. Hello! Is anyone home? My laptop is roving. It's often behind a NATing firewall. I'm supposed to find my public IP address and figure out how to subvert the evil firewalls of the world every time I want to push my changes to the server?

A workaround, and probably the best real-world workflow, is to have a second bare repository or a second branch on the server, push into that, then ssh into the server and pull the changes. I think this page describes how to do that with a second branch, though I'm short on time to actually try it out at the moment.

More of this sickening story in this thread, where you will learn that at least one other person out there has his head screwed on properly, that the developers are more interested in how hooks work (and fail to allow you to do this even if you grok them), and that they've discussed the problem before and decided the correct response is to RTFM (M for minds this time, since the manual was completely unhelpful).

Update: Some of you have been quick to defend git and the design choice of how push behaves. I want to clarify that I don't care so much that push updates the repository but not the working directory. Mercurial works this way too. Not the way I'd do it but it's a valid approach. The problem here is that git push seems like a natural thing to do but screws up your working directory on the remote side. Mercurial doesn't change the working directory, but neither does it silently rebase it and set you up to undo your changes if you're not careful. The problem here is a lack of safety and a lack of warning. They know it's a problem, they've fielded enough "morons". A few words of warning in the man page is all it would take to make me happy.

And now, I have had time to work out a more specific workaround. Here's what I did, and it seems to work well:

# Server setup (set up incoming branch)
server$ git branch incoming
server$ git branch
  incoming
* master
  origin

# Laptop setup (local master to remote incoming)
laptop$ git config remote.origin.push master:incoming

# Everyday usage
laptop$ git push
Counting objects: 5, done.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 279 bytes, done.
Total 3 (delta 1), reused 0 (delta 0)
To server:/tmp/foo
   b108a07..a9d3282  master -> incoming

server$ git status
# On branch master
nothing to commit (working directory clean)
server$ git pull . incoming
From .
 * branch            incoming   -> FETCH_HEAD
Updating b108a07..a9d3282
Fast forward
 foo.txt |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

You could use hooks to automatically do that git pull . incoming if you liked, making it more like darcs than mercurial.

Updated update: On further thought, the cleanest solution is probably to have a separate master (bare) repository, e.g.

$ mkdir master
$ cd master
$ git init
Initialized empty Git repository in /private/tmp/foo/master/.git/
$ git commit --allow-empty -m initial
Created initial commit 999755e: initial
$ cd ..
$ git clone master live
Initialized empty Git repository in /private/tmp/foo/live/.git/
$ cd live
$ git branch
* master
$ cd ..
$ git clone master laptop
Initialized empty Git repository in /private/tmp/foo/laptop/.git/
$ cd laptop
$ echo hello > foo.txt
$ git add foo.txt
$ git commit -m hello
Created commit 2297bcf: hello
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 foo.txt
$ git push
Counting objects: 4, done.
Writing objects: 100% (3/3), 239 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
To /tmp/foo/master/.git
   999755e..2297bcf  master -> master
$ cd ../live
$ ls
$ git pull
remote: Counting objects: 4, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /tmp/foo/master/
   999755e..2297bcf  master     -> origin/master
Updating 999755e..2297bcf
Fast forward
 foo.txt |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 foo.txt
$ echo goodbye >> foo.txt
$ git commit -a -m goodbye
Created commit 04f6702: goodbye
 1 files changed, 1 insertions(+), 0 deletions(-)
$ git push
Counting objects: 5, done.
Writing objects: 100% (3/3), 248 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
To /tmp/foo/master/.git
   2297bcf..04f6702  master -> master
$ cd ../laptop
$ git pull
remote: Counting objects: 5, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /tmp/foo/master/
   2297bcf..04f6702  master     -> origin/master
Updating 2297bcf..04f6702
Fast forward
 foo.txt |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)
11Dec/070

Darcs2 Prerelease

Levi referred me to a post entitled "How I stopped missing Darcs and started loving Git". It's an interesting read, but ironically the thing that interested me most was a comment mentioning that just yesterday darcs 2.0.0pre1 was released. It looks like some very exciting things are coming down the darcs pipe:

  1. It should no longer be possible to confuse darcs or freeze it indefinitely by merging conflicting changes.
  2. Identical primitive changes no longer conflict.
  3. Darcs get is now much faster, and always operates in a "lazy" fashion, meaning that patches are downloaded only when they are needed.
  4. Darcs now supports caching of patches and file contents to reduce bandwidth and save disk space.
  5. Speed improvements.

The most exciting change is, of course, the elimination of the exponential merge. This is very good news indeed, if it means what I think it means. The second change I listed is also very interesting to me. You'll remember I posited that exact situation in a previous post, and was teased incessantly as a result.

Do read the release notes. If darcs2 is released within a reasonable time frame, it will continue to be a strong contender.

Tagged as: , , No Comments
9Apr/060

git

I spent some time over the weekend with git yesterday, so
I finally have an opinion about it.

Distributed revision control systems are absolutely fabulous. If you haven't
given one a serious go yet, you really should. My favorite is
darcs. This post will address git from the
perspective of a darcs user, and I might throw some comparisons to CVS or
Subversion in, too.

git is really a lot easier to use than I had anticipated. I read lots of
warnings in the documentation about how git is stupid (by definition, this is
one of its goals) and how unless your needs are a lot like Linus' needs, it
won't be right for you. I've found that to be unnecessary modesty. git is very
usable as a distributed revision control system for normal people on any size
of project. It's not as nice as darcs, IMO, but it does have better performance
for large projects, and it doesn't trail far behind anyway. It's a lot nicer
to use than GNU Arch, even in its raw form.

git pretends not to be an SCM, but rather a "filesystem". Whatever. git was
written to do what Linus needed from an SCM, and it has never had any other
purpose. Although it is conceivable that git could be used for other things, as
it is quite general and flexible, that doesn't make it not an SCM. It certainly
is like no SCM you've ever seen (at the low level) but it's still basically an
SCM.

Nevertheless, if you look too close at git you'll be either overwhelmed by the
sheer volume of low-level filesystem-like commands, or thrilled by them.
However you don't need most of them, and if you ignore them you can enjoy git
usability, with the added security of knowing that should you need to doctor
your repository (or have a git guru do so) all the low-level commands you need
are there. That is a bonus in my book, but mostly theoretical.

I think originally git had fewer scm-like commands and so people wrote
Porcelains, like Cogito.
Cogito is supposed to be easier to use and a more perfect SCM built on top of
git. Frankly, I can't tell the difference between Cogito and git core, except
that git core has more commands and options. Cogito does use slightly different
terminology and command names, which only confuses the game. I think I'd rather
learn the git commands and options that I need from well-written documentation
and ignore the rest, than confuse myself with cogito. After only an hour or two
of experience, I may be really missing the boat, so you might want to check it
out anyway.

Compared to darcs, git feels very familiar. darcs' UI is more polished, but git
has a much richer set of commands. One primary difference is that git does
branching, where in darcs a branch is basically a new copy of the repository.
Both are valid approaches, but the git approach does take less disk space (and
network bandwidth) which is important for 300-400 megabyte repositories, like
the kernel. I think I will probably continue using darcs primarily, but I will
probably try out git more in earnest when I get a chance to see if it might be
time to switch.

One thing I do not like about git is the tendency people have for providing
their driver for hardware X in the linux kernel as a git repository. Hello!
Nobody wants to download 350MB for your 10k of changes to the kernel. Nobody
wants to run your bleeding edge git repository just so they can get your
driver. That's idiotic. git makes it easy to make patches against whatever
revision you want, so make patches against the latest stable kernel, or
whatever RC version if necessary, and call it good. Providing access to your
git repo is fine for developers, but don't expect it of users.

In summary, I will be using git for my kernels from now on. It will be easier
to move around between versions, save me disk space, and allow me to do minor
hacks without worrying about them surviving the next kernel upgrade patch. If I
didn't already know and love darcs, I'd start using git for my projects. If you
have wanted to investigate distributed revision control, check out darcs and
git and go with whichever your gut tells you to, I think either one will serve
your needs well. For heaven's sake, stop using CVS in any case!

Tagged as: , , , , , , No Comments