Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
276 lines (204 sloc) 11.5 KB
== Geschichtsstunde ==
Eine Folge von Git's verteilter Natur ist, dass die Chronik einfach
verändert werden kann. Aber, wenn Du an der Vergangenheit manipulierst, sei
vorsichtig: verändere nur den Teil der Chronik, den Du ganz alleine hast. So
wie Nationen ewig diskutieren, wer welche Greueltaten vollbracht hat, wirst
Du beim Abgleichen in Schwierigkeiten geraten, falls jemand einen 'Clone'
mit abweichender Chronik hat und die Zweige sich austauschen sollen.
Einige Entwickler setzen sich nachhaltig für die Unantastbarkeit der Chronik
ein, mit allen Fehlern, Nachteilen und Mängeln. Andere denken, dass Zweige
vorzeigbar gemacht werden sollten, bevor sie auf die Öffentlichkeit
losgelassen werden. Git versteht beide Gesichtspunkte. Wie 'Clonen',
'Branchen' und 'Mergen' ist das Umschreiben der Chronik lediglich eine
weitere Stärke, die Git Dir bietet. Es liegt an Dir, diese Weise zu nutzen.
=== Ich nehme alles zurück ===
Hast Du gerade 'commitet', aber Du hättest gerne eine andere Beschreibung
eingegeben? Dann gib ein:
$ git commit --amend
um die letzte Beschreibung zu ändern. Du merkst, dass Du vergessen hast, eine
Datei hinzuzufügen? Führe *git add* aus, um sie hinzuzufügen, und dann die
vorhergehende Anweisung.
Du willst noch ein paar Änderungen zu deinem letzten 'Commit' hinzufügen?
Dann mache diese Änderungen und gib ein:
$ git commit --amend -a
=== ... und noch viel mehr ===
Nehmen wir jetzt an, das vorherige Problem ist zehnmal schlimmer. Nach einer
längeren Sitzung hast Du einen Haufen 'Commits' gemacht. Aber Du bist mit
der Art der Organisation nicht glücklich, und einige 'Commits' könnten etwas
umformuliert werden. Dann gib ein:
$ git rebase -i HEAD~10
und die letzten zehn 'Commits' erscheinen in Deinem bevorzugten
$EDITOR. Auszug aus einem Beispiel:
pick 5c6eb73 Link repo.or.cz hinzugefügt
pick a311a64 Analogien in "Arbeite wie Du willst" umorganisiert
pick 100834f Push-Ziel zum Makefile hinzugefügt
Dann:
- Entferne 'Commits' durch das Löschen von Zeilen.
- Organisiere 'Commits' durch verschieben von Zeilen.
- Ersetze `pick` mit:
* `edit`, um einen 'Commit' für 'amends' zu markieren.
* `reword`, um die Log-Beschreibung zu ändern.
* `squash`, um einen 'Commit' mit dem vorhergehenden zu vereinen ('merge').
* `fixup`, um einen 'Commit' mit dem vorhergehenden zu vereinen ('merge') und die Log-Beschreibung zu verwerfen.
Speichere und Beende. Wenn Du einen 'Commit' mit 'edit' markiert hast, gib
ein:
$ git commit --amend
Ansonsten:
$ git rebase --continue
Also 'commite' früh und oft: Du kannst später mit 'rebase' aufräumen.
=== Lokale Änderungen zum Schluss ===
Du arbeitest an einem aktiven Projekt. Über die Zeit haben sich einige
lokale 'Commits' angesammelt und dann synchronisierst Du mit einem 'Merge'
mit dem offiziellen Zweig. Dieser Zyklus wiederholt sich ein paar Mal, bevor
Du zum 'Pushen' in den zentralen Zweig bereit bist.
Aber nun ist die Chronik in deinem lokalen Git-'Clone' ein chaotisches
Durcheinander deiner Änderungen und den Änderungen vom offiziellen Zweig. Du
willst alle Deine Änderungen lieber in einem fortlaufenden Abschnitt und
hinter den offiziellen Änderungen sehen.
Das ist eine Aufgabe für *git rebase*, wie oben beschrieben. In vielen
Fällen kannst Du den *--onto* Schalter benutzen, um Interaktion zu vermeiden.
Siehe auch *git help rebase* für ausführliche Beispiele dieser erstaunlichen
Anweisung. Du kannst auch 'Commits' aufteilen. Du kannst sogar 'Branches' in
einem 'Repository' umorganisieren.
=== Chronik umschreiben ===
Gelegentlich brauchst Du Versionsverwaltung vergleichbar dem Wegretuschieren
von Personen aus einem offiziellen Foto, um diese in stalinistischer Art aus
der Geschichte zu löschen. Stell Dir zum Beispiel vor, Du willst ein Projekt
veröffentlichen, aber es enthält eine Datei, die aus irgendwelchen Gründen
privat bleiben muss. Vielleicht habe ich meine Kreditkartennummer in einer
Textdatei notiert und diese versehentlich dem Projekt hinzugefügt. Die Datei
zu löschen, ist zwecklos, da über ältere 'Commits' auf sie zugegriffen werden
könnte. Wir müssen die Datei aus allen 'Commits' entfernen:
$ git filter-branch --tree-filter 'rm sehr/geheime/Datei' HEAD
Siehe *git help filter-branch*, wo dieses Beispiel erklärt und eine
schnellere Methode vorstellt wird. Allgemein, *filter-branch* lässt Dich
große Bereiche der Chronik mit einer einzigen Anweisung verändern.
Danach beschreibt der Ordner +.git/refs/original+ den Zustand der Lage vor
der Operation. Prüfe, ob die 'filter-branch' Anweisung getan hat, was Du
wolltest, dann lösche dieses Verzeichnis, bevor Du weitere 'filter-branch'
Operationen durchführst.
Zuletzt, ersetze alle 'Clones' Deines Projekts mit Deiner überarbeiteten
Version, falls Du später mit ihnen interagieren möchtest.
=== Geschichte machen ===
[[makinghistory]] Du möchtest ein Projekt zu Git umziehen? Wenn es mit einem
der bekannteren Systeme verwaltet wird, besteht die Möglichkeit, dass schon
jemand ein Skript geschrieben hat, das die gesamte Chronik für Git
exportiert.
Anderenfalls, sieh dir *git fast-import* an, das Text in einem speziellen
Format einliest, um eine Git Chronik von Anfang an zu
erstellen. Normalerweise wird ein Skript, das diese Anweisung benutzt,
hastig zusammengeschustert und einmalig ausgeführt, um das Projekt in einem
einzigen Lauf zu migrieren.
Erstelle zum Beispiel aus folgendem Listing eine temporäre Datei, z.B. `/tmp/history`:
----------------------------------
commit refs/heads/master committer Alice <alice@example.com> Thu, 01 Jan
1970 00:00:00 +0000 data <<EOT Initial commit. EOT
M 100644 inline hello.c data <<EOT #include <stdio.h>
int main() {
printf("Hallo, Welt!\n");
return 0;
}
EOT
commit refs/heads/master committer Bob <bob@example.com> Tue, 14 Mar 2000
01:59:26 -0800 data <<EOT Ersetze printf() mit write(). EOT
M 100644 inline hello.c data <<EOT #include <unistd.h>
int main() {
write(1, "Hallo, Welt!\n", 14);
return 0;
}
EOT
----------------------------------
Dann, erstelle ein Git 'Repository' aus dieser temporären Datei, durch
Eingabe von:
$ mkdir project; cd project; git init
$ git fast-import --date-format=rfc2822 < /tmp/history
Die aktuellste Version des Projekts kannst Du abrufen ('checkout') mit:
$ git checkout master .
Die Anweisung *git fast-export* konvertiert jedes 'Repository' in das *git
fast-import* Format, dessen Ausgabe Du studieren kannst, um Exporte zu
schreiben und außerdem, um 'Repositories' im menschenlesbaren Text zu übertragen.
Tatsächlich können diese Anweisungen Klartext-'Repositories' über reine
Textkanäle übertragen.
=== Wo ging alles schief? ===
Du hast gerade eine Funktion in Deiner Anwendung entdeckt, die nicht mehr
funktioniert und Du weißt sicher, dass sie vor ein paar Monaten noch
ging. Argh! Wo kommt dieser Fehler her? Hättest Du nur die Funktion während
der Entwicklung getestet.
Dafür ist es nun zu spät. Wie auch immer, vorausgesetzt Du hast oft
'comittet', kann Git Dir sagen, wo das Problem liegt:
$ git bisect start
$ git bisect bad HEAD
$ git bisect good 1b6d
Git ruft einen Stand ab, der genau dazwischen liegt. Teste die Funktion und
wenn sie immer noch nicht funktioniert:
$ git bisect bad
Wenn nicht, ersetzte "bad" mit "good". Git versetzt Dich wieder auf einen
Stand genau zwischen den bekannten Versionen "good" und "bad" und reduziert
so die Möglichkeiten. Nach ein paar Durchläufen wird Dich diese binäre Suche
zu dem 'Commit' führen, der die Probleme verursacht. Wenn Du deine
Ermittlungen abgeschlossen hast, kehre zum Originalstand zurück mit:
$ git bisect reset
Anstatt jede Änderung per Hand zu untersuchen, automatisiere die Suche durch
Ausführen von:
$ git bisect run mein_skript
Git benutzt den Rückgabewert der übergebenen Anweisung, normalerweise ein
Skript für einmalige Ausführung, um zu entscheiden, ob eine Änderung gut
('good') oder schlecht ('bad') ist: Das Skript sollte 0 für 'good'
zurückgeben, 125 wenn die Änderung übersprungen werden soll und irgendetwas
zwischen 1 und 127 für 'bad'. Ein negativer Rückgabewert beendet die
'bisect'-Operation sofort.
Du kannst noch viel mehr machen: die Hilfe erklärt, wie man
'bisect'-Operationen visualisiert, das 'bisect'-Log untersucht oder
wiedergibt und sicher unschuldige Änderungen ausschließt, um die Suche zu
beschleunigen.
=== Wer ist verantwortlich? ===
Wie viele andere Versionsverwaltungssysteme hat Git eine 'blame' Anweisung:
$ git blame bug.c
das jede Zeile in der angegebenen Datei kommentiert, um anzuzeigen, wer sie
zuletzt geändert hat und wann. Im Gegensatz zu vielen anderen
Versionsverwaltungssystemen funktioniert diese Operation offline, es wird
nur von der lokalen Festplatte gelesen.
=== Persönliche Erfahrungen ===
In einem zentralisierten Versionsverwaltungssystem ist das Bearbeiten der
Chronik eine schwierige Angelegenheit und den Administratoren
vorbehalten. 'Clonen', 'Branchen' und 'Mergen' sind unmöglich ohne
Netzwerkverbindung. Ebenso grundlegende Funktionen wie das Durchsuchen der
Chronik oder das 'comitten' einer Änderung. In manchen Systemen benötigt der
Anwender schon eine Netzwerkverbindung, nur um seine eigenen Änderungen zu
sehen oder um eine Datei zum Bearbeiten zu öffnen.
Zentralisierte Systeme schließen es aus, offline zu arbeiten und benötigen
teurere Netzwerkinfrastruktur, vor allem, wenn die Zahl der Entwickler
steigt. Am wichtigsten ist, dass alle Operationen bis zu einem gewissen Grad
langsamer sind, in der Regel bis zu dem Punkt, wo Anwender erweiterte
Anweisungen scheuen, bis sie absolut notwendig sind. In extremen Fällen
trifft das auch auf die grundlegenden Anweisungen zu. Wenn Anwender langsame
Anweisungen ausführen müssen, sinkt die Produktivität, da der Arbeitsfluss
unterbrochen wird.
Ich habe diese Phänomen aus erster Hand erfahren. Git war das erste
Versionsverwaltungssystem, das ich benutzt habe. Ich bin schnell in die
Anwendung hineingewachsen und betrachtete viele Funktionen als
selbstverständlich. Ich habe einfach vorausgesetzt, dass andere Systeme
ähnlich sind: die Auswahl eines Versionsverwaltungssystems sollte nicht
anders sein als die Auswahl eines Texteditors oder Internetbrowser.
Ich war geschockt, als ich später gezwungen war, ein zentralisiertes System
zu benutzen. Eine unzuverlässige Internetverbindung stört mit Git nicht
sehr, aber sie macht die Entwicklung unerträglich, wenn sie so zuverlässig
wie ein lokale Festplatte sein sollte. Zusätzlich habe ich mich dabei
ertappt, bestimmte Anweisungen zu vermeiden, um die damit verbundenen
Wartezeiten zu vermeiden, und das hat mich letztendlich davon abgehalten,
meinem gewohnten Arbeitsablauf zu folgen.
Wenn ich eine langsame Anweisung auszuführen hatte, wurde durch die
Unterbrechung meiner Gedankengänge dem Arbeitsfluss ein unverhältnismäßiger
Schaden zugefügt. Während des Wartens auf das Ende der Serverkommunikation
tat ich etwas anderes, um die Wartezeit zu überbrücken, zum Beispiel E-Mails
lesen oder Dokumentation schreiben. Wenn ich zur ursprünglichen Arbeit
zurückkehrte, war die Operation längst beendet und ich vergeudete noch mehr
Zeit beim Versuch, mich zu erinnern, was ich getan habe. Menschen sind nicht
gut im Kontextwechsel.
Da war auch ein interessanter
http://de.wikipedia.org/wiki/Tragik_der_Allmende[Tragik-der-Allmende]
Effekt: Netzwerküberlastungen erahnend, verbrauchten einzelne Individuen für
diverse Operationen mehr Netzwerkbandbreite als erforderlich, um zukünftige
Engpässe zu vermeiden. Die Summe der Bemühungen verschlimmerte die
Überlastungen, was einzelne wiederum ermutigte, noch mehr Bandbreite zu
verbrauchen, um noch längere Wartezeiten zu verhindern.