Git - Tutorial

26.05.2013

Git-Tutorial

Dieser Artikel beschreibt, wie man das Versionskontrollsystem (VCS) Git verwenden kann, um verschiedene Versionsstände von Dateien zu verwalten. In diesem Artikel steht die Benutzung von lokalen Repositories auf dem eigenen Rechner im Vordergrund, wobei aber auch die Benutzung von remote Repositories kurz erklärt wird. Das Ziel des Artikels ist, dass man bei lokalen Projekten eine zuverlässige Möglichkeit hat, zu einen vorherigen Version zurückzukehren. Bei der Verwendung von Git ist man nicht darauf beschränkt, dass Sourcecode in das Repository abgelegt wird. Es ist beispielsweise problemlos möglich Word- oder Excel-Dokumente mit Git zu verwalten.

Veraussetzungen

Ich gehe im Folgenden davon aus, dass Git installiert ist und von der Kommandozeile aus aufgerufen werden kann.

Um das zu überprüfen kann man folgendes Kommando in einem Terminal ausführen:

git --version

Als Antwort sollte die Versionsnummer von Git zu sehen sein:

git version 1.8.1.3

Beispiel

In den nächsten Schritten wird an einem Beispiel gezeigt, wie man mit Git arbeitet. Dazu wird ein Beispielprojekt angelegt, es werden ein paar Dateien eingecheckt, verändert, wieder zurückgerollt. Außerdem wird ein Feature-Branch angelegt und das Mergen von Branches erklärt. Konkret sind das folgende Schritte:

  1. Git-Repository anlegen (git init)
  2. Status abfragen (git status)
  3. Dateien ausschließen
  4. Dateien vorbereiten (git add)
  5. Dateien einchecken (git commit)
  6. Überblick über commits (git log)
  7. Zwischen Versionen wechseln (git checkout)
  8. Einen Branch anlegen (git checkout -b)
  9. Versionen vergleichen (git diff)
  10. Branches zusammenführen (git merge)
  11. Branches löschen (git checkout -d)
  12. Merge Konflikte
  13. Tags verwenden (git tag)
  14. Ein remote Repository benutzen (git clone)
  15. Ein lokales mit einem remote Repository abgleichen (git pull)
  16. Änderungen im remote Repository speichern (git push)

Git-Repository anlegen

Zum Anlegen eines neuen lokalen Repositories wechselt man in das Verzeichnis, in dem sich die Daten befinden, die im Git abgelegt werden sollen. Beispielsweise: /Users/hameister/Documents/workspace/TCXViewer.

Anschließend ruft man folgenden Befehl auf:

git init

Und erhält als Antwort, dass ein leeres Git Repository initialisiert wurde.

Initialized empty Git repository in /Users/hameister/Documents/workspace/TCXViewer/.git/

Status abfragen

Um sich einen Überblick zu verschaffen in welchem Zustand die Dateien in dem Verzeichnis sind wird folgender Befehl verwendet:

git status

Für das Beispiel von oben, könnte die Ausgabe so aussehen:

# On branch master
#
# Initial commit
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#	.classpath
#	.project
#	.settings/
#	04.05.13 09-04-29.tcx
#	12.05.13 09-32-26.tcx
#	19.05.13 09-06-59.tcx
#	25.04.13 16-43-38.tcx
#	bin/
#	build.fxbuild
#	src/
nothing added to commit but untracked files present (use "git add" to track)

Die Ausgabe sagt, dass man sich im master-Branch befindet und sich eine Reihe von Dateien noch in Zustand untracked befinden. D.h. nicht unter Versionskontrolle von Git stehen.

Dateien ignorieren

Da oft nicht alle Dateien in einem Verzeichnis/Projekt unter Versionskontrolle gestellt werden sollen, bietet Git die Möglichkeit mit der Datei .gitignore gezielt Dateien auszuschließen.

Mit dem Kommando touch kann man unter Unix/Mac eine Datei .gitignore anlegen.

touch .gitignore

Um die neue Datei zu editieren, kann vi oder jeder andere Texteditor verwendet werden.

vi .gitignore

In dem Beispiel soll das Verzeichnis bin und einige tcx-Dateien ignoriert werden. Um das zu erreichen werden diese Dateien und das Verzeichnis in der Datei .gitignore ergänzt. (Verzeichnisse mit '/' abschließen.)

bin/
04.05.13 09-04-29.tcx
19.05.13 09-06-59.tcx
12.05.13 09-32-26.tcx
25.04.13 16-43-38.tcx

Dateien für den commit vorbereiten

Vor dem eigentlich commit in das Repository werden die Dateien in eine sogenannte Staging Area verschoben. Dies passiert mit dem Kommando add. In dem Staging-Area befinden sich alle Dateien, die beim nächsten commit in das Repository eingecheckt werden sollen.

Für das Beispiel könnte das Verschieben in die Staging-Areas folgendermaßen aussehen:

git add .classpath 
git add .gitignore 
git add .project 
git add .settings/
git add build.fxbuild 
git add src/

Es lassen sich also einzelne Dateien und auch ganze Verzeichnisse mit git add verschieben.

Nachdem die Dateien verschoben sind, kann das Ergebnis mit git status überprüft werden.

git status

# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached ..." to unstage)
#
#	new file:   .classpath
#	new file:   .gitignore
#	new file:   .project
#	new file:   .settings/org.eclipse.jdt.core.prefs
#	new file:   build.fxbuild
#	new file:   src/TCX.css
#	new file:   src/TCXAltitude.css
#	new file:   src/TCXHeartrate.css
#	new file:   src/TCXSpeed.css
#	new file:   src/org/hameister/tcxviewer/TCXLoader.java
#	new file:   src/org/hameister/tcxviewer/TCXViewer.java
#

Änderungen einchecken

Zum Einchecken der Dateien aus dem Staging-Area wird das Kommando git commit verwendet. Um einen Kommentar hinzuzufügen wird der Parameter -m benutzt.

git commit -m "Initialer Checkin."

Die Ausgabe von dem commit sieht ungefähr so aus:

[master (root-commit) ad80184] Initialer checkin
 11 files changed, 653 insertions(+)
 create mode 100644 .classpath
 create mode 100644 .gitignore
 create mode 100644 .project
 create mode 100644 .settings/org.eclipse.jdt.core.prefs
 create mode 100644 build.fxbuild
 create mode 100644 src/TCX.css
 create mode 100644 src/TCXAltitude.css
 create mode 100644 src/TCXHeartrate.css
 create mode 100644 src/TCXSpeed.css
 create mode 100644 src/org/hameister/tcxviewer/TCXLoader.java
 create mode 100644 src/org/hameister/tcxviewer/TCXViewer.java

Nach einem erneuten Aufruf von git status wird einem von Git mitgeteilt, dass sich keine Dateien mehr im Arbeitsverzeichnis befinden, die nicht eingecheckt sind.

git status

# On branch master
nothing to commit, working directory clean

Dateien hinzufügen, ändern und einchecken

Im nächsten Schritt sollen neue und veränderte Dateien eingecheckt werden. Nach den Änderungen wird als erstes wieder git status aufgerufen.

git status

# On branch master
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#	modified:   .classpath
#	modified:   src/org/hameister/tcxviewer/TCXLoader.java
#	modified:   src/org/hameister/tcxviewer/TCXViewer.java
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#	src/org/hameister/tcxviewer/TCXController.java
#	src/org/hameister/tcxviewer/TCXView.java
no changes added to commit (use "git add" and/or "git commit -a")

In der Ausgabe sieht man, dass drei Dateien verändert wurden und zwei hinzugekommen sind, die sich noch im Zustand untracked befinden.

Um die Dateien auch unter Versionskontroller zu stellen, wird wieder das Kommando git add verwenden, damit sie in die Staging-Area verschoben werden. Mit den geänderten Dateien verfährt man genauso:

git add .classpath
git add src/org/hameister/tcxviewer/TCXLoader.java
git add src/org/hameister/tcxviewer/TCXViewer.java
git add src/org/hameister/tcxviewer/TCXController.java
git add src/org/hameister/tcxviewer/TCXView.java

Anschließend kann durch den Aufruf git status überprüft werden, ob sich alle veränderten und neuen Dateien im Staging-Area befinden:

git status

# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#	modified:   .classpath
#	new file:   src/org/hameister/tcxviewer/TCXController.java
#	modified:   src/org/hameister/tcxviewer/TCXLoader.java
#	renamed:    src/org/hameister/tcxviewer/TCXViewer.java -> src/org/hameister/tcxviewer/TCXView.java
#	modified:   src/org/hameister/tcxviewer/TCXViewer.java
#

Zum Einchecken wird wieder das Kommando git commit verwendet:

git commit -m "Change to MVC and added Property bindings"

Die Ausgabe könnte folgendermaßen aussehen:

[master 4992ffb] Change to MVC and added Property bindings
 5 files changed, 297 insertions(+), 303 deletions(-)
 create mode 100644 src/org/hameister/tcxviewer/TCXController.java
 rename src/org/hameister/tcxviewer/{TCXViewer.java => TCXView.java} (52%)
 rewrite src/org/hameister/tcxviewer/TCXViewer.java (92%)
 

Commits auflisten

Um sich einen Überblick über die erfolgten commits zu verschaffen, kann das Kommando git log verwendet werden.

In unserem Beispiel wurde zwei commits durchgeführt. Einmal der initiale commit und anschließend der commit mit den ersten Änderungen:

git log

commit 4992ffb2c4fb5612baebdcd34e06c5e281c1de64
Author: Jörn Hameister <hameister@macbookpro.fritz.box>
Date:   Sun May 26 20:34:14 2013 +0200

    Change to MVC and added Property bindings

commit ad80184f75945d7fbdf6e824cb371528be124b26
Author: Jörn Hameister <hameister@macbookpro.fritz.box>
Date:   Sun May 26 09:22:10 2013 +0200

    Initialer checkin

Zwischen Versionen wechseln

Falls man nach einem commit feststellt, dass die Änderungen falsch sind und man wieder zu der vorhergehenden Version zurückkehren möchte, kann dies ganz einfach mit einem git checkout erreicht werden. Was allerdings benötigt wird, ist die commit-Nummer, die man mit git log bestimmen kann.

In dem Beispiel soll zum Initialen checkin zurück gekehrt werden.

git checkout ad80184f75945d7fbdf6e824cb371528be124b26

Als Ausgabe erhält man dann in etwa folgendes:

Note: checking out 'ad80184f75945d7fbdf6e824cb371528be124b26'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at ad80184... Initialer checkin

Wie der Text schon sagt, ist man nun abgekoppelt vom HEAD. Um sich Dinge anzuschauen ist diese Vorgehen in Ordnung. Wenn man allerdings weiterarbeiten möchte und seine Änderungen später in den HEAD zurückfließen lassen möchte, dann sollte man in einem neuen Branch auschecken.

Um wieder zurück zum master zu kommen, kann folgendes Kommando verwendet werden:

git checkout master

Versionen vergleichen

Um zwei Versionen miteinader zu vergleichen existiert das Kommando diff. Um beispielsweise die beiden eingecheckten Versionen miteinander zu vergleichen, kann folgendes Kommando ausgeführt werden (Hinweis: Die commit Nummern erhält man mit git log und sie werden auf deinem Rechner anders aussehen.)

git diff 4992ffb2c4fb5612baebdcd34e06c5e281c1de64 ad80184f75945d7fbdf6e824cb371528be124b26

Ein Ausschnitt der Ausgabe, sieht ungefähr so aus:

...
diff --git a/src/org/hameister/tcxviewer/TCXLoader.java b/src/org/hameister/tcxviewer/TCXLoader.java
index e3b01f2..8109947 100644
--- a/src/org/hameister/tcxviewer/TCXLoader.java
+++ b/src/org/hameister/tcxviewer/TCXLoader.java
@@ -5,13 +5,10 @@ import java.io.IOException;
 import java.text.DecimalFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.util.Collections;
 import java.util.Date;
 import java.util.concurrent.TimeUnit;
 ...
 

Die Diff-Ansicht läßt sich mit q beenden.

Das diese Ansicht bei größeren Changesets unübersichtlicht ist, bieten sich grafisches Tools, wie SourceTree an.

Create New Project

Branch anlegen

Um einen Branch anzulegen, wird bei checkout einfach der Parameter b mit einem Namen für den Branch mitgegeben.

git checkout -b JH_Test_Branch
Switched to a new branch 'JH_Test_Branch'

Nun befindet man sich im Branch JH_Test_Branch.

Auf diese Weise können in dem Branch Änderungen vorgenommen werden, die auch in den Branch eingecheckt werden. Der master wird von den Änderungen nicht berührt.

Mergen von Branches

Falls in der Zwischenzeit auch im master Änderungen durchgeführt werden, kann es notwendig werden den Branch JH_Test_Branch mit dem master zu mergen.

Um sich einen Überblick zu verschaffen, wo man sich befindet, kann folgendes Kommando verwendet werden:

git log --graph --oneline --decorate --all

Als Ausgabe erhält man eine Anzeige, in welchem Branch man sich befindet und ob sich der master verändert hat.

Wie man an der Ausgabe sieht, befinden wir uns im Branch JH_Test_Branch (das ist der HEAD). Allerdings hat jemand im master eine Änderung mit der commit-Nachricht Local variable for TCXController is not necessary. eingecheckt.

Um sich die Änderungen anzusehen, kann wieder der Befehl git diff verwendet werden:

macbookpro:TCXViewer hameister$ git diff 2cac78e b525cd6
diff --git a/src/org/hameister/tcxviewer/TCXLoader.java b/src/org/hameister/tcxviewer/TCXLoader.java
index e3b01f2..f1e41b2 100644
--- a/src/org/hameister/tcxviewer/TCXLoader.java
+++ b/src/org/hameister/tcxviewer/TCXLoader.java
@@ -246,7 +246,7 @@ public class TCXLoader {
                altitudeData = reducePoints(altitudeData);
                heartRateData = reducePoints(heartRateData);
 
-               DecimalFormat altitudeFormatter = new DecimalFormat("#.00 NN");
+               DecimalFormat altitudeFormatter = new DecimalFormat("#.00 N.N.");
                
                distanceKmProperty.setValue(getDistanceInKilometers());
                maxKmPerHourProperty.setValue(getMaximumSpeed());
diff --git a/src/org/hameister/tcxviewer/TCXViewer.java b/src/org/hameister/tcxviewer/TCXViewer.java
index 420c314..5b48f7b 100644
--- a/src/org/hameister/tcxviewer/TCXViewer.java
+++ b/src/org/hameister/tcxviewer/TCXViewer.java
@@ -10,7 +10,7 @@ public class TCXViewer extends Application {
        public void start(Stage primaryStage) {
                long startTime = System.currentTimeMillis();
 
-               new TCXController(primaryStage);
+               TCXController controller = new TCXController(primaryStage);
                
                
                primaryStage.setTitle("TCX Data Viewer");

Da die Änderungen an unterschiedlichen Dateien durchgeführt wurden und keine Abhängikeiten bestehen, kann der Branch einfach in den master gemerged werden. Dazu wechselt man in den master und führt die beiden Branches durch folgendes Kommando zusammen:

git checkout master
git merge JH_Test_Branch

Branch löschen

Der Branch JH_Test_Branch wird nach dem Merge nicht mehr benötigt und kann deshalb mit dem Kommando git branch -d gelöscht werden.

git branch -d JH_Test_Branch

Deleted branch JH_Test_Branch (was 9c1747f).

Merge-Konflikte

Merge-Konflikte treten auf, wenn im master und im Branch Änderungen an der gleichen Datei durchgeführt wurden.

Wurde beispielsweise ein Feature-Branch FEATURE1_BRANCH angelegt, um Änderungen an der Datei TCXLoader.java durchzuführen und gleichzeitig hat jemand anderes Änderungen an der gleichen Datei im master durchgeführt, dann erscheint folgende Fehlermeldung bei einem Merge-Versuch: (Wir befinden uns im master)

git merge FEATURE1_BRANCH

Auto-merging src/org/hameister/tcxviewer/TCXLoader.java
CONFLICT (content): Merge conflict in src/org/hameister/tcxviewer/TCXLoader.java
Automatic merge failed; fix conflicts and then commit the result.

Wenn man nun in die Konfliktdatei schaut, ist der Konflikt folgendermaßen hervorgehoben:

<<<<<<< HEAD
		return "00:00:00";
=======
		return "00:00";
>>>>>>> FEATURE1_BRANCH

Diesen Konflikt muss man dann per Hand beheben und die Datei erneut in den master einchecken.

Wenn man den Branch weiter verwenden möchte, dann sollte man ihn nochmal mit dem master mergen. Dafür wechselt man in den Branch und ruft git merge master auf. Konflikte gibt es nicht mehr, so dass der Merge automatisch durchgeführt wird. Nun sind master und FEATURE1_BRANCH wird auf dem gleichen stand.

Wenn man den Feature-Branch nach dem Merge nicht weiter verwenden möchte, kann man ihn auch einfach mit git branch -d FEATURE1_BRANCH löschen.

Tags verwenden

Um eine eingecheckte Version in Git zu markieren, kann ein Tag verwendet werden. Prinzipiell hat man zwar für jeden commit eine eindeutige Id, aber mit einem Tag läßt sich eine Version nochmal "hervorheben".

Um einen Tag anzulegen kann folgendes Kommando verwendet werden:

git tag -a "Version-1.0.2" -m "Hotfix release 1.0.2"

Dabei wird mit dem Parameter -a die Version angegeben und mit dem Parameter -m ein Kommentar.

Um sich alle Tags anzeigen zu lassen, kann folgendes Kommando verwendet werden:

git tag

Falls versehentlich ein Tag angelegt wurde und der Tag wieder gelöscht werden soll, kann dies mit folgendem Kommando erreicht werden:

git tag -d "Version-1.0.2"

Remote Repositories benutzen

Um ein remote Repository lokal zu benutzen kann der Befehl git clone verwendet werden. Möchte man beispielsweise das Twitter-Projekt Bootstrap benutzen, welches bei GitHub abgelegt ist, dann kann folgendes Kommando verwendet werden:

git clone https://github.com/twitter/bootstrap.git

Daraufhin wird das Projekt lokal in ein eigenes Repository abgelegt.

Cloning into 'bootstrap'...
remote: Counting objects: 35100, done.
remote: Compressing objects: 100% (13392/13392), done.
remote: Total 35100 (delta 24316), reused 32078 (delta 21505)
Receiving objects: 100% (35100/35100), 27.03 MiB | 3.00 MiB/s, done.
Resolving deltas: 100% (24316/24316), done.

Dieses Repository verhält sich genauso, wie ein selbst angelegtes lokales Repository.

Das lokale mit dem remote Repository abgleichen

Wenn man ein clone von einem remote Repository angelegt hat, sollte man regelmäßig überprüfen, ob Änderungen gemacht wurden. Dies kann mit dem Kommando git pull erreicht werden:

git pull

Already up-to-date.

Falls es Änderungen gibt, dann werden diese automatisch in das lokale Repository gemerged.

Änderungen im remote Repository speichern

Wenn man im lokalen Repository Änderungen durchgeführt und diese in remote Repository ablegen möchte, dann wird der Befehl git push verwendet.

git push

Zu beachten ist dabei, dass man Schreibrechte für das remote Repository benötigt. Hat man diese nicht, dann erhält man eine solche Fehlermeldung:

error: The requested URL returned error: 403 while accessing https://github.com/twitter/bootstrap.git/info/refs?service=git-receive-pack
fatal: HTTP request failed

Wie man dennoch Änderungen zurück in das Projekt fließen lassen kann, habe ich in dem Artikel How to use and extend the library JFXtras kurz beschrieben. Unter dem Stichwort Pull Request findet man bei GitHub eine ausführliche Beschreibung. (Using Pull Requests).

Anmerkungen

Dieser Artikel erhebt keinen Anspruch vollständig zu sein und alle Möglichkeiten von Git zu beleuchten. Es werden die wichtigsten Kommandos kurz beschrieben und gezeigt, wie man mit ihnen arbeitet. Ausführlichere Darstellungen findet man hier: