Auf der Open Source Autobahn (Linux-Magazin, August 2009)

Github erleichtert Open-Source-Beiträge, indem es die Kommunikation zwischen Projekt-Maintainern und willigen Mitwirkenden radikal vereinfacht und beschleunigt. Mega-Projekte wie Ruby on Rails profitieren bereits von explosionsartig hochschnellender Beitragswut.

Wer schon einmal versucht hat, einen Patch an ein Open-Source-Projekt zu schicken, kennt die Hürden, die auch den motiviertesten Entwickler oft entmutigen: Die Email-Adresse der Projekt-Maintainer oder der entsprechenden Mailing-Liste ist zu erfragen und eventuell im Weg stehende Moderationsschwellen sind zu zu überwinden. Findet dann endlich ein Verantwortlicher die Zeit, sich den Beitrag anzusehen, ist vielleicht die Formatierung falsch oder der Patch überschneidet sich mit anderen noch nicht publik gemachten Änderungen.

PITA-Faktor verringern

Github hat es sich zur erklärten Aufgabe gemacht, diesen PITA-(Pain in the Ass)-Faktor ([2]) zu verringern. Öffentlich zugängliche Code-Repositories mit dem branch- und merge-freundlichen Source-Control-System Git erlauben jedem Open-Source-Interessierten, nach Herzenslust Änderungen an einer Vielzahl der dort gehosteten Projekte vorzunehmen, lokal zu testen und im Erfolgsfall beinahe reibungslos ins Originalprojekt einzuspielen.

``Fork'' ist kein böses Wort

Ein ``Fork'' zu erstellen, also eine Kopie eines Open-Source-Projekts anzulegen und auf eigene Faust Änderungen vorzunehmen, ist auf github.com kein hinterlistiges Vorgehen, sondern geplanter Alltag. Forks dienen dort nicht der feindlichen Projektübernahme, sondern erlauben es interessierten Programmbastlern, neue Features zu entwickeln, zu testen und schließlich die Projektmaintainer um Übernahme in den Hauptzweig des Projekts zu bitten.

Github hostet öffentlich zugängliche Open-Source-Projekte kostenlos und bietet 300MB Plattenplatz pro Entwickler. Möchte jemand den Service für nicht-öffentliche Projekte nutzen, bietet Github eine Reihe von kostenpflichtigen Plänen an, die der erfolgreichen etwa fünf Mann hohen Garagenfirma ein sicheres Einkommen ermöglichen.

Von CVS nach git

Listing cvs2github hilft, CVS repositories in git-Repos umzuwandeln und sie für den Release auf github.com vorzubereiten. Git selbst bringt, zumindest mit dem Zusatzpaket git-cvs, eine Import-Funktion mit. Alles was cvs2github zu tun hat, ist, das CVS-Repository mittels rsync auf den lokalen Rechner herunter zu laden, das Verzeichnis CVSROOT mit hinzuzuladen und dann git-cvsimport aufzurufen.

Hierzu erzeugt das Skript in Zeile 9 ein temporäres Verzeichnis, in das rsync die Server-Repo-Daten lokal ablegt. Die Kenndaten des CVS-Servers liegen in Zeile 17. Das Verzeichnis, in dem das Git-Repo schließlich landet, legt Zeile 13 in der Variablen $git_dir fest. Da Entwickler im CVS-Repo ihre Einträge unter ihren Unix-IDs vornehmen, auf Github aber üblicherweise unter einer anderen Identität arbeiten, definieren die Zeilen 21 bis 23 in einer Autorenkonversionsdatei eine Zuordnung zwischen alten Unix-Usernamen und neuen Github-IDs mit Emailaddresen.

Alte und neue Entwicklernamen

Im vorliegenden Fall werden drei verschiedene IDs (mschilli, perlmeis, mikeschilli) alle in die neue Github-ID mschilli überführt. Haben hingegen mehrere Entwickler an einem Projekt gearbeitet, sind deren IDs samt und sonders in neue Github-IDs zu überführen. Die Funktion blurt aus dem CPAN-Modul Sysadm::Install legt die Zeilen in einer temporären Datei ab, die git-cvsimport mit der Option -A entgegennimmt, um das Mapping durchzuführen.

Da cvs2github für Perl-Module geschrieben wurde, wandelt Zeile 29 einen Projektnamen wie Log-Log4perl in Kleinbuchstaben um und setzt ein -perl dahinter, so dass daraus etwa log-log4perl-perl wird, was genau dem Debian-Namensschema entspricht und den Namensraum auf Github sauber hält.

Zeile 39 ruft erst für das CVS-Repo und dann ein weiteres Mal für das Metadaten-Verzeichnis CVSROOT auf dem Server ein rsync-Kommando auf, das die Serverdaten auf die lokale Festplatte kopiert, denn git-cvsroot verlangt auch nach CVSROOT. Die Environment-Variable RSYNC_RSH wird auf den ssh-Client gesetzt, denn im vorliegenden Fall hat der Client Zugriff zum Server-seitigen Repository über einen ssh-Zugang. Für Sourceforge-Projekte gibt es ein ähnliches Verfahren ([3]).

Nach dem in Zeile 46 erfolgreich durchgeführten Import des CVS-Repos in ein Git-Repo liegt letzteres unter dem in Zeile 13 festgelegten Verzeichnis (hier $HOME/DEV/log-log4perl-perl). Das in Zeile 49 aufgerufene Kommando git remote add lässt den remote-Branch origin auf das noch nicht angelegte Github-Projekt zeigen. Hierüber wird git später die lokale Kopie und die Version auf dem Github-Server mit push und pull syncronisieren.

Listing 1: cvs2github

    01 #!/usr/bin/perl -w
    02 use strict;
    03 use Getopt::Std;
    04 use Pod::Usage;
    05 use File::Temp qw(tempdir tempfile);
    06 use Sysadm::Install qw(:all);
    07 
    08 my($proj) = @ARGV;
    09 my($temp_dir) = tempdir(CLEANUP => 1);
    10 my($fh, $author_conv_file) = 
    11                      tempfile(UNLINK => 1);
    12 my($home) = glob "~";
    13 my $git_dir  = "$home/DEV";
    14 
    15 my $email   = 'githubemail@mydomain.com';
    16 my $cvs_loc = 
    17     'mikeschilli@some.cvs.server:cvs';
    18 my $github_loc = 'git@github.com:mschilli';
    19 
    20 blurt(<<EOT, $author_conv_file);
    21 mschilli=mschilli <$email>
    22 perlmeis=mschilli <$email>
    23 mikeschilli=mschilli <$email>
    24 EOT
    25 
    26 pod2usage("No project given") unless 
    27     defined $proj;
    28 
    29 my $git_proj_name = lc($proj) . "-perl";
    30 my $git_path = "$git_dir/$git_proj_name";
    31 
    32 if(-e $git_path) {
    33     die "Path $git_path already exists";
    34 }
    35 
    36 mkd $git_path;
    37 
    38 for my $cvs_dir ($proj, "CVSROOT") {
    39   sysrun(
    40   "RSYNC_RSH=/usr/bin/ssh rsync -avz " . 
    41   "$cvs_loc:cvs/$cvs_dir $temp_dir/");
    42 }
    43 
    44 cd $git_path;
    45 
    46 sysrun("git-cvsimport -A " .
    47   "$author_conv_file -d $temp_dir $proj");
    48 
    49 sysrun("git remote add origin " .
    50        "$github_loc/$git_proj_name.git");
    51 
    52 print "Done: $git_proj_name\n";
    53 
    54 __END__
    55 
    56 =head1 NAME
    57 
    58     cvs2github - Convert cvs projects to git
    59 
    60 =head1 SYNOPSIS
    61 
    62     cvs2github My-Project-Name
    63 
    64 =head1 DESCRIPTION
    65 
    66 cvs2github takes a project checked into cvs 
    67 and converts it into a git repo ready for
    68 github.
    69 
    70 =head1 EXAMPLES
    71 
    72   $ cvs2github JavaScript-SpiderMonkey

CVS ist tot

Nun hat das CVS-Repo ausgedient, und um zu verhindern, dass vergessliche oder unwissende Entwickler doch noch Check-ins vornehmen, wird dort am besten eine gut sichtbare Datei wie MOVED_TO_GITHUB eingescheckt, die Schlafmützen wachrüttelt, bevor sie aktuelle Änderungen in ein totes Repo einspeisen.

Nun ist es an der Zeit, das Projekt auf Github anzulegen. Hat der Entwickler einen neuen Account mit einem Usernamen angelegt (mschilli in diesem Fall), genügen ein Klick auf ``Your Repositories (create a new one)'' und drei ausgefüllte Textzeilen, wie in Abbildung 1 ersichtlich.

Abbildung 1: Ein neues Github-Project ist schnell angelegt.

Abbildung 2: Der Public Key des Entwicklers dient auf github der Identifikation.

Als nächstes benötigt Github die Public Keys aller am Projekt Mitwirkenden mit Schreibberechtigung am Repo. Während Public Keys mit einer Secure Shell (ssh) typischerweise das Passworttippen reduzieren, nutzt Github den Public Key tatsächlich zur Identifizierung eines Entwicklers. Der Schreibzugang auf das Repo erfolgt später über git@github.com, ohne Angabe des Nutzernamens. Passwortgestützte Identifizierung ist also über den ssh-Zugang gar nicht möglich.

Der erste Push

Wurde der Public Key auf github.com hinterlegt, synchronisiert der Befehl git push origin master im lokalen aus CVS konvertierten git-Repo die lokale Version mit dem bislang leeren Repo auf github.com. Danach zeigt die Webpage auf http://github.com/user/projekt das Projekt mit seiner gesamten Historie und der Möglichkeit zum Mitmachen wie in Abbildung 3 gezeigt an. Es ist vollbracht, das Projekt ist live!

Abbildung 3: Das aus CVS konvertierte Git-Repository ist auf Github angekommen.

Ein Fork in Ehren ...

Stößt nun ein anderer Github User, nennen wir ihn ``open-source-dude'', auf das Projekt, findet einen Bug oder möchte eine Verbesserung anbringen, legt er schnurstracks einen Fork an, einfach, indem er auf den ``Fork''-Button des Projektes (Pfeil in Abbildung 4) drückt. Dies legt auf github.com eine Kopie des ursprünglichen Repos an und gewährt open-source-dude Schreibzugang auf diese Kopie. Auch er muss dazu einen Public Key auf Github hinterlegen. Abbildung 5 zeigt das nun open-source-dude gehörende Projekt.

Abbildung 4: Fork ist auf Github kein böses Wort.

Abbildung 5: ... und schon wurde ein Projekt geforkt.

Um nun Änderungen anzubringen, fertigt open-source-dude mittels des Git-Kommandos clone einen lokalen Klon des Forks an, wie in Abbildung 6 dargestellt. Wie in git üblich, und undenkbar in Subversion oder CVS, enthält der lokale Workspace nicht nur die letzte Version des Projektes sondern sämtliche Versionen, angefangen vom ersten Check-in. Das Kommando git log mit dem Parameter HEAD~3.. zeigt die Meldungen der letzten drei Check-ins an.

Abbildung 6: Ein lokaler Klon des geforkten Projekts wird angelegt.

Schlaue Syncronisation

Als Beispiel für eine Änderung im geforkten Projekt wird nun eine Zeile in der Datei Changes hinzugefügt, die einen imaginären Bugfix unter dem Kürzel von open-source-dude ankündigt. Ein git diff zeigt den Unterschied zwischen lokalem Workspace und dem lokalen Repo in Abbildung 7. Der nachfolgende commit mit dem Kommentar 'Imaginary Changes' nagelt die Änderung im lokalen Repo fest. Um den Commit auf den öffentlich sichtbaren Fork auf Github hochzuspielen, dient der Befehl git push origin master, der den Hauptzweig master des lokalen Repo zum kurz origin genannten Git-Repo hochspielt. Git ist bei der Syncronisation sehr effizient und tauscht kaum Daten aus, falls sich nichts oder nur wenig geändert hat. Während CVS bei großen Repositories geradezu ewig braucht, um festzustellen, dass lokaler Workspace und Server-Repo sich in Einklang befinden, kehrt git in solchen Situtationen praktisch in Sekundenbruchteilen zurück.

Abbildung 7: Der Kontributor open-source-dude bringt eine Änderung im lokalen Repo an und spielt sie in das geforkte Repository auf Github.

Abbildung 8: Der Network-Graph zeigt, dass der Kontributor open-source-dude eine Änderung weiter ist als das Hauptprojekt.

Der Entwickler hat also eine Änderung im lokalen Repo angebracht und sie in das geforkte Repository auf Github eingespielt. Klickt man nun auf den ``Network''-Button auf der Projektseite, generiert Github eine etwas holperige Flash-Grafik nach Abbildung 8, aus der ersichtlich ist, dass der Besitzer des Forks das Projekt um ein Feature vorangetrieben hat, das das Originalprojekt noch nicht übernommen hat.

In den allermeisten Fällen führt ein Fork auf Github zu einer Integration der neuen Funktionalität ins Original, niemand hat letztlich die Absicht eine Mauer zu errichten oder getrennte Wege zu gehen. Forks sind letztendlich nur temporäre Mittel zu dem Zweck, die Entwicklung voranzutreiben und beim Erreichen eines stabilen Zustands um Übernahme ins Original zu bitten. Dieses Verfahren erleichtert Github nun ganz enorm, denn der voranpreschende Entwickler des Forks muss lediglich den ``Pull Request'' Button seiner Fork-Projektseite drücken und in die aufpoppende Dialogbox einige erklärende Sätze an den Projekt-Maintainer tippen.

Abbildung 9: Der Maintainer wird mit einem Pull-Request aufgefordert, den Patch im Fork ins Hauptprojekt zu übernehmen.

Bitte, nimm mich an

Der Projekt-Maintainer erhält daraufhin sowohl auf seiner Projektseite auf Github als auch in seiner Email-Inbox die Nachricht zugespielt (Abbildungen 10/11). Ein derzeitiger Schwachpunkt im Arbeitsablauf ist, dass die Email aus ``no-reply''-Land zu kommen scheint und der Maintainer nicht per Email antworten kann, sondern dazu mit einem Browser auf die Github-Seite muss.

Abbildung 10: Der Projekt-Maintainer erhält eine Nachricht und kann die Änderung übernehmen oder auch nicht.

Abbildung 11: Auch per Email kommt die Aufforderung an, den Patch anzunehmen.

Findet der Maintainer Gefallen an dem vorgeschlagenen neuen Feature, zieht er ihn in sein lokales Repo. Nach dem Motto ``Trau, schau, wem'' allerdings zunächst auf einen dafür extra eingerichteten Branch. Wer seine Source-Control-Erfahrungen mit Subversion oder CVS gemacht hat, wird nun wahrscheinlich aufstöhnen, denn das Anlegen von Nebenzweigen und deren Rückführung in den Hauptzweig (Mergen) ist auf diesen Plattformen ein alptraumähnliches Unterfangen. In Git hingegen gehört es zum Alltag und sowohl das Anlegen eines Branches als auch der Mergevorgang sind in Sekundenbruchteilen erledigt. Mit ein paar klugen Ideen und geschickter Datenhaltung hat Git es tatsächlich geschafft, dieses seit der Steinzeit der Programmentwicklung herumlungernde Problem elegant zu lösen. Es ist durchaus nicht ungewöhnlich, dass Entwickler für jeden zu fixenden Bug einen neuen Branch eröffnen und gleichzeitig dutzende davon offen halten.

Der Meister integriert

Abbildung 12 illustriert die Befehlsabfolge, die der Maintainer auf der Kommandozeile eintippt, um das neue Feature auf einen neuen Projektbranch zu ziehen. Es wird vorausgesetzt, dass der Maintainer bereits einen lokalen Klon seines Original-Repos auf Github besitzt und in dieses hineingefahren ist. Zunächst legt er mit remote add einen neuen Alias namens open-source-dude-repo für das Repo des vorpreschenden Kontributors an. Der Befehl checkout mit der Option -b legt anschließend einen neuen Branch open-source-dude-branch im lokalen Repo an und springt in diesen hinein.

Abbildung 12: Sieht der Patch interessant aus, zieht der Maintainer ihn auf einen neuen Branch des Projektes.

Der folgende pull-Befehl mit dem vorher angelegten Alias für den Fork und dem Parameter master:open-source-dude-branch zieht den Master-Zweig des Forks auf Github auf den Branch open-source-dude-branch des lokalen Repos. Ein anschließend vom neuen Branch aus abgesetztes diff-Kommando mit dem Parameter master zeigt die Unterschiede im aktuellen Branch gegenüber dem master-Branch, also dem Hauptzweig des lokalen Repos, an.

Abbildung 13: Übernahme des Patches in den lokalen Workspace des master-Branches, und anschließender Commit.

Akzeptiert der Maintainer den Patch, springt er dazu wieder mit checkout master in den Hauptzweig und ruft git merge mit der Option --squash und dem zu integrierenden Branch als Parameter auf. Während merge normalerweise die Änderungen des Forks allesamt wörtlich übernimmt und unter Umständen dutzende von Einträgen im Log des Originalprojekts erzeugt, presst --squash diese auf einen Commit zusammen, und checkt das Ergebnis noch nicht ins lokale Repo ein, sondern stellt es in Gits ``Staging Area'', dem Bereich, der beim nächsten Aufruf von git commit ins Repo wandert. So kann der Projekt-Maintainer den Patch mit einer einzigen Log-Nachricht seiner Wahl ins Projekt einfließen lassen.

Diese revolutionäre Art, Beiträge zu Open-Source-Projekten zu leisten, ist auch schon aufs CPAN vorgedrungen, wo Woche für Woche hunderte von neuen Modulversionen veröffentlicht werden. Damit ein CPAN-Modul auf search.cpan.org wie in Abbildung 14 einen Link auf das verwendete Github-Repo anzeigt, ist lediglich ein META-Eintrag nach Abbildung 15 im zentralen Makefile.PL erforderlich, die CPAN-Software erledigt den Rest automatisch.

Abbildung 14: Mit dem richtigen META-Eintrag zeigt search.cpan.org auch das Source-Repository des Modules an.

Abbildung 15: Dieser Eintrag im Makefile.PL des CPAN-Moduls erzeugt den Link zum Git-Repo auf search.cpan.org.

Vorsicht, Falle!

Zu bedenken ist, dass Github die Projektarbeit plötzlich radikal veröffentlicht: Wer also die Angewohnheit hat, auf Mailinglisten herumzupöbeln oder anstößige Kommentare einzuchecken, sollte dies schleunigst abstellen, denn auf Github steht alles plötzlich groß und breit im Internet als Futter für die Suchmaschinen. Dies gilt auch für konvertierte CVS-Repos: Was bislang vielleicht nur wenigen Entwicklern zugänglich war, ist nun selbst für Oma und Opa Meume gut auf normalen HTML-Seiten sichtbar. Vor der endgültigen Veröffentlichung sollte man schon ein kritisches Auge auf den Inhalt werfen.

[1]

Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2009/08/Perl

[2]

Dan Dascalescu, ``The PITA Threshold: GitHub vs. CPAN'', http://wiki.dandascalescu.com/essays/pita-threshold

[3]

``Turn your Sourceforge Project into a Git Repo'', http://blog.usarundbrief.com/?p=12

Michael Schilli

arbeitet als Software-Engineer bei Yahoo! in Sunnyvale, Kalifornien. Er hat "Goto Perl 5" (deutsch) und "Perl Power" (englisch) für Addison-Wesley geschrieben und ist unter mschilli@perlmeister.com zu erreichen. Seine Homepage: http://perlmeister.com.