Julius Plenz – Blog

gitolite and interpolated paths

The Git daemon supports a feature called Interpolated Path. For example, my Git daemon is called like this:

git-daemon --user=git --group=git \
    --listen=0.0.0.0 --reuseaddr \
    --interpolated-path=/var/git/repositories/%IP%D \
    /var/git/repositories

What it does is it translates the request for a git repository before actually searching for it. When I do a git clone git://git.plenz.com/configs.git what actually happens is that the Git daemon will deliver the repository at /var/git/repository/176.9.247.89/configs.git. That is especially nice in environments where you share one git daemon for several people/projects and have sufficient IP addresses.

However, Gitolite doesn't seem to have a config option for this. Now what I did was patch Gitolite so that it'll prepend the repository name with the IP of the network interface the call came through.

This information is available from the SSH_CONNECTION environment variable.

(The patch to gl-compile-conf is needed so that Gitolite will create a projects.list per virtual host. That way, you can use different configuration files for each CGit instance, according to your VHost.)

diff --git a/src/gl-auth-command b/src/gl-auth-command
index 61b2f5a..a8d1976 100755
--- a/src/gl-auth-command
+++ b/src/gl-auth-command
@@ -124,6 +124,12 @@ unless ( $verb and ( $verb eq 'git-init' or $verb =~ $R_COMMANDS or $verb =~ $W_
     exit 0;
 }

+# prepend host's ip address
+# SSH_CONNECTION looks like this: 92.225.139.246 41714 176.9.34.52 22
+#                                 from-ip        port  to-ip       port
+my $connected_via = (split " " => $ENV{"SSH_CONNECTION"})[2] // "";
+$repo = $connected_via . "/" . $repo;
+
 # some final sanity checks
 die "$repo ends with a slash; I don't like that\n" if $repo =~ /\/$/;
 die "$repo has two consecutive periods; I don't like that\n" if $repo =~ /\.\./;
diff --git a/src/gl-compile-conf b/src/gl-compile-conf
index 2d4110f..6f8f0d7 100755
--- a/src/gl-compile-conf
+++ b/src/gl-compile-conf
@@ -577,6 +577,22 @@ unless ($GL_NO_DAEMON_NO_GITWEB) {
     }
     close $projlist_fh;
     rename "$PROJECTS_LIST.$$", $PROJECTS_LIST;
+
+    # vhost stuff
+    my %vhost = ();
+    for my $proj (sort keys %projlist) {
+        my ($ip, $repo) = split '/' => $proj => 2;
+        $vhost{$ip} //= [];
+        push @{$vhost{$ip}} => $repo;
+    }
+    for my $v (keys %vhost) {
+        my $v_file = "$REPO_BASE/$v/projects.list";
+        my $v_fh = wrap_open( ">", $v_file . ".$$");
+        print $v_fh $_ . "\n" for @{$vhost{$v}};
+        close $v_fh;
+        rename $v_file . ".$$" => $v_file;
+    }
 }

 # ----------------------------------------------------------------------------

With this patch applied, I can git push git.plenz.com:config.git and it will end up pushing to 176.9.247.89/configs.git, fully transparent to the user.

N.B.: This strictly is a "works for me" solution. It's not very clean – but I don't plan on properly implementing a config setting. As I said, it works for me. ;-)

Update 2014-09-10: I upgraded to Gitolite v3.6, and it’s different there. There is a functionality called “triggers” now, which you can activate in the .gitolite.rc like such: Uncomment the key LOCAL_CODE => "$ENV{HOME}/local", and insert these two trigger actions:

INPUT => [ 'VHost::input', ],
POST_COMPILE => [ 'VHost::post_compile', ],

Then, place this code under ~/local/lib/Gitolite/Triggers/VHost.pm (you’ll need to create that directory first):

package Gitolite::Triggers::VHost;

use strict;
use warnings;

use File::Slurp qw(read_file write_file);

use Gitolite::Rc;

sub input {
    return unless $ENV{SSH_ORIGINAL_COMMAND};
    return unless $ENV{SSH_CONNECTION};

    my $dstip = (split " " => $ENV{"SSH_CONNECTION"})[2] // "";
    return unless $dstip;
    return if $dstip eq "127.0.0.1";

    my $git_commands = "git-upload-pack|git-receive-pack|git-upload-archive";
    if($ENV{SSH_ORIGINAL_COMMAND} =~ m/(?<cmd>$git_commands) '\/?(?<repo>\S+)'$/) {
        $ENV{SSH_ORIGINAL_COMMAND} = "$+{cmd} '$dstip/$+{repo}'";
    }
}

sub post_compile {
    my %vhost = ();
    my @projlist = read_file("$ENV{HOME}/projects.list");
    for my $proj (sort @projlist) {
        my ($ip, $repo) = split '/' => $proj => 2;
        $vhost{$ip} //= [];
        push @{$vhost{$ip}} => $repo;
    }
    for my $v (keys %vhost) {
        write_file("$rc{GL_REPO_BASE}/$v/projects.list",
                   { atomic => 1 }, @{$vhost{$v}});
    }
}

1;

posted 2011-09-25 tagged git and gitolite