Take the 2-minute tour ×
Super User is a question and answer site for computer enthusiasts and power users. It's 100% free, no registration required.

Is there any way to serve files from branches in a git repo without checking them out into different directories?

I'd love the convenience of being able to create a new branch, push it to a test server repo and then do something like browse to http://branchname.test.com. I can use rewrite rules to handle branch name subdomain to point to different root paths but Apache can't read bare repos of course.

gitfs might work, but seems abandoned.

I don't especially need Apache either.

share|improve this question
1  
It is possible to read the ‘blob’s out of a repository without having a working tree (checkout). Tools like gitweb git.wiki.kernel.org/index.php/Gitweb serve primarily from bare repositories. Probably gitweb and its ilk offer more than you want, but maybe you could strip one down to just serve the bare files (git.kernel.org/?p=git/git.git;a=blob_plain;f=README) in a way where relative links would work. –  Chris Johnsen Feb 2 '10 at 1:47
    
Interesting idea, but probably too much work atm. I'm trying to test branches of a Django app, so Python needs to be able to import modules from the repo, etc. –  Van Gale Feb 2 '10 at 16:58
    
Except in the most basic scenarios (static content), serving general content with a web server does not require actual files. If what you wanted was a way provide normal access to files in a Git repository without actually creating real files (a virtual file system; an updated/maintained/working version of gitfs), then you probably should have left out all mentions of Apache, URLs, etc. –  Chris Johnsen Feb 3 '10 at 12:32

3 Answers 3

up vote 1 down vote accepted

The simplest approach would be checking out each branch and creating clones for each branch in subdirectories. It's very easy to do that with a script runned by cron (or in a post-commit hook) like:

#!/bin/sh
ROOT=/srv/repos/control
cd ${ROOT} # it's an internal repo
git fetch
for ref in .git/refs/remotes/origin/*; do
  BRANCH=`basename ${ref}`
  if ! [ -d ../${BRANCH} ]; then
    git clone --local ${ROOT} ../${BRANCH}
    cd ../${BRANCH}
    git branch --track ${BRANCH} remotes/origin/${BRANCH}
    git checkout ${BRANCH}
  else
    cd ../${BRANCH}
    git pull
  fi
done

After it's work you will get a bunch of directories with branches in /srv/repos.

git clone --local is very fast and optimal even on huge repositories like Linux kernel because it does not create all the objects in repository but instead creates hardlinks to them, so you won't waste any space on repository copies. You will, through, waste space on checked-out copies, but a) they're probably not very big and b) this will make web-based access much easier and faster.

share|improve this answer
    
Thanks, this looks reasonable to implement. Hopefully I can find time in the next week to try it out :) –  Van Gale Feb 2 '10 at 16:58

You can serve content directly from the repository using a combination of git-show (or git cat-file) and Apache CGI or similar. I have set up an example using .htaccess and a simple zsh script, as follows:

.htaccess

Options +ExecCGI
AddHandler cgi-script .sh

RewriteEngine On
RewriteRule ^(?!index.sh)(.*)$ /index.sh [L]

index.sh

#!/bin/zsh

# Strips the initial slash, as git show ain't got time for that!
# REQUEST_URI is set by Apache when executing the script.
# You may also be interested in HTTP_HOST which is the request host.
# Don't get confused with HOST which is a default shell variable.
function get_request_uri {
    if [ $REQUEST_URI[0,1] = '/' ]
    then
        REQUEST_URI=$REQUEST_URI:s#/##
    fi

    echo $REQUEST_URI
}

# You may not want to show the folders (represented by trees).
# Or you may want to manipulate the output to produce a proper directory index.
function is_file_folder {
    [ $(git cat-file -t HEAD:$1) = 'tree' ]
}

# Just gets the content of the file.
# For trees will produce a directory listing, one line per file.
# To show different branches, pass them to git show in place of HEAD
function get_file_content {
    local file=$1

    git show HEAD:${file}
}

# Attempts to determine the file type without writing the data to a file.
# Has to resort to manually looking up the type in /usr/share/mime/globs
# to test by extension, you may have to adjust that code to your environment.
function get_file_type {
    local file=$1
    local content=$2

    mime_type=$(echo ${content} | mimetype -b --stdin)
    if [ -e /usr/share/mime/globs ]
    then
        file_mime_type=$(
            grep "\*.${file:e}\$" /usr/share/mime/globs |
                sed -ne 's/:.*$//' -e '1p'
                    )

        # Conflict resolution is also something you should work on.
        if [ $file_mime_type != '' ]
        then
            mime_type=$file_mime_type
        fi
    fi

    echo $mime_type
}

function do_404 {
    echo 'Status: 404 Not Found'
    echo ''
}

# Just does the header because there can be problems
# getting binary data to the client by echoing it.
# A little something for you to explore a fix for :)
function do_200 {
    mime_type=$1

    echo "Content-Type: $mime_type"
    echo ''
}


cd site.git

uri=$(get_request_uri)
file_content=$(get_file_content $uri)

# A non zero exit status from git show probably means the file does not exist.
# Even if the error was something else, you still have nothing to show for it.
if [ $? -ne 0 ]
then
    do_404
fi

# You may not want to do the directory listing. Even if you do, you may
# not want to use the default directory listing (which is very primitive).
# If you wanted to redirect to index or similar, you would also handle that
# here.
if is_file_folder $uri
then
    file_type='text/plain'
else
    file_type=$(get_file_type $uri "$file_content")
fi

do_200 $file_type

# Doing this twice is not optimal, but it is very easy to
# mangle binary data accidentally when passing around the
# $file_content. Just reading the file directly to the client
# seems to sidestep this. Another thing for you to fix :)
get_file_content $uri

# You might want to uncomment the two lines below (commenting out 
# any response code from above first), to show all of the variables
# available to you. This list will include the variables set by this
# script - to filter them out just run this earlier, with an exit after.

# do_200 'text/plain'
# set

# vim: set ai et sw=4 syntax=zsh :

This assumes that your site is in site.git in the same folder as the two files above. There are plenty of improvements that can be made to the script - as commented. This is more of a proof of concept.

share|improve this answer

Also have a look at Divergence, a rack application that maps git branches to subdomains. I haven't used it my self yet, but it seems to fit your needs quite well.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.