7. Git Servers

The following is about hosting Git repositories and Gitolite, which allows you to flexibly manage repository access rights via SSH public keys. It also explains how to install and configure the two web interfaces Gitweb and CGit, alternatively for Apache or Lighttpd.

7.1. Hosting a Git Server

First some basics: How do repositories on a server differ from those of a normal user? And how does Git exchange the changes?

7.1.1. The Git Protocol

Git is designed for decentralized repository management; the smallest unit for exchanging changes between repositories is commits. However, since there are sometimes thousands of commits between two versions of a piece of software, and a single commit-by-commit transfer would generate a lot of overhead, commits are grouped together into so-called packfiles before being transferred. These packfiles are a simple but effective format.⁠[92] They are also used to store (older) commits on the hard disk in a space-saving way (git gc or git repack, see Sec. B.1, “Cleaning Up”).

These packfiles are usually transmitted via the Git protocol, which runs on port 9418/TCP by default. The Git protocol is deliberately kept very simple in design and offers only a few functions that are directly related to the structure of Git: What data to send or receive, and a way for the sender and receiver sides to agree on the smallest possible amount of data that needs to be transmitted to synchronize both sides.

Therefore, the Git protocol does not include any authentication option. Instead, Git uses an already existing, secure and simple authentication structure: SSH, the 'Secure Shell'.

So while the Git protocol can be used unencrypted and in raw form for anonymous read-only access, writing or uploading via Git protocol only works via SSH.

Git also supports transport via HTTP(S), FTP(S), and Rsync. Although Rsync is now considered deprecated, and should not be used anymore, there are some use cases for HTTP(S): In particularly restrictive environments with very restrictive firewall rules, you may be able to access a repository via HTTP(S) (i.e. only on port 80 or 443) for both read and write operations. Platforms like GitHub (see Ch. 11, GitHub) therefore offer HTTPS as default transport method.

7.1.2. Repositories on the Same Computer

If you want to synchronize changes to repositories on the same computer, this does not have to be done via detours: Git communicates directly with the other side via Unix pipes, negotiates a common basis and synchronizes the data. (Of course, this requires that the user invoking the Git command has at least read permission to the other repository’s pack files).

7.1.3. Bare Repositories: Repositories Without Working Tree

So far, you’ve probably worked mostly with Git repositories, which were working tree and repository in one: The repository-internal data is stored in the .git subdirectory, all other files belong to the working tree, i.e. you can edit them while Git observes and stores the changes to these files (tracking).

A so-called bare repository, i.e. a “mere” repository, has no assigned working tree. It contains only the files and directories that are stored in a “regular” repository under .git.

You create such a bare repository with git init --bare. Take a look at the difference between the two options:

$ cd /tmp/ && mkdir init-test && cd init-test
$ git init
Initialized empty Git repository in /tmp/init-test/.git/
$ ls -AF
.git/

$ mkdir ../init-test-bare && cd ../init-test-bare
$ git init --bare
Initialized empty Git repository in /tmp/init-test-bare/
$ ls -AF
branches/  config  description  HEAD  hooks/  info/  objects/  refs/

To create a backup of one of your normal repositories, you can create a new bare repository (e.g. on a USB stick) and upload all your references (and thus all your commits):

$ git init --bare /mnt/usb/repo-backup/
$ git push --all /mnt/usb/repo-backup/

7.1.4. Repository Access Permissions

With git init the files are usually created with read and write permission according to the umask setting. This is also a convenient choice for the end user. However, if you want to set up a repository on a server, you can use the --shared option to specify who (at the filesystem level) can access the repository.

umask

Default, if --shared is not specified; uses the currently set umask.

group

Default, if only --shared is specified. Assigns write permissions to all group members. Especially directories are also set to g+sx mode, allowing all group members to create new files (i.e. upload commits). Note that if the umask sets read permission for all users (a+r), this permission will still be granted.

all

Same as group, except that read permissions are explicitly granted for all, regardless of the umask.

0<nnn>

Set the umask explicitly to <nnn>.

When you initialize a repository with --shared, the receive.denyNonFastForwards option is automatically set. It prevents uploading commits that cannot be integrated via Fast-Forwards (even if the user explicitly wants to via git push -f).

7.1.5. Access via SSH: The Git Shell

Usually, write access to Git repositories located on another computer can only be granted via SSH. However, it is generally undesirable to grant a user who will have access to a repository the same user rights to the whole system.

Git works around this problem with the included git-shell program. It works like a shell, but only allows you to run four Git commands that are responsible for uploading and downloading pack files. Interactive use or execution of other commands is denied by the shell unless you explicitly enable the “Interactive Mode” of the shell — see the git-shell(1) man page for details.

If you create a new user and assign the git shell to him, e.g., using chsh <user>, he cannot log in via SSH, but he can upload commits to all git repositories to which he has write permission.

7.1.6. Access via SSH: Public Keys

It’s a major advantage that Git uses SSH as an encrypted and authenticated transport channel, because most users already have a key pair (public/private) with which they log in on other computers.

So instead of tediously assigning (and then sending out) passwords to accounts, a system administrator can limit access to Git repositories to users who authenticate against SSH public keys. This saves time for the user (by eliminating the need to re-enter a password), but also saves the administrator from having to worry about password changes (which would not be easily possible using the Git shell).

7.1.7. Example: Two Users Want to Collaborate

In the following we will show you how to set up two users on your system, max and moritz, and let them work on the same repository.

First, we have to set up a repository that the two users will want to access later. Assuming that other repositories might follow later, we will create a Unix group git (generally for Git users) and a directory /var/repositories with read permission for members of the git group, as well as a git-example group and its corresponding directory, writeable only for members of git-example, in which the repository will later be located:

$ groupadd git
$ groupadd git-beispiel
$ mkdir -m 0750 /var/repositories
$ mkdir -m 0770 /var/repositories/git-beispiel
$ chown root:git /var/repositories
$ chown root:git-beispiel /var/repositories/git-beispiel

We also create a repository in the last created directory:

$ git init --bare --shared /var/repositories/git-beispiel
$ chown -R nobody:git /var/repositories/git-beispiel

Next we create the two users. Note that this call will not create a home directory for the users under /home/. Also, both are added to the git and git-example groups:

$ adduser --no-create-home --shell /usr/bin/git-shell max
$ adduser --no-create-home --shell /usr/bin/git-shell moritz
$ adduser max git
$ adduser max git-beispiel
$ adduser moritz git
$ adduser moritz git-beispiel

Next, we have to assign a password to each user via passwd so that they can log in via SSH. Afterwards, the new users can now work together on a project. You add the remote as follows:

$ git remote add origin max@server:/var/repositories/git-example

All other users who want to contribute to this project must belong to the git-example group. So this approach is essentially based on the use of Unix groups and Unix users. However, a server admin usually wants to offer not only Git, but various services. And to control the user administration entirely via Unix groups is rather inflexible.

7.2. Gitolite: Simple Git Hosting

The aforementioned described way of managing users has some major disadvantages. Namely:

  • A full Unix account must be created for each user. This means a lot of additional work for the administrator and possibly also opens security holes.

  • For each project a separate Unix group must be created.

  • For each user created, the access permissions must be adjusted manually (or via a script).

The program Gitolite provides a remedy.⁠[93]

Gitolite originated from the Gitosis project, which is now considered obsolete. The idea: Only only Unix user (e.g. git) is created on the server. Internally, Gitolite then manages a list of users with associated SSH keys. But these users do not have a “real” user account on the system.

Users log just into the git account with their public SSH keys. This has three major advantages:

  • No password needs to be assigned or changed.

  • Users can store multiple SSH keys (for different computers they work on).

  • Using the SSH key a user logs in with, Gitolite can uniquely[94] derive the internal username and thus the permissions on the repositories managed by Gitolite.

7.2.1. Installing Gitolite

The installation of Gitolite is simple. All you need to do is have your public key ready to register as an administrator. You don’t need root privileges unless you need to create the git user first,⁠[95] so skip the next step if you have already created such a user.

First, create a user on the computer that will act as the git server (henceforth <server>). Usually, this user is called git, but you may also call it something else (e.g. gitolite). You can specify /home/git as your home directory or, as in this example, something like /var/git:

server# adduser --home /var/git git

Now switch to the git user. Gitolite needs the .ssh/ and bin/ directories, so we need to create them:

server$ mkdir -m 0700 ~/.ssh ~/bin

Now clone the Gitolite repository and install a symlink to bin (this is already the whole installation):

server$ git clone git://github.com/sitaramc/gitolite
server$ gitolite/install -ln

You can now configure Gitolite and enter your public key with which you want to manage the Gitolite configuration:

server$ bin/gitolite setup -pk <ihr-key>.pub

Check that Gitolite works on the computer where you normally work (and where you have stored the corresponding private key):

client$ ssh -T git@<server>
...
 R W    gitolite-admin

You should verify that your key gives you read and write permission to the gitolite-admin repository. Now clone it onto your computer:

client$ git clone git@<server>:gitolite-admin

The repository contains the entire configuration for Gitolite. You check in your changes there and upload them via git push: the server automatically updates the settings.

7.2.2. Configuring Gitolite

The Gitolite admin directory contains two subdirectories, conf and keydir. To introduce a new user to Gitolite, you need to put their SSH key under keydir/<user>.pub. If the user has multiple keys, you can store them in separate files of the format <user>@<description>.pub:

client$ cat > keydir/feh@laptop1.pub
ssh-dss AAAAB3NzaC1kc3M ... dTw== feh@mali
^D
client$ cat > keydir/feh@laptop2.pub
ssh-dss AAAAB3NzaC1kc3M ... 5LA== feh@deepthought
^D

Don’t forget to check in the new keys with git add keydir followed by git commit. To make them known to the gitolite installation, you also need to upload the commits using git push.

Then you can assign permissions to this username in the conf/gitolite.conf configuration file.

You can save yourself a lot of administrative work and typing by using macros. You can combine groups (of users or repositories), e.g.:

@test_entwickler = max markus felix
@test_repos      = test1 test2 test3

These macros are also evaluated recursively. When defining them, it does not have to be clear whether they are users or repositories; the macros are only evaluated at runtime. This allows you to create groups from other groups:

@proj = @developer @tester @admins

There is a special group @all which, depending on the context, contains all users or all repositories.

You can configure one (or more) repositories as follows:

repo @test_repos
    RW+ = @test_entwickler

R and W stand for read or write access. The plus means that forced uploading is also allowed (non-fast-forward, i.e. also deleting commits).

For a repository, of course, several such lines can be entered. In a small project there could be maintainers, other developers and testers. Then the access rights could be regulated as follows:

@maintainers = ... # Hauptentwickler/Chefs
@developers  = ... # Weitere Entwickler
@testers     = ...

repo Projekt
    RW+ = @maintainers
    RW  = @developers
    R   = @testers

Thus, the testers have read-only access, while the developers are allowed to upload new commits, but only if they can be integrated via fast-forward. The main maintainers are allowed “everything”.

These lines are processed sequentially. If the line applies to a user, Gitolite authorizes the user and grants him the appropriate rights. If no line matches the user, the user is rejected and is not allowed to change anything in the repository.

A user can view all his permissions by simply logging into the Git server via SSH. Immediately after installation, this is how it looks like to the administrator:

$ ssh -q git@<server>
hello feh, this is git@mjanja running gitolite3 v3.6.1-6-gdc8b590 on git 2.1.0

 R W     gitolite-admin
 R W     testing

7.2.3. Ownership and Description

If you want to install a web-based tool to browse the Git repositories later, you should also name a person in charge and describe the project:

repo <repo-name>
  # Zugriffsrechte
  config gitweb.owner = "Julius Plenz"
  config gitweb.description = "Ein Test-Repository"

For this to work, you must first enable Gitolite to set these config settings: This is done on the server where Gitolite is installed, in the file .gitolite.rc: Enter the value gitweb\..* under the GIT_CONFIG_KEYS key.

7.2.4. Access Rights on File or Branch Level

Especially in corporate environments, access rights often have to be differentiated even more finely than a mere “has access” and “must not access”. For this purpose, Gitolite offers access restriction on directory- and file- as well as tag- and branch-level.

We will first look at a case that occurs frequently: developers should be able to develop on development branches at will, but only a small group of maintainers should be able to edit “important” branches such as master.

This could be implemented in a similar way:

@maintainers = ...
@developers  = ...

repo Projekt
    RW+ dev/    = @developers
    RW+         = @maintainers
    R           = @developers

Here a “development namespace” is created: The group of developers can work with branches below dev/, e.g. create dev/feature or delete it. However, the developers can only read the master branch, not change it — this is reserved for the maintainers.

The part between the flags (RW+) and the equal sign is a so-called Perl-Compatible Regular Expression (PCRE). If it does not start with refs/, the expression refers to all references below refs/heads/, i.e. branches. In the above example, any references below refs/heads/dev/ can be modified — but not the dev branch itself, nor anything-dev!

But if such an expression starts explicitly with refs/, you can manage any references. In the following way you can set up that all maintainers are allowed to create Release Candidate tags,⁠[96] but only one maintainer is really allowed to create the versioning tag (or any other):

repo Projekt
    RW+ refs/tags/v.*-rc[0-9]+$     = @maintainers
    RW+ refs/tags/                  = <projektleiter>

If one of the maintainers still wants to upload a tag like v1.0, the following happens:

remote: W refs/tags/v1.0 <repository> <user> DENIED by fallthru
remote: error: hook declined to update refs/tags/v1.0
To <user>:<repository>
 ! [remote rejected] v1.0 -> v1.0 (hook declined)

As mentioned above, here the rules are applied one after the other. Since the tag v1.0 does not match the regular expression above, only the bottom line comes into question, but the username does not match. No line is left (fallthru), so the action is not allowed.

7.2.5. Personal Namespaces

The concept of personal namespaces is somewhat more flexible. This gives each developer his own hierarchy of branches that he can manage.

There is a special keyword for this, USER, which is replaced by the user name currently accessing the branch. This makes the following possible:

repo Projekt
    RW+ p/USER/  = @developers
    R            = @developers @maintainers

Now all developers under p/<user>/ can manage their branches as they like. The lower directive makes sure that all developers can read these branches. Now max can e.g. create p/max/bugfixes, but moritz can only read them.

7.2.6. File-Level Access Control

Gitolite also allows file- and directory-level access restrictions. The virtual reference VREF/NAME is responsible for this. For example, you can allow the documentation team only (writing⁠[97]) access to doc/:

@doc = ...  # Dokumentations-Team

repo Projekt
    RW VREF/NAME/doc/   = @doc
    -  VREF/NAME/       = @doc

However, the following pitfalls must be taken into account: Once the keyword VREF/NAME appears once, the file-based rules are applied to all users. If none of them apply, access is allowed — so the second rule is important, which prohibits access for @doc unless the commit only modifies files under doc/ (see also Sec. 7.2.7, “Explicitly Prohibiting Actions” below).

Access control checks at the commit level which files are modified; if a commit contains changes to a file that the user is not allowed to edit, the entire push process is aborted. In particular, no actions can be performed that involve commits from other developers that modify files outside the allowed range.

Specifically, in relation to the above example, this means that @doc members generally cannot create new branches. Creating a new branch would mean creating a new reference to an initial commit and then fast-forwarding all commits from top to root, i.e., the entire project history. However, there are certainly commits in it that modify files outside of doc/, and so the action is prohibited.

7.2.7. Explicitly Prohibiting Actions

Previously, a user was only rejected if he failed all rules (fallthru), i.e. if no rights were assigned to him. But the - flag (instead of RW) can be used to explicitly restrict access. Again, the rules are passed through from top to bottom.

repo Projekt
    -   VREF/NAME/Makefile   = @developers

This directive prohibits members of @developers from making commits that modify the Makefile.⁠[98]

By convention, you should never upload forced updates to the master or maint branches (see also Sec. 3.1, “References: Branches and Tags”). You can now force this policy with Gitolite:

repo Projekt
    RW  master maint    = @developers
    -   master maint    = @developers
    RW+                 = @developers

If a branch that is not called master or maint is uploaded, only the third rule is applied and arbitrary access (including non fast-forward updates) is allowed. Commits that can be integrated to master or maint via fast-forward are allowed by the first rule. Note the missing plus sign, though: A forced update is not covered by the first rule, but by the second one, which explicitly prohibits everything (that has not been allowed before).

7.2.8. Should Policies Be Enforced?

With the means presented here and others, which you can take from the documentation, ⁠[99] you are able to force policies very flexibly. However, it may not be useful to control everything down to the smallest detail. As mentioned above, especially a control on file name level is problematic. Then, if hours of work go into a commit, but it can’t be uploaded because one of those restrictions prohibits it, the frustration is great (and fixing that commit is not trivial, either; see rebase, Sec. 4.1, “Moving commits — Rebase”).

At the branch level, it makes sense to give only a limited group of developers access to “important” branches (such as master). Of course, strict control over who can do what comes at the expense of flexibility, and it’s this flexibility that makes branching in Git so practical.

7.3. Git Daemon: Anonymous Read-Only Access

The Git daemon allows unencrypted, anonymous, read-only access to Git repositories via the Git protocol. It comes with Git and usually runs on TCP port 9418 (and can thus be started without root privileges).

  • The transmission is not encrypted. However, the cryptographic integrity that Git constantly checks excludes the possibility of attackers manipulating the data stream and smuggling in malicious code.⁠[100]

  • This way is ideal for making source code available to a large number of people quickly and easily. Only the minimum of necessary information is downloaded (only the required commits are negotiated and then transferred packed).

In order to export one or more repositories, simply execute git daemon <path>, where <path> is the path where your repositories are located. You can also specify multiple paths. If you have already set up Gitolite as above, /var/git/repositories is a useful path.

For testing, you can run a Git daemon on a single repository:

$ touch .git/git-daemon-export-ok
$ git daemon --verbose /home/feh/testrepo

Then clone (preferably into a temporary directory) this very repository:

$ git clone git://localhost/home/feh/testrepo
Initialized empty Git repository in /tmp/tmp.kXtkwxKgkc/testrepo/.git/
remote: Counting objects: 130, done.
remote: Compressing objects: 100% (102/102), done.
Receiving objects: 100% (130/130), 239.71 KiB, done.
Resolving deltas: 100% (54/54), done.
remote: Total 130 (delta 54), reused 0 (delta 0)

However, the Git daemon will only export a repository if a git-daemon-export-ok file is created in the .git directory (as done above; in the case of bare repositories, of course, this must be done in the directory itself). This is done for security reasons: For example, /var/git/repositories may contain many (even private) repositories, but only those that really need to be exported without access control will receive this file.

However, the daemon accepts the --export-all option, which removes this restriction and exports all repositories in all subdirectories.

Another important setting is the Base Path, which is the path where the actual Git repositories are located. Start the Git daemon as follows:

$ git daemon --base-path=/var/git/repositories /var/git/repositories

every request for a git repository is preceded by the base path. Now users can clone a repository with the address git://<server>/<project>.git instead of using the cumbersome git://<server>/var/git/repositories/<project>.git.

7.3.1. Git-Daemon and Inetd

As a rule, the Git daemon is supposed to constantly deliver a large number of repositories. To do this, it runs constantly in the background or is restarted for each request. The latter task is typically performed by Inetd from OpenBSD. To make this work, you just need to add the following (one!) line to /etc/inetd.conf:

git     stream  tcp     nowait  <user>   /usr/bin/git git daemon
  --inetd --base-path=/var/git/repositories /var/git/repositories

<user> must be a user who has read access to the repositories. This can be root, because the Inetd normally runs with root privileges, but should be git or a similarly unprivileged account.

The configuration for the xinetd is similar, but more self-explanatory. It is stored e.g. under /etc/xinet.d/git-daemon:

service git
{
    disable         = no
    type            = UNLISTED
    port            = 9418
    socket_type     = stream
    wait            = no
    user            = <user>
    server          = /usr/bin/git
    server_args     = daemon --inetd --base-path=... ...
    log_on_failure  += USERID
}

Do not forget to restart the respective daemon via /etc/init.d/[x]inetd restart.⁠[101]

7.3.2. The Debian Way: Git Daemon SV

Debian offers the git-daemon-run package which contains configuration files for sv.⁠[102] The package essentially creates a gitlog user and two executable shell scripts, /etc/sv/git-daemon/run and /etc/sv/git-daemon/log/run. Modify the former to run the Git daemon in the directory where your repositories are located:

#!/bin/sh
exec 2>&1
echo _git-daemon starting._
exec git-daemon --verbose --listen=203.0.113.1 --user=git --group=git \
  --reuseaddr --base-path=/var/git/repositories /var/git/repositories

If you start the Git daemon from a shell script this way (or similarly via SysV-Init), the script will be executed with root privileges. The following options are therefore useful:

--user=<user>

The user which the daemon runs as (e.g. git). Must have read access to the repositories.

-⁠-⁠group⁠=⁠<group>

The group which the daemon runs as. It makes sense to use the user group (git) or nobody.

--reuseaddr

Prevents the daemon restart from going wrong because there are still open connections waiting for a timeout. This option uses the bind address even if there are still connections. You should always specify this option if an instance is running continuously.

If you are using SysV-Init, which means that services are usually started via symlinks in /etc/rc2.d/ to scripts in /etc/init.d/, you will also need to create the following symlinks to automatically start the git daemon when the system boots

# ln -s /usr/bin/sv /etc/init.d/git-daemon
# ln -s ../init.d/git-daemon /etc/rc2.d/S92git-daemon
# ln -s ../init.d/git-daemon /etc/rc0.d/K10git-daemon
# ln -s ../init.d/git-daemon /etc/rc6.d/K10git-daemon

7.3.3. The Git Daemon on Production Systems

On a production system that is more than just a Git server, you may encounter the following situations:

  • There are several network cards or virtual interfaces.

  • The service should run on a different port.

  • Different IPs should deliver different repositories.

The Git daemon provides options to respond to such situations. They are summarized below. For more detailed explanations, please consult the git-daemon man page.

--max-connections=<n>

By default, the Git daemon only allows 32 simultaneous connections. With this option you can increase the number. A value of 0 allows any number of connections.⁠[103]

--syslog

Uses the syslog mechanism instead of standard error to log error messages.

--port=<n>

Uses a port other than 9418.

--listen=<host/ip>

Determines which interface the Git daemon should bind to. By default the daemon is accessible on all interfaces, so it binds to 0.0.0.0. A setting of 127.0.0.1, for example, only allows connections from the local machine.

--interpolated-path=<template>

If a Git daemon shall offer different repositories depending on the interface-address, this is controlled by the <template>: %IP is replaced by the IP address of the interface, where the connection comes in, and %D by the given path. With a template of /repos/%IP%D, a git clone git://localhost/testrepo will display the following message in the log files: interpolated dir '/repos/127.0.0.1/testrepo' (because the connection is established via the loopback interface). For each interface on which the Git daemon runs, there must be a subdirectory in /repos/ with the interface’s corresponding IP address in which exportable repositories are located.

7.3.4. Specifying Exportable Repositories on Gitolite

Gitolite knows a special username, daemon. For all repositories where this user has read permission, the file git-daemon-export-ok is automatically created. So you can use Gitolite to directly specify which repositories to export:

repo Projekt
    R = daemon

Note that this setting has no effect if you start the Git daemon with the --export-all option. Also, you cannot give this permission to all repositories via repo @all.

7.4. Gitweb: The Integrated Web Frontend

Git comes with an integrated, browser-based frontend called Gitweb. The frontend allows you to search the entire version history of a project: Each commit can be viewed with full details, differences between commits, files or branches, as well as all log messages. In addition, each snapshot can be downloaded individually as a tar archive (this is especially handy for Git newbies).

To get an overview of the functionality, you can use the command git instaweb to set up a temporary web server with Gitweb without further configuration.

Git does not come with its own web server. You can use the --httpd=<webserver> option to specify which web server Git should use to deliver the page. If you just want to try out Gitweb, we recommend using the webrick web server — this is a small web server that automatically ships with the Ruby scripting language.

As soon as you execute the following command, the web server will be started and the page will be displayed in the browser (which browser is used can be specified with the --browser option).

$ git instaweb --httpd=webrick

Note that the command must be started at the top level of a Git directory. If necessary, stop the web server with the following command:

$ git instaweb --stop

7.4.1. Installing Gitweb Globally

Many distributions already include Gitweb as a separate package or directly in the Git package. Under Debian, the corresponding package is called gitweb. If you are not sure if Gitweb is available on your system, you should check under /usr/share/gitweb and install it if necessary.

Gitweb only requires a large Perl script plus a configuration file and optionally a logo, CSS stylesheet, and favicon. The configuration file is usually located in /etc/gitweb.conf, but can also be named differently. It is important that each time the Perl script is called, the environment variable GITWEB_CONFIG is used to specify where this file is located.

Usually you should already have such a file. The following list shows the most important configuration options.

Attention: The file must be written in valid Perl. In particular, do not forget the concluding semicolon when assigning variables!

$projectroot

The directory where your Git repositories are located.

$export_ok

File name that determines whether a repository should be visible in Gitweb. You should set this variable to "git-daemon-export-ok" so that only those repositories that are also delivered by the Git daemon are displayed.

@git_base_url_list

Array of URLs that can be used to clone the project. These URLs appear in the project overview and are very helpful to give people quick access to the source code after they have gotten a brief overview. It’s best to specify the URL where your Git daemon can be reached, e.g. ('git://git.example.com').

$projects_list

Assignment of projects and their owners. This project list can be automatically generated by Gitolite; see the sample configuration file below.

$home_text

Absolute path to a file containing, for example, a company or project-specific text module. This is displayed above the list of repositories.

If you installed Gitolite as mentioned above, and your repositories are located under /var/git/repositories, the following Gitweb configuration should be sufficient:

$projects_list = "/var/git/projects.list";
$projectroot = "/var/git/repositories";
$export_ok = "git-daemon-export-ok";
@git_base_url_list = (_git://example.com_);

7.4.2. Gitweb and Apache

Assuming that you have installed the CGI script under /usr/lib/cgi-bin and the image and CSS files under /usr/share/gitweb (as the Debian gitweb package does), configure Apache as follows:

Create /etc/apache2/sites-available/git.example.com with the following content:

<VirtualHost *:80>
  ServerName    git.example.com
  ServerAdmin   admins@example.com

  SetEnv GITWEB_CONFIG /etc/gitweb.conf

  Alias /gitweb.css         /usr/share/gitweb/gitweb.css
  Alias /git-logo.png       /usr/share/gitweb/git-logo.png
  Alias /git-favicon.png    /usr/share/gitweb/git-favicon.png
  Alias /                   /usr/lib/cgi-bin/gitweb.cgi

  Options +ExecCGI
</VirtualHost>

Then you need to activate the virtual host and let Apache reload the configuration:

# a2ensite git.example.com
# /etc/init.d/apache2 reload

7.4.3. Gitweb and Lighttpd

Depending on how you implement virtual hosts in Lighttpd, the configuration might look different. Three things are important: That you make aliases for the globally installed Gitweb files, set the environment variable GITWEB_CONFIG and that CGI scripts are executed. To do this you need to load the modules mod_alias, mod_setenv and mod_cgi (if you haven’t already done so).

The configuration then looks like this:⁠[104]

$HTTP["host"] =~ "^git\.example\.com(:\d+)?$" {
    setenv.add-environment = ( "GITWEB_CONFIG" => "/etc/gitweb.conf" )
    alias.url = (
        "/gitweb.css"       => "/usr/share/gitweb/gitweb.css",
        "/git-logo.png"     => "/usr/share/gitweb/git-logo.png",
        "/git-favicon.png"  => "/usr/share/gitweb/git-favicon.png",
        "/"                 => "/usr/lib/cgi-bin/gitweb.cgi",
    )
    $HTTP["url"] =~ "^/$" {
        cgi.assign = ( ".cgi" => "" )
    }
}
gitweb overview
Figure 42. Gitweb’s Summary page
gitweb commitdiff
Figure 43. Viewing a commit in Gitweb

7.5. CGit — CGI for Git

CGit (“CGI for Git”) is an alternative web frontend. Unlike Gitweb, which is written entirely in Perl, CGit is written in C and uses caching where possible. This renders it much faster than Gitweb.

To install CGit, you need to download the sources first. You will need the latest version of Git to access routines from the Git source code. To do this, you need to initialize the already configured submodule and download the code:

$ git clone git://git.zx2c4.com/cgit
...
$ cd cgit
$ git submodule init
Submodule 'git' (git://git.kernel.org/pub/scm/git/git.git) registered
for path 'git'
$ git submodule update
<Git-Sourcen werden heruntergeladen.>

By default CGit installs the CGI file in a somewhat obscure directory /var/www/htdocs/cgit. To choose more sensible alternatives, create a file cgit.conf in the CGit directory, which is automatically included in the Makefile:

CGIT_SCRIPT_PATH=/usr/lib/cgi-bin
CGIT_DATA_PATH=/usr/share/cgit

Now the program can be compiled and installed via make install. However, it is recommended to use checkinstall[105] so that you can easily get rid of the package if necessary.

cgit overview
Figure 44. Overview page of CGit

7.5.1. CGit, Apache and Lighttpd

The integration in Apache and Lighttpd is similar. However, since CGit uses “nicer” URLs (like http://git.example.com/dwm/tree/dwm.c for the dwm.c file from the dwm repository), a little effort is required to rewrite the URLs.

The following configurations run CGit on git.example.com:

<VirtualHost *:80>
  ServerName git.example.com

  AcceptPathInfo On
  Options +ExecCGI

  Alias /cgit.css /usr/share/cgit/cgit.css
  Alias /cgit.png /usr/share/cgit/cgit.png
  AliasMatch ^/(.*) /usr/lib/cgi-bin/cgit.cgi/$1
</VirtualHost>

For Lighttpd you have to resort to some tricks. You must not forget to configure virtual-root=/ (see below — this setting is not harmful for Apache either).

$HTTP["host"] =~ "^git\.example\.com(:\d+)?$" {
    alias.url = (
        "/cgit.css" => "/usr/share/cgit/cgit.css",
        "/cgit.png" => "/usr/share/cgit/cgit.png",
        "/cgit.cgi" => "/usr/lib/cgi-bin/cgit.cgi",
        "/"         => "/usr/lib/cgi-bin/cgit.cgi",
    )
    cgi.assign = ( ".cgi" => "" )
    url.rewrite-once = (
        "^/cgit\.(css|png)" => "$0", # statische Seiten "durchreichen"
        "^/.+" => "/cgit.cgi$0"
    )
}

7.5.2. Configuration

The configuration is controlled by the file /etc/cgitrc. A list of supported options can be found in the file cgitrc.5.txt in the source directory of CGit (unfortunately the program does not include any other documentation). The most important ones are listed below:

clone-prefix

URL where the source code (preferably via Git protocol) can be downloaded (similar to @git_base_url_list from Gitweb).

enable-index-links

If set to 1, another column appears in the repository listing, with direct links to the tabs “summary”, “log” and “tree”.

enable-gitweb-owner

If set to 1, the owner is read from the Git repository’s gitweb.owner configuration. Gitolite sets this option automatically when you specify a name, see Sec. 7.2.3, “Ownership and Description”.

enable-log-filecount

Displays a column for each commit, showing the number of changed files.

enable-log-linecount

Analogous to -filecount, displays a summary of added/removed rows.

scan-path

Path that CGit should search for Git repositories. Attention: This option doesn’t take into account whether the repository has been released by the git-daemon-export-ok file (see also project-list)! Also note that the repositories added in this way will only inherit the settings that were made up to that point. It is therefore recommended to list the scan-path line last in the file.

project-list

List of project files to be included in the scan-path. Gitolite creates such a file for all public repositories. See the sample configuration below.

remove-suffix

If the option is set to 1: the .git suffix is removed from URLs or repository names.

root-title

Headline that is displayed on the home page, next to the logo.

root-desc

Lettering that is displayed on the home page, under the headline.

side-by-side-diffs

If the option is set to 1, diff output will display two files side by side instead of using the unified diff format.

snapshots

Specifies which snapshot formats are offered. By default, none are offered. Possible values are tar, tar.gz, tar.bz2 and zip. Specify the desired formats separated by spaces.

virtual-root

Specifies which URL CGit should prefix to each link. If you set CGit to a “higher”" layer, e.g. http://git.example.com, this option should be set to / (this is especially necessary if you use Lighttpd). If you want to run CGit in a subdirectory instead, you should adjust this option accordingly, e.g. to /git.

With the following configuration, any repository you have allowed Gitweb access to in Gitolite will appear in the listing — and the description and author (if specified, see Sec. 7.2.3, “Ownership and Description”) will also be displayed:

virtual-root=/
enable-gitweb-owner=1
remove-suffix=1
project-list=/var/git/projects.list
scan-path=/var/git/repositories
cgit commitdiff
Figure 45. Viewing a commit in CGit

7.5.3. Special Configuration of Individual Repositories

With the scan-path option explained above, in combination with Gitolite it is usually not necessary to add and configure repositories individually. However, if you want to do this, or if your repositories are not stored in a central location, you can do this per repository as follows:

repo.url=foo
repo.path=/pub/git/foo.git
repo.desc=the master foo repository
repo.owner=fooman@example.com

For more repository-specific configurations, consult the sample configuration file or the explanations of the options in the cgitrc.5.txt file in the source directory of CGit. You can also group these manually configured repositories under different sections (option section).

7.5.4. Exploiting Caching

CGit is especially fast compared to Gitweb because it is written in C and also supports caching. This is especially necessary if you have many repositories and/or many page views in a short time.

CGit uses a simple hash mechanism to check if a request is already in the cache and not too old (configurable, see list below). If such a cache entry is present, it will be delivered instead of re-creating the same page (the HTTP header Last-Modified stays the same, i.e. the browser knows when the page is from).

CGit also caches the result of scan-path. This way CGit doesn’t have to add all repositories one by one for the overview page each time.

cache-root

Path where the cache files are stored; defaults to /var/cache/cgit.

cache-size

Number of entries (i.e. individual pages) that the cache contains. The default value is 0, so caching is disabled. A value of 500 should be enough even for large pages.

cache⁠-⁠<type>⁠-⁠ttl

Time in minutes for a cache entry to be considered “current”. You can configure the time specifically for individual pages. Possible <type> values are: scanrc for the result of scan-path, root for the repository listing, repo for the “home” page of a repository, and dynamic or static for the “dynamic” pages (such as for branch names) or static pages (such as for a commit identified by its SHA-1 sum). By default, these values are set to five minutes, except for scanrc (15).

Another important factor that influences how fast the index page builds up is the use of so-called age files. The Idle column is usually recreated each time CGit goes through the branches of each repository and notes the age. This is not very fast though.

It’s more practical to use one file per repository, indicating when the last commit was uploaded. This is best done with hooks (see Sec. 8.2, “Hooks”). Use this command in the post-update hook:

mkdir -p info/web || exit 1
git for-each-ref \
    --sort=-committerdate \
    --format='%(committerdate:iso8601)' \
    --count=1 'refs/heads/*' \
    > info/web/last-modified

If you want to use a different path instead of info/web/last-modified (relative to $GIT_DIR), use the CGit configuration key agefile for the specification.


92. A more detailed description can be found in the Git source repository in the Documentation/technical directory. There you can find three files that explain the packfile format, partly based on explanations by Linus Torvalds on IRC: pack-format.txt, pack-heuristics.txt, pack-protocol.txt. Modern versions of Git also use an additional “Bitmap Reachability Index,” which is explained in bitmap-format.txt.
93. The installation and configuration described here refers to Gitolite version 3.6. Since Gitolite version 1.5, which was described in the first edition of this book, there have been some incompatible changes, which you can read about here: https://gitolite.com/gitolite/migr.html
94. A user can only authenticate to an SSH server with his private key if he can decrypt a message encrypted with his public (and Gitolite’s) key. Gitolite can derive the internal user name from the key the user authenticates against.
95. Some distributions also provide ready-made packages of Gitolite. However, it is not recommended to use them because they are usually outdated and are installed globally and with a certain configuration. If you then choose a different username than the one chosen by the developers, you will have to spend a lot of extra effort to get Gitolite working.
96. A release candidate of a software is a pre-release version of a new release that is made available to the public (and not only to a small group of beta testers). Only bug fixes are then incorporated into the final release. Version 1.0 RC 1 (v1.0-rc1) is followed by RC 2 (v1.0-rc2) etc. until version 1.0 is released (v1.0).
97. Of course, Gitolite cannot prohibit read-only access to a subdirectory; this would make the concept of the Git object model with its cryptographically guaranteed integrity absurd.
98. Please also note that this may again cause problems when creating branches, see above.
99. The documentation can be found at https://gitolite.com. The author has also published the book Gitolite Essentials (Packt Publishing, 2014).
100. Strictly speaking, it is necessary for the copied HEAD to match that of the opposite side. Better still, check a version tag signed by a developer.
101. In some distributions, such as Debian, the daemon is called openbsd-inetd.
102. The program sv is part of the init framework runit (http://smarden.org/runit/). It replaces the functionality of SysV-Init, but can also be integrated into it.
103. Note that an instance of the Git daemon is not “expensive.” Packing the requested objects together is, however. So just because your server can handle dozens of HTTP requests per second doesn’t mean it can handle the same number of Git connections.
104. Note that the order in the alias.url directive is important. If you use the line "/" => …​ to the top, Lighttpd will no longer start or the alias assignment will not be the desired one.
105. The tool checkinstall automatically builds Debian or RPM packages containing all files that would have been installed by make install. Homepage of the program: https://www.asic-linux.com.mx/~izto/checkinstall/