18.12.2012Version 5: English translation

1 Introduction

Git was developed by Linux Torvalds as the revision control system of the linux kernel. Currently, it is most likely the most powerful and most famous revision control system of the open source community and has a well known platform named GitHub.

For error fixes, suggestions relating to commands and scenarios belonging into this tutorial, please just send us an email.

1.1 Content and Aim of this Tutorial

On the one hand side, this tutorial should give a basic knowledge about git as well as a practical guide through the most common and some advanced commands of git. It is assumed that a git repository of the projects server and TortoiseGit or the git command line tool are used. Since the latter is the reference implementation of a git client, the usage for this tool is our guildeline in this tutorial. But every command is contained more or less equal in TortoiseGit's context menu, so only serious differences to the command line client are noted explicitly

1.2 Common Tips for Git

  • If youo are using git or an advanced feature of git for the first time: Test it somewhere else! Copy your repository folder and use it as a sandbox.
  • All commands introduced here have a big number of arguments. Linux offers man git or man git <command> for help. This manpages are also available online.
  • A very useful argument is -p, which can be found with most commands that do some changes for viewing this changes in a patch file format.
  • Since this tutorial cannot show every facet of git, we recommend some other useful tutorials online:

1.3 Definitions

The following technical terms will appear in this tutorial:

  • remote: Remote repository, e.g. on the projects server (similar to svn location)
  • clone: Making a copy of a repository of a remote (similar svn checkout)
  • commit: Transferring changes of the local working copy to the local repository (similar svn commit, but with intermediate step via the local repository)
  • push: Transferring the comitted changes to the remote.
  • pull: Transferring comitted changes from the remote to the local repository (similar svn update)
  • checkout: Getting a working copy out of a branch or a certain commit.
  • rebase: Changing of the commit history, e.g. by merging commits (squash) or pulling changes into another branch

1.4 What belongs into a repository

First: The repositories are not meant to be disk space for free. If you commit large files, we will notice!

  • good: Source code, program code, text files in common
  • slightly good: Graphics, small PDFs, word documents: Everything that is no text file, but not large either (<5MB). Commit your documentations if they are small, we do not mind.
  • bad: Binary files (.exe, .class, etc) and every ofther files that can be created out of the source files.
  • very bad: Large video files, large installation files of your tools, etc.

In addition, every commit should contain a single feature, a complete thing. This is because single commits in git can be easily reverted (revert) or picked into other branches (cherry-pick). Example: You commit a new feature and some bug fixes in the same commit. If the feature should be removed because it is faulty, the bug fixes will be reverted with the same commit.

Last but not least, only working code parts should be comitted. Just imagine a colleague commits code you cannot compile, so you are unable to continure your work until he fixes this!

2 Installing and Configuring Git

2.1 Installation

Linux: Most likely every distribution offers a package. For example Debian/Ubuntu: apt-get install git.

Windows: TortoiseGit and Git for Windows are required. In both cases, terms like beta or testing should not scare you - just choose the most recent version that is marked as featured

2.2 Configuration

First, you should tell git your name and mail address to make clear who is committing:

git config --global "Firstname Lastname"
git config --global ""

TortoiseGit: Very comfortable via the GUI: right click explorer -> TortoiseGit -> Settings -> Git

3 Creating a Git Repository

Creation of a repository is very simple: On the corresponding management site, click on create new git repository, enter a valid name and confirm. After a short time your repository is ready to use. wählt ihr Neues Git-Repository erstellen, gebt einen gültigen Namen ein und bestätigt das Ganze. Nach einer kleinen Wartezeit hat der Server das Repository in seine Konfiguration aufgenommen.

Fig.1: Creation of a Git Repository
Fig.1: Creation of a Git Repository

Currently, no one else has access to your repository! This can be changed by clicking on the title containing the repositorie's name and entering some user numbers in the appearing input field. All uses have the same permissions as you - read and write.

Fig.2: Adding one or more Users to your Git Repository
Fig.2: Adding one or more Users to your Git Repository

4 The Architecture

In comparison to other version control systems git does not use a single repository on some server, but every user owns a real copy of the repository in the .git subdirectory.

Our projects server is nothing else than a special user which does not use a working copy of the repsitory, but very well the .git directory in its file system. We are using it as a central repository, but this is more a logical than a physical paradigm.

The server acts as a so-called remote, this means a place, where changes of the local repository are transferred and synchronized to. By the way, since every user shares a copy of the repository, every user can act as a remote! The usage of more than one remote is an advanced technique not being explained by this repository.

5 Simple Usage

5.1 Clone: Copy the Repository from the Server

After we got to know the architecture it comes clear why the initial download of the repository is not named checkout, but clone: The remote repository is simply copied to the local disk and a local working copy of the master branch is checked out.

git clone
Cloning into tutorial...
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.

If no destination is given, a directory with the name of the repository - tutorial in our case - will be used, which contains the following:

$ ls -la
drwxr-xr-x  8 devel devel 4096  7. Mai 21:24 .git
-rw-r--r--  1 devel devel 2587  7. Mai 21:24 readme.txt

Two facts can be seen:

  1. The .git directory is the repository and an exact copy of the project server's repository. Do not ever touch this directory!
  2. The readme.txt has been placed by us for giving a short introduction to git. It is a working copy of the branchmaster, which is the tip of the development similar to trunk at SVN.

By the way, the slightly disturbing query for username and password can be skipped by editing a git file.

5.2 Staging und Commit: Änderungen lokal übertragen{#commit}

Wir nehmen nun ein paar Änderungen an dieser readme.txt vor und wollen diese natürlich ins Repository übertragen, wobei es wichtig ist, zwei Schritte voneinander zu unterscheiden: staging und commit. Zunächst sieht Git unsere Änderungen, jedoch sind diese nicht bereit zum comitten, wie im status zu sehen:

$ git status
# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#   modified:   readme.txt
no changes added to commit (use "git add" and/or "git commit -a")

Nun kommt die Staging-Area ins Spiel. Diese ist ein eigener kleiner Index, in den alles kommt, was comitted werden soll. Dies geschieht auf der Kommandozeile mit gitadd<dateiname> oder auch z.B. gitadd. für den aktuellen Ordner und seinen gesamten Inhalt.

TortoiseGit: Hier geschieht das staging entweder explizit über das Kontextmenü->add... (nur bei neuen Dateien) oder implizit durch Anhaken der Datei im Commit-Dialog.

$ git status
# On branch master
# Your branch is ahead of 'origin/master' by 1 commit.
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#   modified:   readme.txt

Git weiß jetzt also, dass wir die Änderungen an der readme.txt übertragen wollen. Den Inhalt der Staging-Area an das lokale Repository zu committen, ist nun ganz einfach:

$ git commit -m "Unser erster Commit"
[master 90de6d4] Unser erster Commit
 1 files changed, 2 insertions(+), 0 deletions(-)

Anmerkung1: Wie in den oberen Beispielen bereits zu sehen ist, gibt Git auch zur Laufzeit Tipps, damit man sich nicht ständig alle Kommandos merken muss. Zum Beispiel ist es möglich, mitgit checkout -- <datei>die Änderungen an der Arbeitskopie rückgängig zu machen, was dem svn revert entspricht.

Anmerkung2: Um eine Datei wieder aus dem Staging-Bereich zu entfernen, kanngit reset HEAD <datei>verwendet werden. HEAD ist ein Zeiger auf den letzten ausgecheckten Commit - in diesem Fall also dem letzten Commit im master-Entwicklungszweig.

5.3 Push: Lokale Änderungen an den Server übertragen

Der grade übertragene Commit ist zur Zeit noch nur lokal gespeichert. Um ihn an den Projekteserver zu übertragen und es damit anderen Programmierern zu ermöglichen, die Änderungen am lokalen Repository zu nutzen, müssen diese an den Projekteserver zurückübertragen (gepusht)werden. Außerdem erhöht dies natürlich durch Speicherung auf einen entfernten Server die Datensicherheit.

$ git push
Counting objects: 5, done.
Delta compression using up to 3 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 320 bytes, done.
Total 3 (delta 1), reused 0 (delta 0)
   61df014..90de6d4  master -> master

Was sagt die Meldung aus? Interessant ist eigentlich nur die letzte Zeile, die besagt, dass unser Commit 61df014 zum Eltern-Commit 90de6d4 vom lokalen master-Zweig in den entfernten master-Zweig übertragen wurde. Es muss übrigens nicht nach jedem commit gepusht werden - das kann zu einem beliebigen Zeitpunkt geschehen.

TortoiseGit: Push und Pull verstecken sich im Menüpunkt Git Sync...

Anmerkung: Die oben gezeigten Commit-IDs sind nur die bei Git üblichen Kurzformen der eigentlichen Hashes. Über jeden Commit wird solch ein Hash gebildet, welcher auf dem Eltern-Commit und anderen Faktoren beruht, sodass ein nachträgliches Fälschen der Historie (log) sehr schwer ist:

$ git log 
commit 90de6d4c321efaec38c45f21b1bf1641caec4179
Author: f4studi <>
Date:   Mon May 7 22:14:42 2012 +0200

    Unser erster Commit

commit 61df01427663ac0c11aaac981f5489c2391f4f6d
Author: f4studi <>
Date:   Fri Oct 7 00:41:22 2011 +0200

    Initial commit

5.4 Pull: Änderungen vom Server holen

Nun hat jemand anderes oder du selbst an einem entfernten Rechner etwas am Repository geändert und an den Projekteserver gepusht. Um diese Änderungen auf den lokalen Rechner zu bekommen, genügt ein

$ git pull
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
   90de6d4..68a2a52  master     -> origin/master
Updating 90de6d4..68a2a52
 readme.txt |    2 ++
 1 files changed, 2 insertions(+), 0 deletions(-)

TortoiseGit: Push und Pull verstecken sich im Menüpunkt Git Sync...

Im oben abgebildeten Fall wurden die Änderungen ins lokale Repository geholt und in die aktuelle Arbeitskopie eingefügt - pull holt übrigens den Stand aller Branches!

Solltest du parallel auch Änderungen an der selben Datei wie derjenige, dessen Änderungen du gerade gepullt hast, vorgenommen haben, muss Git diese Änderungen mergen. Meist geschieht dies vollautomatisch, da der Merging-Algorithmus von Git sehr gut arbeitet. Sollten die Änderungen an der Datei jedoch zu gravierend sein, musst du sie manuell mergen. Diesen Vorgang genau zu erklären, würde den Rahmen dieses Tutorials sprengen, jedoch seien zwei Hinweise gegeben:

  • Ein grafisches Merge-Tool ist sehr zu empfehlen. TortoiseGit-Benutzer haben das sowieso, aber auch andere Tools wie EGit für Eclipse sind verfügbar.
  • Google bietet viele gute Tipps zum Thema Merging.

Hinweis: Es könnte sein, dass du die Meldung bekommst, dass deine Arbeitskopie nicht sauber wäre und somit nicht gepullt werden kann. In diesem Falle musst du die Arbeitskopie bereinigen. Eine gute Möglichkeit dazu ist der Stash.

6 Fortgeschrittene Nutzung

6.1 Details der Architektur Gits

Kommt man aus dem Subversion-Bereich, fällt sofort auf, dass Gits Commits mit sehr langen Hashes und nicht mit fortlaufenden natürlichen Zahlen bezeichnet werden. Zwei Gründe hierfür sind:

  1. Die Hashes werden über der ID des Elterncommits, den übertragenen Daten und anderen Faktoren berechnet, was das nachträgliche Fälschen eines Commits erschwert.
  2. Da Git ein verteiltes System ist, in dem lokale Commits und Branches fest verankert sind, wäre es praktisch unmölich, eine fortlaufende Sequenz als ID zu vergeben.

Ohne noch viel weiter in die Tiefe zu gehen: Commits sind immer Änderungen am Repository, kein Abbild des gesamten Repositories. Sie haben immer einen (normaler Commit) oder mehrere (Merge) Vorgänger und bilden damit den Revisionsgraphen. Auf dieser wohl jedem Informatiker bekannten Datenstruktur interpretieren wir dann Begriffe wie Branches hinein.

So kann auch jeder Commit einzeln ausgecheckt werden: Es muss bei git checkout nicht unbedingt ein Branch angegeben werden, Commit-IDs sind ebenso möglich. Außerdem gibt es die sogenannten Referenzen, also Zeiger auf bestimmte Commits. Branches sind somit eigentlich auch nur Zeiger auf irgendeinen Commit, der berühmte HEAD ist einfach die Spitze des aktuellen Branches. Die folgende Abbildung zeigt anhand eines kleinen Beispiels, wie der origin/dev-Branch, also der dev-Branch auf dem Projekteserver, immernoch in der Mitte des Graphen hängt, obwohl dev schon lange in den master gemerged wurde.

Abb.1: Die Referenz auf den dev-Branch des Projekteservers hängt irgendwo.
Abb.1: Die Referenz auf den dev-Branch des Projekteservers hängt irgendwo.

Aus dieser Architektur Gits ergeben sich viele sehr praktische funktionen, die es von anderen Systemen abhebt. Auf diese wird im Folgenden eingegangen.

6.2 Branches

Branches - Entwicklungszweige - sind ein integraler Bestandteil Gits. Zur Erinnerung: Das, was wir auf der Festplatte als Repository sehen (den .git-Ordner ausgenommen) ist nur eine Arbeitskopie, die wir aus einem beliebigen Branch ausgecheckt haben. Dies steht im Gegensatz zu Subversion, wo ein Branch nicht mehr als ein kopierter Ordner, der per Konvention in /branches liegt, ist.

Bis jetzt haben wir immer in einem Branch gearbeitet, dem master. Das mag vielleicht in kleinen Projekten mit wenigen Mitgliedern nicht schief gehen, aber selbst dort wäre es angebracht. Stellt euch folgendes vor:

  1. Was passiert, wenn ich an einem neuen Feature arbeite, lokal schon ein paar Commits zum Sichern meines Arbeitsstandes gemacht habe und nun jemand einen Bug bemerkt, den ich beheben soll?
  2. Was, wenn ich an einem Feature arbeite, commits tätige, dann aber an einem anderen Rechner weitermachen möchte?
  3. Ist es nicht sehr unübersichtlich, wenn zehn Projektmitglieder an zehn Features arbeiten und ihre Zwischenstände in den Master comitten?

Da das hier keine Klassenarbeit ist, kommen die Antworten natürlich sofort:

  1. Hättest du das so gemacht, könntest du entweder den Bugfix mit deinen bereits gemachten Änderungen comitten und an den Server pushen - aber das wäre, keine Frage, extrem unsauber.
  2. Ohne Branch müsstest du die Änderungen im Master an den Server pushen und würdest somit in den eigentlich sauberen master deine Änderungen, die vielleicht sogar zu Kompilierungsproblemen führen, hineinpressen.
  3. Die Pflege des Revisionsgraphen ist bei Git sehr wichtig, weswegen es auch Befehle gibt, mit denen du die Historie im Nachhinein ändern kannst! Das hat unter Anderem einen sehr großen Vorteil: Ist ein neues Feature ein einziger Commit, so kann man diesen Commit und somit auch das Feature mit einem einzigen Befehl wieder rückgängig machen.

6.2.1 Einen Branch anlegen

$ git branch feature1
$ git checkout feature1
Switched to branch 'feature1'

TortoiseGit: Der Menüpunkt heißt direkt Create Branch... und der Checkout wird automatisch getätigt, falls ihr das Kästchen switch to new branch ankreuzt. Dies kann auch manuell mit Switch/Checkout... erledigt werden.

Nun, das war einfach. Wirklich! Der neue Branch wurde angelegt (branch) und wir haben ihn ausgecheckt (checkout). Da noch keine Änderungen an dem Branch getätigt wurden, befinden wir uns immernoch an der selben Stelle wie der Master.

Abb.2: Revisionsgraph nach Erstellung des Branches klzzwxh:0116.
Abb.2: Revisionsgraph nach Erstellung des Branches feature1.

Hier kann jetzt exakt so gearbeitet werden, als wäre es der master. Um das Folgende zu verdeutlichen, machen wir ein paar Änderungen in feature1 und master:

Abb.3: Revisionsgraph nach Änderungen an klzzwxh:0121 und klzzwxh:0122.
Abb.3: Revisionsgraph nach Änderungen an feature1 und master.

6.2.2 Einen Branch auf den Server pushen

Was ist zu tun, wenn auf einem anderen Rechner an einem Branch weitergearbeitet werden soll? git push überträgt feature1 nicht an den Projekteserver!

$ git push origin feature1
Counting objects: 5, done.
Delta compression using up to 3 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 326 bytes, done.
Total 3 (delta 1), reused 0 (delta 0)
 * [new branch]      feature1 -> feature1

In den letzten beiden Zeilen der Erfolg: feature1 wurde auf dem Projekteserver erstellt.

TortoiseGit: Hier ist es noch einfacher: Im Sync/Push-Dialog kann der zu pushende lokale Branch direkt gewählt werden - oder man nutzt das Kästchen push all branches.

Abb.4: Der Branch wurde auch auf dem Projekteserver erstellt (klzzwxh:0130).
Abb.4: Der Branch wurde auch auf dem Projekteserver erstellt (origin/feature1).

Hinweis: git push arbeitet nur mit Branches, die im remote, also auf dem Projekteserver, existieren. Das heißt, dass ihr ab dem ersten git push origin <branchname> nur noch git push nutzen müsst.

6.2.3 Einen neuen Branch vom Server pullen

Das geschieht automatisch, sobald sich der neue Branch auf dem Projekteserver befindet (siehe voriger Schritt).

6.2.4 Branches zusammenführen (mergen)

Nun zum wichtigsten Feature Gits, dem Mergen. Das bedeutet nichts anderes, als dass Änderungen aus verschiedenen Commits bzw. Branches wieder zusammengeführt werden. Wie im ersten Teil des Tutorials bereits erwähnt, ist Gits Algorithmus hierfür sehr fortgeschritten, sodass selten ein manuelles Eingreifen erforderlich ist.

Auf diese Fälle kann hier leider nicht im Einzelnen eingegangen werden. Es sei nur gesagt, dass ein grafisches Merge-Tool wie das in TortoiseGit oder EGit für Eclipse eingebaute dringend empfohlen werden.

Nehmen wir also unser Repository von weiter oben her und mergen die Änderungen aus feature1 in den master, da das Feature komplett implementiert wurde. Achtung: Man muss sich im Branch befinden, in den hineingemerged werden soll, nicht anders herum!

$ git status
# On branch feature1
nothing to commit (working directory clean)

$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 2 commits.

$ git merge feature1
Auto-merging readme.txt
Merge made by recursive.
 readme.txt |    2 ++
 1 files changed, 2 insertions(+), 0 deletions(-)

Abb.5: Der Branch klzzwxh:0143 wurde in den klzzwxh:0144 gemerged.
Abb.5: Der Branch feature1 wurde in den master gemerged.

TortoiseGit: Hier gilt das selbe: Man muss sich im Branch befinden, in den hineingemerged werden soll. Danach kann aus dem Kontextemenü Merge... aufgerufen werden.

Kurz gesagt: Es befinden sich alle Änderungen an der readme.txt aus beiden Branches im master.

6.2.5 Historie verändern: Rebase

TÖDLICHER HINWEIS: Alle Änderungen an der Historie dürfen nur erfolgen, wenn die betreffenden Commits noch nicht gepusht wurden! Denkt daran: Git ist ein verteiltes System. Sobald jemand anderes eure Änderungen gepullt hat, zieht eine Änderung am Revisionsgraphen böse Inkonsistenz und den Untergang der Welt herbei!

Eingangs dieses Abschnittes wurde erwähnt, dass die Pflege der Historie bei Git wichtig ist. Sehen wir uns das Log an, bemerken wir, dass es zwei sehr ähnliche Commits im Master gibt. Wenn diese Commits nun eigentlich ein einziges Feature sind, sollten diese beiden Commits auch zusammengefasst werden!

Achtung: Dieser Schritt sollte Dringend vor einem Merge wie im vorigen Abschnitt beschrieben geschehen, da rebase die Historie komplett neu schreibt und den Merge verwirft!

Hinweis: Wenn ihr das erste Mal rebase benutzt, solltet ihr den Repositoryordner kopieren, falls ihr aus versehen etwas kaputt macht! Schaut euch vor dem endgültigen pushen den Revisionsgraphen an, um festzustellen, ob wirklich alles OK ist!

Hier kommt das magische Kommando rebase ins Spiel. Zuerst benutzen wir seine Fähigkeit, Commits zusammenzuführen (squash):

$ git rebase -i HEAD~2
[detached HEAD 0bdf2bd] Eine gemeinsame Änderung im Master.
 1 files changed, 4 insertions(+), 0 deletions(-)
Successfully rebased and updated refs/heads/master.

Die Anweisung lässt Git einen interaktiven rebase mit den letzten zwei Commits des aktuellen Branches (Erinnerung: HEAD ist eine Referenz auf die Spitze des aktuellen Commits) machen. Da es interaktiv ist, werden zwei Abfragen erscheinen:

pick 2c00a0a Eine Änderung im Master 1.
squash 05a501a Eine Änderung im Master 2.

Zuerst soll die Aktion gewählt werden. Hier nehmen wir den ersten Commit und squashen den zweiten in ihn hinein. Die folgende Abfrage ist ebenfalls selbsterklärend und möchte eine neue Commit-Message haben. Nach dieser Aktion sieht unser Revisionsgraph dann geordneter aus:

Abb.6: Der Revisionsgraph nach zusammenfügen zweier Commits zu klzzwxh:0167.
Abb.6: Der Revisionsgraph nach zusammenfügen zweier Commits zu Eine gemeinsame Änderung im Master.

Nun steht aber unser einzelner Commit im Branch, der ja auch schon durch einen Squash entstanden sein könnte, ziemlich alleine da. Sähe doch dumm aus, wenn der Branch aus einem einzigen Commit mit einem kleinen Feature bestehen würde. Hier kommt die eigentliche Magie rebases zum Vorschein:

$ git rebase feature1
First, rewinding head to replay your work on top of it...
Applying: Eine gemeinsame Änderung im Master.

Außer dass wir rebase anstatt merge schreiben, ändert sich an diesem Aufruf nicht viel. Doch, siehe da, der Revisionsgraph ist schön übersichtlich und bereinigt:

Abb.7: Nun wurde klzzwxh:0171 auch direkt in den Master überführt.
Abb.7: Nun wurde Eine Änderung im Branch auch direkt in den Master überführt.

TortoiseGit: Hier geschieht das alles über den Menüpunkt Rebase.... UpStream ist dabei der Branch, in den der Rebase gehen soll. Allerdings ist dieser Dialog im Vergleich zur Kommandozeile wirklich hakelig, sodass auch hier die Git-Kommandozeile unter Windows empfohlen wird.

6.2.6 Einen Branch löschen (lokal und remote)

Wird ein Branch nicht mehr benötigt, kann er sowohl lokal, als auch auf dem Server gelöscht werden:

$ git branch -d feature1
Deleted branch feature1 (was dcca4c0).

Löscht den Branch lokal. Achtung: Sollten sich im Branch Änderungen befinden, die noch nicht in den master gemerged wurden, wird eine Warnung angezeigt. Diese kann - auf eigene Gefahr! - mit -D ignoriert werden.

$ git push origin :feature1
 - [deleted]         feature1

Diese Anweisung löscht den Branch auf dem Projekteserver. Die Anweisung sieht zuerst etwas verwirrend aus, haben wir doch vorhin mit push origin feature1 erst den Branch erstellt! Die vollständige Syntax dieses Befehls lautet jedoch eigentlich push <remote> <local branch>:<remote branch>. In diesem Falle ist der lokale Branch einfach leer, wir überschreiben den remote Branch also mit nichts.

Abb.8: Der Revisionsgraph nach dem Löschen des lokalen und remote Branch klzzwxh:0182.
Abb.8: Der Revisionsgraph nach dem Löschen des lokalen und remote Branch (origin/)feature1.

TortoiseGit: Im Sync-Dialog gibt es neben den Auswahlfeldern für den local und remote Branch einen Button mit drei Punkten, über den Ihr an die Verwaltung der lokalen bzw remote Branches kommt.

6.3 Lokale Änderungen zwischenspeichern

Wenn man grade konzentriert an einem neuen Feature in einem Branch arbeitet, ein anderer Programmierer aber genau jetzt einen Bug gefixt haben möchte, hat man ein Problem: Wohin mit den lokalen Änderungen? Repository woanders neu klonen, master auschecken? Das geht viel einfacher: stash ist eine Art Zwischenspeicher, mit dem ihr eure Arbeitskopie ganz einfach säubern und somit an etwas anderem weiterarbeiten könnt.

$ git status
# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#   modified:   readme.txt
no changes added to commit (use "git add" and/or "git commit -a")

Es gibt Änderungen an der readme.txt! Diese jagen wir in einen Stash:

$ git stash
Saved working directory and index state WIP on master: 14de5dd Eine gemeinsame Änderung im Master.
HEAD is now at 14de5dd Eine gemeinsame Änderung im Master.

$ git stash list
stash@{0}: WIP on master: 14de5dd Eine gemeinsame Änderung im Master.

$ git status
# On branch master
# Your branch is ahead of 'origin/master' by 2 commits.
nothing to commit (working directory clean)

Und die Arbeitskopie ist wieder sauber. Nun kann der gespeicherte Zwischenstand zum Beispiel mit pop wieder herausgeholt werden.

$ git stash pop
# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#   modified:   readme.txt
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (06c452424c48143531fe79e99992ed34371f71fb)

stash ist ein sehr nützliches und einfach zu bedienendes Tool. Im entsprechenden Handbuch können noch weitere Fähigkeiten nachgelesen werden, wie zum Beispiel das Anwenden eines bestimmten Stashes und nicht des ersten.

TortoiseGit: Auch hier ist TortoiseGit etwas beschnitten, sodass der Stash tatsächlich nur als Stack mit save und pop benutzt werden kann.

6.4 Arbeitskopie säubern

Manchmal möchte man die Arbeitskopie von allen nicht von Git getrackten Dateien säubern (clean), zum Beispiel kompilierte Binärdateien:

$ ll
insgesamt 20
drwxr-xr-x  3 devel devel 4096 17. Mai 16:56 .
drwxr-xr-x 52 devel devel 4096 17. Mai 16:56 ..
drwxr-xr-x  8 devel devel 4096 17. Mai 16:53 .git
-rw-r--r--  1 devel devel    7 17. Mai 16:56 muelldatei
-rw-r--r--  1 devel devel 2747 17. Mai 16:53 readme.txt

$ git clean -f
Removing muelldatei

$ ll
insgesamt 16
drwxr-xr-x  3 devel devel 4096 17. Mai 16:56 .
drwxr-xr-x 52 devel devel 4096 17. Mai 16:56 ..
drwxr-xr-x  8 devel devel 4096 17. Mai 16:53 .git
-rw-r--r--  1 devel devel 2747 17. Mai 16:53 readme.txt

Hinweis: Bei aktivierter Git-Variable clean.requireForce (default: ja) ist -f ist nötig.

6.5 Einen bestimmten Commit zurücknehmen

Wenn man nun später feststellt, dass ein Feature doch nicht so toll war, man aber brav die Historie gepflegt hat und das Feature ein einziger Commit war, so kann man das ganz einfach wieder reverten:

$ git revert dcca4c0ead53a27f3404f40ccbc0b58ace5ec7d3
Finished one revert.
[master f95ac16] Revert "Eine Änderung im Branch"
 1 files changed, 0 insertions(+), 2 deletions(-)

Nach einer kurzen Abfrage für eine Commit-Message des Commits, der die Änderungen rückgängig macht, ist das Feature weg:

Abb.9: Der Revisionsgraph nach dem Zurücknehmen von klzzwxh:0202.
Abb.9: Der Revisionsgraph nach dem Zurücknehmen von Eine Änderung im Branch.

TortoiseGit: Hier kommt ihr über das Log an die Liste der Commits heran, von wo aus wieder alles schön per Rechtsklick auf einen Commit funktioniert.

6.6 Die Arbeitskopie auf einen Commit zurücksetzen

Alles Mist, ihr wollt wieder eine saubere Arbeitskopie und ihr braucht eure Änderungen nicht mehr? Das, was bei Subversion verwirrenderweise revert heißt, nennt sich in Git reset:

$ git status
# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#   modified:   readme.txt
no changes added to commit (use "git add" and/or "git commit -a")

$ git reset --hard
HEAD is now at f95ac16 Revert "Eine Änderung im Branch"

$ git status
# On branch master
# Your branch is ahead of 'origin/master' by 1 commit.
nothing to commit (working directory clean)

Bitte beachtet, dass dieses Kommando sich auf alle von Git getrackten Dateien bezieht, also in dieser Hinsicht irgendwo das Gegenstück zu clean ist. --hard bedeutet hier, dass ohne Rücksicht auf Verluste alles auf den HEAD zurückgesetzt wird. Natürlich kann als optionales Argument der HEAD oder jede andere Commit-ID explizit angegeben werden.

TortoiseGit: Auch hier funktioniert das alles über das Log.

6.7 Nicht gepushte Commits verwerfen

Ganz einfach: git reset --hard HEAD~x, wobei x die Anzahl der Commits ist, die verworfen werden soll.

Achtung: Auch hier sollte das nur getan werden, wenn noch nicht gepusht wurde!

TortoiseGit: Log! (siehe die beiden Punkte zuvor)

6.8 Einen bestimmten Commit in einen Branch mergen

Stellt euch vor, ihr arbeitet gerade an einem neuen Feature in einem Branch, während ein Kollege ein anderes Feature, ein besseres Installationsskript oder Ähnliches in den Master comitted, welches ihr aber gerne auch in eurem Branch haben möchtet.

In Subversion könnte man vielleicht nur die entsprechende Datei updaten, was jedoch bei Git nicht geht, da die feingranularste Änderung in Git der Commit und nicht die Datei ist. Hier kommt das Kommando cherry-pick zum Einsatz. Wie es schon sagt, kann man sich einen Commit herauspicken und in den eigenen Branch einpflegen:

Abb.10: klzzwxh:0222 möchten wir in klzzwxh:0223 sehen...
Abb.10: Ein anderes Feature möchten wir in feature2 sehen...

$ git cherry-pick 92969541c9e0ddf8972ef3c408f8ddef45f56064
Finished one cherry-pick.
[feature2 b8df4bf] Ein anderes Feature
 1 files changed, 2 insertions(+), 0 deletions(-)

Und schon haben wir das neue Feature auch in unserem Branch:

Abb.11: Nun befindet sich klzzwxh:0225 auch in klzzwxh:0226
Abb.11: Nun befindet sich Ein anderes Feature auch in feature2

TortoiseGit: Wieder Log! (Siehe die drei Punkte zuvor)

6.9 Nur bestimmte Änderungen einer Datei comitten

Nicht immer ist der Alltag so linear, wie Git es möchte: Sehr oft findet man während der Implementierung eines Features einen Bug und fixt ihn nebenbei. Was nun, wenn man das in der selben Datei, die auch Änderungen des neuen Features beinhaltet, getan hat? Hier kommt der eingangs erwähnte -p Parameter zum Einsatz:

$ git add -p [dateiname]

Es werden nun nach und nach alle Änderungen der Datei interaktiv abgefragt. Durch drücken der h Taste kann eine Hilfe zu allen Befehlen angezeigt werden.