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 |
group
|
Default, if only |
all
|
Same as |
0<nnn>
|
Set the |
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. |
--group=<group>
|
The group which the daemon runs as.
It makes sense to use the user group ( |
--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 of127.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
, agit 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" => "" ) } }
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.
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 alsoproject-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 thescan-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
andzip
. 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
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 |
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 |
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.