Verteilte Versionskontrolle (DVCS) mit Mercurial
Peter Arrenbrecht, August 2008
http://arrenbrecht.ch/
Einleitung
Verteilte Versionskontrolle (DVCS) ist trendy. Immer mehr Open-Source-Projekte stellen darauf um, und es hält auch in Firmen Einzug. Die Grundideen sind einfach: jeder hat immer die relevante Versionsgeschichte dabei, jeder arbeitet immer in einem ad-hoc Branch, und Merging ist darum eine ganz einfache und natürliche Sache. Welche Vorteile bringt das? Welche Probleme bereitet es? Was gibt es für Lösungen? Wir schauen uns das konkret an am Beispiel von Mercurial. Dieses ist mit Git und Bazaar eines der verbreitetsten DVCS und wird z.B. von Mozilla, OpenJDK und Netbeans eingesetzt.
Kursziel
- Sie wissen, was DVCS ist, was es von Subversion unterscheidet, und welche Vorteile und Probleme es hat.
- Sie können beurteilen, ob DVCS grundsätzlich in Ihr Projekt und Ihr Team passt.
- Sie kennen die Stärken und Schwächen von Mercurial.
Adressaten
Alle, die mit server-orientierter Versionskontrolle à la Subversion, CVS, Perforce etc. bereits Erfahrungen gemacht haben (oder die zumindest wissen, warum Versionskontrolle sinnvoll ist und sich jetzt endlich darum kümmern wollen). Wer bereits mit DVCS wie Git, Bazaar oder Darcs gearbeitet hat, wird sehen, wie sich Mercurial im Vergleich dazu anfühlt, aber bei den Grundlagen kaum Neues lernen.
Voraussetzungen
- Grundkenntnisse der Versionskontrolle
- Erfahrung mit z.B. Subversion, CVS oder Perforce von Vorteil
- Kenntnisse über Begriffe wie branching, merging, merge conflict, diff/patch von Vorteil
Wir versionieren ein kleines Projekt
Zunächst starten wir das Projekt. Typischerweise denken wir noch nicht an Versionskontrolle in diesem Moment, sondern legen einfach los:
$ mkdir hgdemo
$ cd hgdemo
$ cat >README <<-eof
= hgdemo =
Dies ist ein kleines Demo-Projekt zum Thema DVCS mit Mercurial.
Es darf frei kopiert werden.
eof
$ cat >hgdemo.py <<eof
print "The numbers from 1 to 10"
for i in range(1,11): print i,
print
eof
$ python hgdemo.py
The numbers from 1 to 10
1 2 3 4 5 6 7 8 9 10
Versionieren
hg init
Nun wollen wir aber sauber versionieren, denn es steht das erste Refactoring an: die Lizenz soll ausgelagert und besser definiert werden. Dazu wandeln wir einfach das aktuelle Verzeichnis um in eine Arbeitskopie (working copy) eines neuen Mercurial-Archivs (repository):
$ hg init
Das neue Archiv landet automatisch im Unterverzeichnis .hg/
(netterweise haben wir aber nur immer ein .hg/
Verzeichnis pro Arbeitskopie, nicht die verstreuten .svn/
wie bei Subversion):
$ ls -Ap
.hg/
hgdemo.py
README
Hier sehen wir bereits ein fundamentales Prinzip von DVCS in Aktion: jede Arbeitskopie hat immer ihr Archiv gleich mit dabei. Daher das Wort distributed in DVCS. Dafür stellt DVCS mächtige Mittel bereit, solche Archive to kopieren und zu synchronisieren. Mehr dazu ein bisschen später.
Im Moment ist das Archiv noch leer. Wie andere VCS protokolliert Mercurial erst, wenn wir es explizit dazu anweisen. Versuchen wir das sofort, so erhalten wir:
$ hg commit --message "erster Import"
No username found, using 'hans@muehle' instead
nothing changed
~/.hgrc
Oops. Mercurial warnt uns, dass wir ihm noch nicht gesagt haben, unter welchem Namen es unsere Änderungen im Archiv protokollieren soll. Das holen wir gleich nach, denn sonst nimmt es einfach den aktuellen User- und Maschinennamen. Alle persönlichen Einstellungen für Mercurial schreiben wir nach ~/.hgrc
:
$ echo "[ui]" >>~/.hgrc
$ echo "username = Hans Müller <hans.mueller@example.com>" >>~/.hgrc
Versuchen wir es erneut, so kommt immer noch nothing changed:
$ hg commit --message "erster Import"
nothing changed
hg status
Warum? Listen wir den Zustand unserer Arbeitskopie auf, so sehen wir, dass Mercurial unsere Dateien noch nicht unter seine Fittiche genommen hat. ?
bedeutet unbekannt (unknown):
$ hg status
? README
? hgdemo.py
hg add
Mercurial nimmt - wie viele andere VCS auch - Dateien nicht automatisch auf. Stellen wir die Dateien also explizit unter Versionskontrolle:
$ hg add
adding README
adding hgdemo.py
Ohne weitere Argumente nimmt @hg add@ rekursiv alle unbekannten Dateien auf. Wir sehen später, wie wir Build-Produkte filtern können. Zur Kontrolle listen wir alle Dateien mit anstehenden Änderungen nochmals auf (A
steht für add):
$ hg status
A README
A hgdemo.py
hg commit
Sieht gut aus, also protokollieren wir die Änderungen jetzt (neudeutsch committen). Dies erzeugt im Archiv ein sogenanntes changeset (manchmal auch einfach change, oder revision; geschrieben auch cset oder rev). Das ist eine Sammlung von zusammengehörigen Änderungen an einer Reihe von Dateien. Wie bei allen Versionskontroll-Systemen dokumentieren wir jedes Changeset mit einem Kommentar:
$ hg commit --message "erster Import"
Die Arbeitskopie hat nun keine anstehenden Änderungen mehr:
$ hg status
hg log
Sehen wir uns das Resultat in der Versionsgeschichte an:
$ hg log
changeset: 0:fbf04a8078f0
tag: tip
user: Hans Müller <hans.mueller@example.com>
date: Mon Aug 04 17:27:29 2008 +0200
summary: erster Import
Die Zahl 0
in changeset: 0:...
ist die Revisions-Nummer innerhalb dieses Archivs. Sie entspricht in etwa der Revisionsnummer in Subversion. Die darauf folgende Hex-Zahl ist eine ziemlich eindeutige Kennung des Changesets über Archive hinweg (dazu später mehr).
Backup
Wie oben erwähnt, ist unser neues Archiv ein Unterverzeichnis der Arbeitskopie. Hier liegt eine Gefahr. Man kann mit einem unbedachten @rm -rf mein-neues-projekt@ schnell seine einzige Arbeitskopie und das ganze Archiv löschen. Dabei wollte man wohl einfach die Arbeitskopie löschen und neu auschecken nach einer unglücklichen Änderung. Das ist bei Subversion ungefährlich, wo man das Archiv separat anlegt.
hg clone
Mit Mercurial können wir das Archiv klonen (kontrolliert kopieren) um solchem Datenverlust vorzubeugen. Das geht sehr einfach mit:
$ hg clone --noupdate . ../hgdemo-repo
Dies erzeugt eine Kopie unseres Archivs. Die Option @–noupdate@ unterdrückt die Arbeitskopie, die sonst am Ziel gleich wieder anhand des letzten Commits angelegt würde. Für eine reine Archiv-Kopie brauchen wird diese aber nicht. Wir erhalten:
$ ls -Ap ../hgdemo-repo
.hg/
Sinnvollerweise legen wir diese Kopie natürlich an einem Ort an, der regelmässig gesichert wird. Z.B. auf einem Server-Share.
Änderungen committen
Jetzt sind wir bereit, die Lizenz sauber versioniert einzuarbeiten:
$ cat >README <<-eof
= hgdemo =
Dies ist ein kleines Demo-Projekt zum Thema DVCS mit Mercurial.
Es steht unter der BSD-Lizenz. Siehe COPYING.
eof
$ cat >COPYING <<-eof
Redistribution and use in source and binary forms, with or
without modification, are permitted provided that the following
conditions are met...
eof
Nach diesen Änderungen können wir wieder den Zustand unserer Arbeitskopie sichten:
$ hg status
M README
? COPYING
M
bedeutet geändert (modified), ?
bedeutet wieder unbekannt. Wenn wir jetzt committen, protokolliert Mercurial nur die Änderung an @README@. Wir verwenden darum wiederum:
$ hg add
adding COPYING
und erhalten:
$ hg status
M README
A COPYING
Das committen wir:
$ hg commit --message "unter BSD-Liznz gestellt"
und erhalten:
$ hg log
changeset: 1:3a4353b4fc8c
tag: tip
user: Hans Müller <hans.mueller@example.com>
date: Mon Aug 04 17:27:29 2008 +0200
summary: unter BSD-Liznz gestellt
_
changeset: 0:fbf04a8078f0
user: Hans Müller <hans.mueller@example.com>
date: Mon Aug 04 17:27:29 2008 +0200
summary: erster Import
hg tip
Wir können auch nur den letzten Commit nochmals anschauen:
$ hg tip
changeset: 1:3a4353b4fc8c
tag: tip
user: Hans Müller <hans.mueller@example.com>
date: Mon Aug 04 17:27:29 2008 +0200
summary: unter BSD-Liznz gestellt
tip bedeutet in Mercurial immer den zuletzt dem Archiv zugefügten Commit. Wir können tip auch als Angabe für eine Revision verwenden. Folgendes ist demnach zu hg tip
äquivalent:
$ hg log --rev tip
changeset: 1:3a4353b4fc8c
tag: tip
user: Hans Müller <hans.mueller@example.com>
date: Mon Aug 04 17:27:29 2008 +0200
summary: unter BSD-Liznz gestellt
Commit korrigieren
Hoppla. Schreibfehler im Commit-Kommentar: “Liznz”. Macht aber nix. Dies ist DVCS, und wir arbeiten ja immer noch in unserem lokalen, absolut privaten Archiv in .hg/
. Da wir diese Arbeit noch mit niemandem geteilt haben - nicht mal mit der Backup-Kopie auf dem Share -, können wir gefahrlos den letzten Commit nochmals rückgängig machen.
Weil ich später etwas damit zeigen will, merken wir uns aber vorher kurz dieses unkorrigierte Archiv:
$ hg clone --noupdate . ../hgdemo-nofix
hg rollback
Jetzt machen wir rückgängig:
$ hg rollback
rolling back last transaction
und erhalten wieder die ursprüngliche Situation:
$ hg log
changeset: 0:fbf04a8078f0
tag: tip
user: Hans Müller <hans.mueller@example.com>
date: Mon Aug 04 17:27:29 2008 +0200
summary: erster Import
$ hg status
M README
A COPYING
Darauf committen wir sauber:
$ hg commit --message "unter BSD-Lizenz gestellt"
$ hg log
changeset: 1:b17bd4101bb7
tag: tip
user: Hans Müller <hans.mueller@example.com>
date: Mon Aug 04 17:27:30 2008 +0200
summary: unter BSD-Lizenz gestellt
_
changeset: 0:fbf04a8078f0
user: Hans Müller <hans.mueller@example.com>
date: Mon Aug 04 17:27:29 2008 +0200
summary: erster Import
Änderungen übertragen
hg outgoing
Auch der korrigierte Commit ist nun aber immer noch nur in unserem lokalen Archiv in .hg/
. Im Backup-Archiv fehlt er noch. Das sehen wir so:
$ hg outgoing ../hgdemo-repo
comparing with ../hgdemo-repo
searching for changes
changeset: 1:162e3dc28898
tag: tip
user: Hans Müller <hans.mueller@example.com>
date: Wed Aug 06 12:22:08 2008 +0200
summary: unter BSD-Lizenz gestellt
hg push
Wir übertragen ihn nun ins Backup-Archiv:
$ hg push ../hgdemo-repo
pushing to ../hgdemo-repo
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 2 changes to 2 files
(Analog dazu hätten wir auch mit hg pull
from Backup-Archiv aus die Änderung rüberholen können.)
.hg/hgrc und [paths]default
Solche push
es werden wir noch oft tun, also wäre es simpler, wir könnten den Zielpfad fixieren. Das lässt sich pro Archiv festlegen in .hg/hgrc
:
$ echo "[paths]" >>.hg/hgrc
$ echo "default = ../hgdemo-repo" >>.hg/hgrc
Damit klappt nun:
$ hg outgoing
comparing with /home/peo/dev/hg/workshop/tmp/out/hgdemo-repo
searching for changes
no changes found
$ hg push
pushing to /home/peo/dev/hg/workshop/tmp/out/hgdemo-repo
searching for changes
no changes found
Globale IDs
Wie erkennt Mercurial, welche Änderungen bei einem @push@ noch übertragen werden müssen? Es verlässt sich dazu auf die globale ID der einzelnen Commits (auch node id, change id oder revision id). Diese ID ist ein kryptographisch guter Hash über folgende Informationen:
- den Diff-Output der Änderung, inkl. aller Dateinamen, Anfügungen, Löschungen und Flag-Änderungen,
- den Commit-Text, -User und -Zeitpunkt,
- den Hash des Vorgänger-Commits (oder der beiden Vorgänger-Commits bei Merges).
Jede Änderung an irgend einer dieser Angaben führt also dazu, dass Mercurial einen Commit als neu betrachtet. Das können wir konkret anhand des vorhin gesicherten Archivs mit dem unkorrigierten Schreibfehler im Commit-Text sehen:
$ hg incoming ../hgdemo-nofix
comparing with ../hgdemo-nofix
searching for changes
changeset: 1:6ed260ddcb95
tag: tip
user: Hans Müller <hans.mueller@example.com>
date: Fri Aug 22 07:10:42 2008 +0200
summary: unter BSD-Liznz gestellt
Mercurial schlägt die von uns zuvor verworfene Fassung wieder vor. Dahinter steht ein zentrales Prinzip von Mercurial: die Geschichtsschreibung darf nicht unbemerkt manipuliert werden können. Und zur Geschichte gehören in Mercurial auch die Commit-Texte. Sobald ich also einen Commit einem anderen Archiv mitgeteilt habe, kann ich diesen nicht mehr verändern, ohne dass dies bemerkt wird.
Das geht noch weiter. Ich kann mit einer einzigen globalen ID dokumentieren, auf genau welchen Quelltextstand ich mich beziehe, inkl. seiner Geschichte. Das ist nicht fälschbar, solange der verwendete Hash-Algorithmus nicht angreifbar wird.
Die lokale Revisionsnummer (0, 1, …) hingegen ist nur ein Hilfsmittel, damit man auf der Kommandozeile eine Version in einem bestimmten Archiv einfacher benennen kann. Derselbe Commit kann in verschiedenen Archiven aber verschiedene lokale Nummern erhalten.
Build-Identifikation
Wenn ich also in mein Programm einbetten will, aus welchem Stand es compiled wurde, reicht dazu die globale ID zum Compile-Zeitpunkt. Das deckt mit einer Angabe sämtliche versionierten Quellen ab und gilt in jedem der verteilten Archive. Darum ist es unter Mercurial auch nicht üblich, in den Quellen selbst die aktuelle Revisionsnummer etc. zu hinterlegen (es ist aber möglich mit der keywords extension).
Verwandt dazu ist in Mercurial auch das Code-Signing mit digitalen Signaturen. Da die globale ID sicher ist, unterschreibt man einfach diese ID.
hg id
Die globale ID erhalten wir mit:
$ hg identify --id
2e378d9455e1
Wir automatisieren das entsprechend:
$ cat >build.sh <<-eof
#! /bin/bash
rm -rf temp; mkdir temp
touch temp/__init__.py
echo "VERSION = \"$(hg identify --id)\"" >temp/buildinfo.py
eof
$ chmod +x build.sh
$ ./build.sh
und verwenden die Version im Programm:
$ mv hgdemo.py hgdemo.py~
$ cat >hgdemo.prefix~ <<eof
from temp import buildinfo
print "Mercurial demo (version %s)" % buildinfo.VERSION
eof
$ cat hgdemo.prefix~ hgdemo.py~ >hgdemo.py
$ cat >hgdemo.sh <<-eof
#! /bin/bash
PYTHONPATH=. python hgdemo.py
eof
$ chmod +x hgdemo.sh
$ ./hgdemo.sh
Mercurial demo (version 2e378d9455e1)
The numbers from 1 to 10
1 2 3 4 5 6 7 8 9 10
Was haben wir geändert?
$ hg status
M hgdemo.py
? build.sh
? hgdemo.prefix~
? hgdemo.py~
? hgdemo.sh
? temp/__init__.py
? temp/__init__.pyc
? temp/buildinfo.py
? temp/buildinfo.pyc
Wenn wir jetzt hg add
ausführen, wird Mercurial alle temporären Dateien auch aufnehmen. Das können wir umgehen mit:
$ hg add build.sh hgdemo.sh
Ausblenden von temporären Dateien
.hgignore
Aber eigentlich möchten wir Mercurial ein für alle Mal mitteilen, dass das temp
-Verzeichnis nicht versioniert werden soll. Das tun wir in der pro Archiv zentralen .hgignore
-Datei. Mercurial kennt hier zwei Formen: regular expressions (default) und globs. Details dazu erhält man mit man hgignore
.
Wir blenden in diesem Beispiel temp
(nicht aber z.B. xy/temp
) und alle *~
-Backupdateien aus:
$ cat >.hgignore <<-eof
syntax: regexp
^temp/
syntax: glob
*~
eof
und erhalten:
$ cp hgdemo.py hgdemo.py~
$ hg status
M hgdemo.py
A build.sh
A hgdemo.sh
? .hgignore
Die Datei .hgignore
wird auch versioniert. Damit sind die Ignore-Regeln in allen Kopien des Archivs verfügbar:
$ hg add .hgignore
Commits aufteilen
Diese Änderung umfasst mehrere Aspekte:
- Ignore-Regeln
- Build-Script
- Ausgeben der Version im Programm
hg commit [file…]
Netterweise folgen die Grenzen der Aspekte auch Dateigrenzen. Somit können wir sie sauber einzeln committen. Wir geben dazu bei hg commit
einfach die gewünschten Dateien mit. Die tab completion in bash
reagiert dabei intelligent und schlägt nur geänderte Dateien vor:
$ hg status
M hgdemo.py
A .hgignore
A build.sh
A hgdemo.sh
$ hg commit --message ".hgignore für /temp und *~" .hgignore
$ hg status
M hgdemo.py
A build.sh
A hgdemo.sh
$ hg commit --message "build.sh für temp/buildinfo.py" build.sh
$ hg status
M hgdemo.py
A hgdemo.sh
Lange Commit-Texte
hg commit --logfile
Diese letzte Änderung nehmen wir als zusammengehörig an. Wir möchten sie aber gerne mit etwas mehr Text beschreiben als bisher. Rufen wir hg commit
ohne --message
auf, so started Mercurial den System-Editor zur Eingabe des Kommentars (oder den in der HGEDITOR
-Variablen konfigurierten Editor). Die erste Zeile gilt dabei als Zusammenfassung, die in kompakten Logs isoliert angezeigt werden kann. Alternativ dazu kann man den Text aus einer Datei lesen lassen. Das tun wir hier:
$ cat >/tmp/my-commit-text <<-eof
Version wird beim Start ausgegeben
* hgdemo.py gibt die Version aus temp/buildinfo.py aus
* hgdemo.sh führt hgdemo.py mit dem korrekten PYTHONPATH aus
eof
$ hg commit --logfile /tmp/my-commit-text
Standardmässig sehen wir nur die Zusammenfassung:
$ hg tip
changeset: 4:b976242cb6c7
tag: tip
user: Hans Müller <hans.mueller@example.com>
date: Fri Aug 22 11:47:59 2008 +0200
summary: Version wird beim Start ausgegeben
Den gesamten Text erhalten wir so:
$ hg tip --template "{desc}\n"
Version wird beim Start ausgegeben
* hgdemo.py gibt die Version aus temp/buildinfo.py aus
* hgdemo.sh führt hgdemo.py mit dem korrekten PYTHONPATH aus
Solche Templates werden wir gleich intensiver nutzen.
Output-Format von Logs ändern
Nun haben wir schon eine längere Versionsgeschichte:
$ hg log
changeset: 4:da20c07ec7d9
tag: tip
user: Hans Müller <hans.mueller@example.com>
date: Fri Aug 22 11:24:27 2008 +0200
summary: Version wird beim Start ausgegeben
_
changeset: 3:15ce21a2a10d
user: Hans Müller <hans.mueller@example.com>
date: Fri Aug 22 11:24:27 2008 +0200
summary: build.sh für temp/buildinfo.py
_
changeset: 2:84dc08f83c51
user: Hans Müller <hans.mueller@example.com>
date: Fri Aug 22 11:24:27 2008 +0200
summary: .hgignore für /temp und *~
_
changeset: 1:ba93b47fe009
user: Hans Müller <hans.mueller@example.com>
date: Fri Aug 22 11:24:26 2008 +0200
summary: unter BSD-Lizenz gestellt
_
changeset: 0:f1bc88aa6193
user: Hans Müller <hans.mueller@example.com>
date: Fri Aug 22 11:24:25 2008 +0200
summary: erster Import
hg log --template
Mittels Templates lässt sich das kompakter darstellen:
$ hg log --template="{rev}\t{desc|firstline} - {author|user}\n"
4 Version wird beim Start ausgegeben - hans
3 build.sh für temp/buildinfo.py - hans
2 .hgignore für /temp und *~ - hans
1 unter BSD-Lizenz gestellt - hans
0 erster Import - hans
Templater
Details zur Templater-Syntax findet man im Templater-Kapitel im mercurial book.
[ui]logtemplate
Ich persönlich finde das viel übersichtlicher und habe darum dieses Format als Standard etabliert:
$ echo "[ui]" >>~/.hgrc
$ echo "logtemplate = \"{rev}\t{desc|firstline} - {author|user}\n\"" >>~/.hgrc
$ hg log
4 Version wird beim Start ausgegeben - hans
3 build.sh für temp/buildinfo.py - hans
2 .hgignore für /temp und *~ - hans
1 unter BSD-Lizenz gestellt - hans
0 erster Import - hans
(In Wirklichkeit habe ich am Ende noch {date|age}
drin. Das gibt noch das Alter in sinnvollen Grössenordnungen aus. Ist hier nicht aktiv, da es mühsam zum Diffen ist.)
[alias]
Dazu habe ich mir noch Aliase eingerichtet mit anderen Formaten und Limits. Dazu muss man aber zunächst mal die alias extension aktivieren:
$ echo "[extensions]" >>~/.hgrc
$ echo "hgext.alias =" >>~/.hgrc
Dann kann man Aliase einrichten:
$ echo "[alias]" >>~/.hgrc
$ echo "last = log --limit 15" >>~/.hgrc
$ echo "long = log --limit 15 --template=\"{rev}\t{desc|tabindent} - {author|user}\n\"" >>~/.hgrc
$ echo "revs = log --limit 15 --template=\"{rev}:{node|short} {desc|firstline} - {author|user}\n\"" >>~/.hgrc
$ hg last
4 Version wird beim Start ausgegeben - hans
3 build.sh für temp/buildinfo.py - hans
2 .hgignore für /temp und *~ - hans
1 unter BSD-Lizenz gestellt - hans
0 erster Import - hans
$ hg long
4 Version wird beim Start ausgegeben
* hgdemo.py gibt die Version aus temp/buildinfo.py aus
* hgdemo.sh führt hgdemo.py mit dem korrekten PYTHONPATH aus - hans
3 build.sh für temp/buildinfo.py - hans
2 .hgignore für /temp und *~ - hans
1 unter BSD-Lizenz gestellt - hans
0 erster Import - hans
$ hg revs
4:b976242cb6c7 Version wird beim Start ausgegeben - hans
3:350fedc67de3 build.sh für temp/buildinfo.py - hans
2:4be6f82dd6e7 .hgignore für /temp und *~ - hans
1:bc5075549b2c unter BSD-Lizenz gestellt - hans
0:9a8bcafc18d4 erster Import - hans
Theorie: DVCS und die verbreitetsten Vertreter
Was ist DVCS?
- distributed: Archive können einfach repliziert werden. Dies ist auch der Normalfall.
- Typischerweise hat jede Arbeitskopie ein eigenes Archiv.
- Robuste inkrementelle Übertragung von Änderungen zwischen Archiven.
- Man arbeitet darum häufig mit feiner granulierten Commits als bei zentralen VCS, da man zuerst lokal mehrere Commits erzeugen kann, die man dann en bloc pusht.
- Kurzfristiges, anonymes Branching ist die Regel.
- Robustes Merging ist daher zentral.
Verbreitetste Vertreter
- git
- bzr
- svk
- darcs
- BitKeeper
Wir branchen einen Release, machen Bugfixes und mergen diese zurück
Release branchen und stabilisieren
Branch für den Release:
$ hg clone . ../hgdemo-rel
updating working directory
6 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ cd ../hgdemo-rel
Testen, stabilisieren, etc.
$ ./build.sh
$ ./hgdemo.sh
Mercurial demo (version 2e378d9455e1)
The numbers from 1 to 10
1 2 3 4 5 6 7 8 9 10
$ sed -i hgdemo.py -e "s/to 10/to 10:/"
$ ./hgdemo.sh
Mercurial demo (version 2e378d9455e1)
The numbers from 1 to 10:
1 2 3 4 5 6 7 8 9 10
$ hg commit --message "Nachricht korrigiert"
hg tag
Release ist ok:
$ hg tag 0.1
$ hg tip
6 Added tag 0.1 for changeset dd81bed74545 - hans
Release ziehen:
$ ./build.sh
$ ./hgdemo.sh
Mercurial demo (version dd81bed74545)
The numbers from 1 to 10:
1 2 3 4 5 6 7 8 9 10
Entwicklung läuft weiter
$ cd ../hgdemo
$ echo "print \"So cool!\"" >>hgdemo.py
$ ./hgdemo.sh
Mercurial demo (version dd81bed74545)
The numbers from 1 to 10
1 2 3 4 5 6 7 8 9 10
So cool!
$ hg commit --message "So cool!"
Bugfix aus Release mergen
hg pull
Bugfix abholen:
$ hg pull ../hgdemo-rel
pulling from ../hgdemo-rel
searching for changes
adding changesets
adding manifests
adding file changes
added 2 changesets with 2 changes to 2 files (+1 heads)
(run 'hg heads' to see heads, 'hg merge' to merge)
Der Branch ist nun in unserem Archiv enthalten:
$ hg heads
7 Added tag 0.1 for changeset 14673810e207 - hans
5 So cool! - hans
hg glog
Glog:
$ echo "[extensions]" >>~/.hgrc
$ echo "hgext.graphlog =" >>~/.hgrc
$ hg glog
o 7 Added tag 0.1 for changeset d4f21b377ff6 - hans
|
o 6 Nachricht korrigiert - hans
|
| @ 5 So cool! - hans
|/
o 4 Version wird beim Start ausgegeben - hans
|
o 3 build.sh für temp/buildinfo.py - hans
|
o 2 .hgignore für /temp und *~ - hans
|
o 1 unter BSD-Lizenz gestellt - hans
|
o 0 erster Import - hans
hg merge
Merge:
$ hg merge
merging hgdemo.py
1 files updated, 1 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
Testen:
$ ./hgdemo.sh
Mercurial demo (version dd81bed74545)
The numbers from 1 to 10:
1 2 3 4 5 6 7 8 9 10
So cool!
Wenn OK, commit:
$ hg commit --message "Merge von Version 0.1"
$ hg glog
@ 8 Merge von Version 0.1 - hans
|\
| o 7 Added tag 0.1 for changeset d4f21b377ff6 - hans
| |
| o 6 Nachricht korrigiert - hans
| |
o | 5 So cool! - hans
|/
o 4 Version wird beim Start ausgegeben - hans
|
o 3 build.sh für temp/buildinfo.py - hans
|
o 2 .hgignore für /temp und *~ - hans
|
o 1 unter BSD-Lizenz gestellt - hans
|
o 0 erster Import - hans
Wir schauen uns das in einigen Mercurial-GUIs an
Theorie: Wie funktioniert das Merge-Tracking bei Mercurial?
TODO
- split commits
- record commits
- longer messages
- .hgignore
Rest-Programm
- Theorie: Was ist DVCS? Welches sind die verbreitetsten Vertreter?
- Wir branchen einen Release, machen Bugfixes und mergen diese zurück.
- Wir schauen uns das in einigen Mercurial-GUIs an.
- Theorie: Wie funktioniert das Merge-Tracking bei Mercurial?
- Wir arbeiten auf diesem Projekt zusammen per Dateiaustausch und Server-Betrieb.
- Wir publizieren unser Projekt auf einem öffentlichen Server.
- Theorie: Wie setzt man grosse Mercurial-Server auf?
- Wir provozieren ein Problem (commit race).
- Theorie: Wie organisiert man sich mit einem DVCS?
- Theorie: Welche Probleme kennen wir aus der Praxis bereits? Was liegt an DVCS generell, was spezifisch an Mercurial? Was kann man tun?
- Wo erhalten wir Hilfe zu Mercurial?
Wenn die Zeit reicht:
- Wir konvertieren ein Projekt von Subversion nach Mercurial.
- Theorie: Wie macht man den Wechsel?
- Ausblick: Was läuft in der Entwicklung von Mercurial?
- Ausblick: DVCS als optionales Front-end für Subversion-Repos?
Ideen:
- Signing?