Overview
We use cgit to host our open source Git repositories on https://privateisland.tech and also for our internal projects on local web servers. If you're not already familiar with cgit, it is a popular Common Gateway Interface (CGI) web interface written in the C language. We interact with it externally on a daily basis with sites like The Yocto Project. cgit is highly customizable, provides an intuitive web interface, and allows developers to continue working with their Git repos directly using a shell (as opposed to Github) regardless of whether the repo is bare or has a working directory.
One of the great things about the cgit CGI application is that it can be tested and debugged on a command line independent of how it is deployed (e.g., Apache), and we show this below while also building cgit from its own repo. The cgit README includes easy build instructions, but we include them below for the sake of completeness.
Clone and build cgit:
$ cd /build $ git clone https://git.zx2c4.com/cgit/ cgit Cloning into 'cgit'... $ cd cgit $ git submodule init Submodule 'git' (https://git.kernel.org/pub/scm/git/git.git) registered for path 'git' $ git submodule update Cloning into '/build/cgit/git'...
Before building the code, we create a cgit.conf file to customize the installation (i.e., install into /opt/cgit):
CGIT_SCRIPT_PATH = /opt/cgit/bin CGIT_CONFIG = /opt/cgit/etc/cgitrc CACHE_ROOT = /opt/cgit/cache/cgit prefix = /opt/cgit/local
And the installed package requirements to make both cgit and git on Ubuntu are minimal:
$ sudo apt install build-essential libssl-dev zlib1g-dev
Let's build it:
$ make $ make install $ tree /opt/cgit /opt/cgit . ├── bin │ ├── cgit.cgi │ ├── cgit.css │ ├── cgit.js │ ├── cgit.png │ ├── favicon.ico │ └── robots.txt └── local └── lib └── cgit └── filters ├── about-formatting.sh ├── commit-links.sh ├── email-gravatar.lua ├── email-gravatar.py ├── email-libravatar.lua ├── file-authentication.lua ├── gentoo-ldap-authentication.lua ├── html-converters │ ├── man2html │ ├── md2html │ ├── rst2html │ └── txt2html ├── owner-example.lua ├── simple-authentication.lua ├── syntax-highlighting.py └── syntax-highlighting.sh
Now create a simple test repo for the purpose of verifying cgit operation and add the repo to the etc/cgitrc file:
$ mkdir /opt/tst-git; cd /opt/tst-git $ touch a $ git init Initialized empty Git repository in /opt/tst-git/.git/ $ git add . $ git commit -m 'initial commit' [master (root-commit) 7891040] initial commit ... $ mkdir -p /opt/cgit/etc $ touch /opt/cgit/etc/cgitrc
Add the following lines to etc/cgitrc:
repo.url=tst-git repo.path=/opt/tst-git/.git
Now try running cgit.cgi from the command line:
$ ./cgit.cgi Content-Type: text/html; charset=UTF-8 Last-Modified: Sun, 15 Sep 2024 03:33:37 GMT Expires: Sun, 15 Sep 2024 03:38:37 GMT <!DOCTYPE html> <html lang='en'> <head> <title>Git repository browser</title> <meta name='generator' content='cgit v1.2.3-70-g09d2'/> <meta name='robots' content='index, nofollow'/> <link rel='stylesheet' type='text/css' href='/cgit.css'/> <script type='text/javascript' src='/cgit.js'></script> <link rel='shortcut icon' href='/favicon.ico'/> </head> <body> <div id='cgit'><table id='header'> <tr> <td class='logo' rowspan='2'><a href='cgit.cgi/'><img src='/cgit.png' alt='cgit logo'/></a></td> <td class='main'>Git repository browser</td></tr> <tr><td class='sub'>a fast webinterface for the git dscm</td></tr></table> <table class='tabs'><tr><td> <a class='active' href='cgit.cgi/'>index</a></td><td class='form'><form method='get' action='cgit.cgi/'> <input type='search' name='q' size='10' value=''/> <input type='submit' value='search'/> </form></td></tr></table> <div class='content'><table summary='repository list' class='list nowrap'><tr class='nohover'><th class='left'><a href='cgit.cgi/?s=name'>Name</a></th><th class='left'><a href='cgit.cgi/?s=desc'>Description</a></th><th class='left'><a href='cgit.cgi/?s=owner'>Owner</a></th><th class='left'><a href='cgit.cgi/?s=idle'>Idle</a></th></tr> <tr><td class='toplevel-repo'><a href='cgit.cgi/tst-git/'>tst-git</a></td><td><a href='cgit.cgi/tst-git/'>[no description]</a></td><td><a href='cgit.cgi/?q='></a></td><td><span class='age-mins' data-ut='1726371138' title='2024-09-15 03:32:18 +0000'>1 min.</span></td></tr> </table></div> <!-- class=content --> <div class='footer'>generated by <a href='https://git.zx2c4.com/cgit/about/'>cgit v1.2.3-70-g09d2</a> (<a href='https://git-scm.com/'>git 2.46.0</a>) at 2024-09-15 03:33:37 +0000</div> </div> <!-- id=cgit --> </body> </html>
Next, you can test that cgit returns the repo's contents when passing the tst-git url to it:
$ QUERY_STRING="url=tst-git" ./cgit.cgi ...
Below is how this looks when served up from our local Apache web server:
Debugging cgit
When we first started testing cgit, we were getting a segfault due to a typo in our cgitrc file. Instead of "repo.path", we had "reo.path". Here's how we debugged / discovered it:
$ QUERY_STRING="url=tst-git" ./cgit.cgi Segmentation fault (core dumped)
Next, we run it again, but this time with gdb and generate a backtrace:
$ QUERY_STRING="url=tst-git" gdb ./cgit.cgi GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git ... (gdb) run Starting program: /opt/cgit/bin/cgit.cgi Program received signal SIGSEGV, Segmentation fault. __strlen_sse2 () at ../sysdeps/x86_64/multiarch/../strlen.S:120 120 ../sysdeps/x86_64/multiarch/../strlen.S: No such file or directory. (gdb) backtrace #0 __strlen_sse2 () at ../sysdeps/x86_64/multiarch/../strlen.S:120 #1 0x00007ffff73e2aa9 in __add_to_environ (name=name@entry=0x55555569acef "GIT_DIR", value=0x0, combined=combined@entry=0x0, replace=replace@entry=1) at setenv.c:131 #2 0x00007ffff73e2c8a in __setenv (name=name@entry=0x55555569acef "GIT_DIR", value=<optimized out>, replace=replace@entry=1) at setenv.c:259 #3 0x0000555555561dc5 in prepare_repo_env (nongit=0x7fffffffcfdc) at ../cgit.c:564 #4 process_request () at ../cgit.c:718 #5 0x00005555555636bc in cache_process (size=<optimized out>, path=<optimized out>, key=<optimized out>, ttl=<optimized out>, fn=fn@entry=0x555555561d70 <process_request>) at ../cache.c:370 #6 0x0000555555562ad7 in cmd_main (argc=<optimized out>, argv=<optimized out>) at ../cgit.c:1096 #7 0x000055555555f6af in main (argc=1, argv=0x7fffffffe438) at common-main.c:45
From here, we started stepping through the code in prepare_repo_env and soon realized that repo.path was set to null and causing the segfault.
Instead of stepping through the code in gdb on the command line, we created a quick C project for it in Eclipse inside our /build/cgit directory, set the CGIT_CONFIG and QUERY_STRING environment variables, and found the problem by stepping through the /build/cgit application in the Eclipse IDE.
Testing with Apache
Below we follow Apache's Tutorial on Dynamic Content with CGI to set up a test cgit server using the Apache Web Server on Ubuntu 18.04 Linux.
Make sure we have cgid module support enabled:
$ ls -l /etc/apache2/mods-enabled/*cgi* ls: cannot access '/etc/apache2/mods-enabled/*cgi*': No such file or directory $ sudo a2enmod cgid Enabling module cgid. $ sudo apachectl -k restart $ ls -l /etc/apache2/mods-enabled/*cgi* lrwxrwxrwx 1 root root 27 Apr 29 17:43 /etc/apache2/mods-enabled/cgid.conf -> ../mods-available/cgid.conf lrwxrwxrwx 1 root root 27 Apr 29 17:43 /etc/apache2/mods-enabled/cgid.load -> ../mods-available/cgid.load
Specify the location of our static files in our "cgitrc" file:
css=/static/cgit.css logo=/static/cgit.png repo.url=tst-git repo.path=/opt/tst-git/.git
Modify our apache2.conf file
ScriptAlias "/repos/" "/opt/cgit/bin/" <Directory /opt/cgit/bin/> Require all granted </Directory>
Restart your Apache server and visit cgit.cgi (e.g., http://<host>/repos/cgit.cgi). You should see something similar to Figure 1.
Super Simple Python CGI Script
The Apache Tutorial covers a lot of great material and provides some simple example CGI scripts in Perl. However, if you're looking for something in Python, try this one (prints the environment):
#!/usr/bin/python3 import cgi print("Content-Type:text/html\n\n") cgi.print_environ()
Making Repo Changes via HTTP
As we understand it, cgit is for viewing repositories and not making changes (i.e., pushing a new commit). Fortunately Git itself provides CGI backend scripts that can be used to set up a Smart HTTP server. We document the basic steps for this in Build an Apache HTTP Server from Source and Configure it to Work as a GIT HTTP Server
We'll continue updating this article as we progress with cgit...
Additional References
- cgit About page
- Apache Tutorial: Dynamic Content with CGI
- An ANSI C library for CGI Programming
Date: Nov. 17, 2019
Author: Peter
Comment: