Subject: inotify daemon speedup for git [POC/HACK]

From: Finn Arne Gangstad <[email-masked]>
Date: Tue, 27 Jul 2010 14:20:18 +0200

Reading through the thread about subtree I noticed Avery mentioning
using inotify to speed up git status & co.

Here is a quick hack I did some time ago to test this out, to use it
call "igit" instead of "git" for all commmands you want to speed up.

There is one minor nit: The speedup gain is zero :) git still
traverses all directories to look for .gitignore files, which seems to
totally kill the optimisation.

To use it, put igit and git-inotify-daemon.pl in path, and do git
config core.ignorestat=true in the repositories you want to test it
with. The igit wrapper will run git update-index --no-assume-unchanged
on all modified files before running any real git commands.

To get inotify to ignore all changes that the git commands themselves
perform, the "igit" wrapper kills the currently running daemon. Then
it reads the list of updates files, and does git-update-index
--no-assume-unchanged on them. Then the git command is run, and
finally the daemon is fired up again.

I had to do one tiny modification to git to make update-index ignore
bad paths.

igit - a git wrapper with an inotify daemon

Linux only - requires inotifytools installed. This is juct a quick hack/proof
of concept!
update-index: Do not error out on bad paths, just warn
---
 .gitignore             |    1 +
 builtin/update-index.c |    2 +-
 git-inotify-daemon.pl  |   28 ++++++++++++++++++++++++++++
 igit                   |   22 ++++++++++++++++++++++
 4 files changed, 52 insertions(+), 1 deletions(-)
 create mode 100755 git-inotify-daemon.pl
 create mode 100755 igit

diff --git a/.gitignore b/.gitignore
index 14e2b6b..fa67132 100644
--- a/.gitignore
+++ b/.gitignore
@@ -204,3 +204,4 @@
 *.pdb
 /Debug/
 /Release/
+.igit-*
diff --git a/builtin/update-index.c b/builtin/update-index.c
index 3ab214d..c905d78 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -282,7 +282,7 @@ static void update_one(const char *path, const char *prefix, int prefix_length)
 	}
 	if (mark_valid_only) {
 		if (mark_ce_flags(p, CE_VALID, mark_valid_only == MARK_FLAG))
-			die("Unable to mark file %s", path);
+			fprintf(stderr, "Unable to mark file %s\n", path);
 		goto free_return;
 	}
 	if (mark_skip_worktree_only) {
diff --git a/git-inotify-daemon.pl b/git-inotify-daemon.pl
new file mode 100755
index 0000000..a57ceef
--- /dev/null
+++ b/git-inotify-daemon.pl
@@ -0,0 +1,28 @@
+#!/usr/bin/env perl
+# Run from igit
+
+use warnings;
+use strict;
+
+die "Usage: $0 <output-file>" unless $#ARGV == 0;
+my $output = $ARGV[0];
+my $pid = open(INOTIFY, "exec inotifywait -q --monitor --recursive --exclude .git -e attrib,moved_to,moved_from,move,create,delete,modify --format '%w%f' .|") or die "Cannot run inotifywait: $!\n";
+
+$| = 1;
+print "$pid\n";
+
+my %modified_files;
+while (<INOTIFY>) {
+    s=^./==;
+    chomp;
+    $modified_files{$_} = 1;
+}
+
+# Output file must be opened as late as possible, it is a named pipe
+# and the listener won't be here before inotifywait exits.
+# open would just hang if it was done earlier.
+open(OUT, ">$output");
+foreach my $key (sort keys %modified_files) {
+    print OUT "$key\000";
+}
+exit 0;
diff --git a/igit b/igit
new file mode 100755
index 0000000..60c5bb2
--- /dev/null
+++ b/igit
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+TOPDIR=`git rev-parse --show-cdup` || exit 1
+
+if [ ! "$TOPDIR" ]; then
+    TOPDIR="./"
+fi
+
+PIPE=.igit-pipe
+PIDFILE=.igit-pid
+
+if [ -p ${TOPDIR}${PIPE} ] && kill -TERM `cat ${TOPDIR}${PIDFILE}`; then
+    ( cd $TOPDIR && git update-index --verbose --no-assume-unchanged -z --stdin < $PIPE )
+fi
+
+git "$@"
+
+cd $TOPDIR
+rm -f $PIPE
+mkfifo $PIPE
+git-inotify-daemon.pl $PIPE > $PIDFILE 2>> .igit-errors </dev/null &
+
-- 
1.7.2.rc0

- Finn Arne