git 1.7, apache2 über https mit smart-http

git 1.7, apache2 über https mit smart-http

git ist svn in vielen Belangen überlegen. Am Eindrücklichsten werden die Vorteile von git von Linus Torvalds, dem Autor von git, erklärt. Es gibt etliche Tutorials und Einführungen zu git.

Seit git-Version 1.6.6 kann man ein remote repository sehr effizient über http(s) betreiben, wenn man smart-http aktiviert. Dies geht, indem man das cgi-Modul von git in einen Webserver einbaut. Dieser Weg ist ähnlich effizient wie git über ssh oder über das git-Protokoll.

Schön ist, dass man für jedes repository einen eigenen apache-vhost verwenden kann. So sind alle Haupt-Projekte sauber voneinander getrennt und haben ihre eigene Benutzerverwaltung. Die Benutzerverwaltung kann natürlich auf alle Arten, die der Apache beherrscht, erfolgen, z.B. LDAP. Im Beispiel verwenden wir Digest-Dateien, man könnte aber auch htpasswd verwenden, solange man ausschließlich https benutzt (hier wird das Passwort verschlüsselt übertragen).

Außerdem müssen keine System-Benutzer für die Verwendung von ssh angelegt werden.

Server-Konfiguration (hier: debian squeeze 6.0)

Voraussetzung: apache2 ist bereits installiert. HTTPS verfügbar, Zertifikate stehen bereit (hier: nicht self-signed)

apt-get install git-core

Datei erzeugen: /etc/apache2/sites-available/git.example.com-ssl

<VirtualHost *:443>
        ServerName git.example.com
        DocumentRoot /var/www/git.example.com/git
        ErrorLog /var/log/apache2/error-git.log
        CustomLog /var/log/apache2/access-git.log common
        <Location />
                AuthType Digest
                AuthName git
                AuthDigestDomain /
                AuthDigestProvider file
                AuthUserFile /var/www/git.example.com/password/users.digest
                Order allow,deny
                Deny from all
                Satisfy any
                Require valid-user
        </Location>
        SetEnv GIT_PROJECT_ROOT /var/www/git.example.com/git
        SetEnv GIT_HTTP_EXPORT_ALL
        # 64 bit only!
        ScriptAlias / /usr/lib64/git-core/git-http-backend/
        SSLEngine on
        SSLProtocol all
        SSLCipherSuite HIGH:MEDIUM
        SSLCertificateFile /etc/ssl/certs/example_com.crt
        SSLCertificateKeyFile /etc/ssl/private/example_com.key
        SSLCertificateChainFile /etc/ssl/certs/RapidSSL_Intermediate_CA.pem
        SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown downgrade-1.0 force-response-1.0
</VirtualHost>

a2ensite git.example.com-ssl

mkdir /var/www/git.example.com
cd /var/www/git.example.com

mkdir password
htdigest -c password/users.digest git newuser

mkdir git
cd git

Im Folgenden wird ein git Repository erzeugt. git Push ist ab Version 1.7 nur noch mit  repositories möglich, die mit --bare erzeugt wurden. Das erzeugte Repository ist insofern keine Arbeitskopie.

git init --bare

Schreibrechte für apache-Benutzer setzen:

cd ..
chown -R www-data git
service apache2 restart

Update debian squeeze auf wheezy

Der Skriptalias lautet jetzt:

ScriptAlias / /usr/lib/git-core/git-http-backend/

statt

ScriptAlias / /usr/lib64/git-core/git-http-backend/

Danach apache neu starten:

service apache2 restart

Für jedes repository ist Folgendes zu tun:

cd var/www/git.xyz.de/git/<repo-name>
git update-server-info
mv hooks/post-update.sample hooks/post-update
git config http.receivepack true
chown -R www-data *

Hierbei werden folgende Probleme gelöst:

Mit dem neuen ScriptAlias wird das git-http-backend wieder richtig eingebunden.

git update-server-info löst folgenden Fehler:

fatal: https://git.xyz.de/git/<repo>/info/refs not found: did you run git update-server-info on the server?

.git/hooks/post-update erledigt ein update-server-info automatisch bei Änderungen

http.receivepack true erlaubt pushes. Ansonsten kommt folgender Fehler:

error: Cannot access URL https://heinz@git.xyz.de/git/<repo>/, return code 22. fatal: git-http-push failed

Multible Projekte -> Multible repositories

git betrachtet jedoch immer das komplette Projekt, nie einzelne Dateien. Man kann daher nicht wie bei svn einzelne Unterverzeichnisse auschecken. Daher sollte man jedes Projekt in ein eigenes repository legen. Dieser Schritt ist nicht nötig, wenn man nur ein Projekt verwendet (wie im Beispiel oben) oder wenn man jedes Projekt in seinen eigenen apache vhost legt (auch wie im Beispiel)

Auf dem Server lassen sich die verschiedenen repositories in einem apache vhost z.B. so anlegen:

cd /var/www/git.example.com/git
mkdir projectA
cd projectA
git init --bare
cd ..
chown -R www-data projektA
mkdir projectB
cd projectB
git init --bare
cd ..
chown -R www-data projektA

usw. Beim Auschecken muss man dann das entsprechende Projekt angeben:

git clone https://git.example.com/projectA projectA

Client-Installation (hier: Ubuntu natty 11.04)

git installieren:

sudo apt-get install git

In $HOME/.netrc kann das Login festgelegt werden:

machine git.example.com
login newuser
password newuserpass

Rechte setzen:

chmod 600 ~/.netrc

Namen und E-Mail hinterlegen

git config --global user.name "Your Name Comes Here"
git config --global user.email you@yourdomain.example.com

vim als Standard-Editor festlegen

git config --global core.editor vim

Syntax Highlighting (farbige Formatierung)

git config --global color.branch auto
git config --global color.diff auto
git config --global color.interactive auto
git config --global color.status auto

master-Branch erstellen

Da wir Server-seitig keine Arbeitskopie haben, müssen wir den master-Branch Client-seitig erstellen. Dies wird gemacht, indem auf dem Client ein erster commit gemacht wird:

Lokale Arbeitskopie des repositories vom Server herunterladen:

git clone https://git.example.com tmpgit
cd tmpgit

Änderung machen und lokal committen:

touch readme
git add readme
git commit -a

Aktuelle Branches anzeigen:

git branch liefert die Ausgabe

* master

master branch mit auf den Server hochladen:

git push origin master
cd ..

Workflow

Mit diesem Workflow werden Änderungen für ein neues Feature zunächst in einem neuen lokalen Branch gemacht. Dort kann beliebig oft committed werden (lokal und remote), ohne den master-Branch anzufassen. Wenn das Feature fertig ist, wird in den master-Branch gemerged. Lokale Arbeitskopie herunterladen (clone) oder bestehende Arbeitskopie aktualisieren (pull):

git clone https://git.example.com gitdir
cd gitdir
git pull

Statt git pull kann man auch erstmal die Änderungen prüfen:

git fetch
git diff master origin/master
git merge origin/master

Neuen Branch namens new-feature erzeugen, wechseln in new-feature

git checkout --track -b new-feature

Nun können Änderungen am Quellcode erfolgen, wobei Dateien unter Versionskontrolle zu stellen sind. Beispiel:

touch readme
git add readme

In branch new-feature committen (nur lokal!), dann den branch new-feature auch ins remote repository stellen (Optional. git push funktioniert ab Version 1.7 nur mit einem bare repository (s.o))

git commit -a
git push origin new-feature

Andere Team-Mitglieder können jetzt diesen remote branch auschecken und können darauf mit git pull und git push arbeiten, ohne den master branch anfassen zu müssen.

Wechseln auf master-Branch (im selben Verzeichnis sieht man nun wieder den alten Stand vom master-Branch):

git checkout master
git merge new-feature

Jetzt sind alle Änderungen des Branches new-feature auch im Branch master und mit einem

git push

auch im master-Branch des Remote Repositories.

Remote Branch auschecken

git clone checkt immer den default Branch aus. Dieser lässt sich pro Repository festlegen und verweist meistens auf den master Branch bzw. auf den letzten Entwicklungsstand. Wenn man jedoch an einem anderen Branch arbeiten möchte, muss man diesen so auschecken:

default branch auschecken:

git clone https://example.com/ gittest
cd gittest

git branch zeigt hierbei nur den default Branch an (hier entsprechend den master Branch):

* master

Man kann jedoch auch alle Branches, inklusive der Remote Branches, anzeigen:

git branch -a

Remote Branch experimental auschecken:

git checkout --track -b experimental origin/experimental

Jetzt arbeitet man in der lokalen Arbeitskopie auf dem Branch experimental.

Branch löschen

Branch new-feature im lokalen Repository löschen:

git branch -d new-feature

Branch new-feature im Remote Repository löschen:

git push origin :new-feature

Weitere git Kommandos

Datei untracken (Datei wird danach nicht mehr committed. Datei wird trotz rm nicht gelöscht). Alternative: Von Anfang an .gitignore benutzen.

git rm --cached filename

Versionstag setzen:

git tag -a v0.1

Tags auflisten:

git tag -l

Tag löschen:

git tag -d v0.1

Letzte Commit-Kommentare verändern, Commits zusammenfassen

git rebase -i HEAD~5

Log formatieren (hier Datum und Commit-Text):

git log --date=short --pretty="tformat:%ad %s"

Benutzerverwaltung

git verfügt selbst über keine Benutzerverwaltung. In Verbindung mit smart-http kann man zunächst nur die Authentifizierungsmechanismen des Apache-Webservers benutzen. Hiermit lässt sich immerhin über eine Passwort-Datei definieren, wer Zugriff auf ein Repository bekommt.

Immerhin müssen in Verbindung mit der Apache-Authorisierung keine Systembenutzer angelegt werden, was z.B. bei der Verwendung von ssh nötig wäre.

Benötigt man eine feingranularere Benutzerverwaltung, bietet sich die Verwendung von gitolite an. Dieses funktioniert in neueren Versionen auch mit smart-http.