#language de
#format wiki

||<style="border: none;"> <<TableOfContents()>> ||<style="border: none;"> {{attachment:Git-Logo-2Color.png||width=400}} ||

= Info =
 Thema:: Einführung in git
 Vortragende:: [[martin|Martin]]
 Datum:: 22.04.2015
 Beschreibung:: Die freie Versionsverwaltungssoftware git erfreut sich seit ihrem Erscheinen größter Beliebtheit und hat die Art und Weise, wie Software entwickelt wird, regelrecht revolutioniert. In diesem Workshop wollen wir etwas über die Geschichte von git erfahren, die Basics kennenlernen und in kleinen Teams kollaborativ arbeiten.

== Themen ==
 * Entstehungsgeschichte
 * allerlei Umgang (anlegen, klonen, stagen, committen, resetten, pullen, …)
 * Eigenarbeit in Kleingruppen

= Inhalt =
== Entstehungsgeschichte ==

Ziemlich genau 10 Jahre ist es her, als git das Licht der Welt erblickte. Damals
wurde bei der Entwicklung des Linux-Kernels ein proprietäres Programm namens
„!BitKeeper“ eingesetzt. Obwohl es unüblich ist proprietäre Programm im Umfeld
freier Software einzusetzen, entschied sich Linus Torvalds aus Gründen dafür.
Der Deal war wie folgt: !BitKeeper durfte für die Entwicklung des Kernels
kostenlos genutzt werden, wenn diese nicht versuchen würden es zu
reverse-engineeren.

Es kam, wie es kommen musste: das GNU war stark in Andrew "Tridge" Tridgell und
er forschte/experimentierte/spielte zu viel mit !BitKeeper herum, sodass Larry
!McVoy die Lizenz änderte und !BitKeeper damit für die Kernelentwicklung aus dem
Rennen war.

Linus stoppte die Kernelentwicklung und schaute sich nach alternativen Source
Control Management Systemen um. Jedoch fand er nichts, was seinen Ansprüchen
genügte. Also sagte er: „Ich kann in 2 Wochen etwas bauen, was besser als alles
andere da draußen ist.“ Und er sollte recht behalten.

== Alice sagt "Hallo" ==
Alice hat gerade angefangen Python zu lernen und möchte ihr erstes Programm
schreiben. Dies ist natürlich ein "hello world" Programm. Zum Glück kann Alice
schon mit git umgehen, daher nutzt sie es um ihre Zwischenergebnisse
festzuhalten.

Zu Beginn legt Alice ein Verzeichnis und darin ein git-Repository an:

{{{#!highlight sh
alice@git-workshop:~$ mkdir hello_python
alice@git-workshop:~$ cd hello_python/
alice@git-workshop:~/hello_python$ git init
Initialized empty Git repository in /home/alice/hello_python/.git/}}}

Darin fängt sie an zu programmieren:

{{{#!highlight sh
alice@git-workshop:~/hello_python$ vim hello.py
alice@git-workshop:~/hello_python$ cat hello.py
print("Hello, world.")
alice@git-workshop:~/hello_python$ python3 hello.py
Hello, world.
}}}

Die ersten Gehversuche laufen prächtig, also fügt sie die Datei
{{{hello.py}}} zu der ''staging area'' hinzu.

{{{#!highlight sh
alice@git-workshop:~/hello_python$ git add hello.py
}}}

Die staging area enthält alle Änderungen, die beim nächsten Commit eingepflegt
werden sollen. Dieser Zwischenschritt ist sinnvoll, weil Alice so sehr genau
festlegen kann, was sie committen möchte und was nicht. Wir schauen uns gleich
an, wie wir die staging area inspizieren können.

Nach dem Hinzufügen zur staging area ''committet'' Alice die Änderungen:

{{{#!highlight sh
alice@git-workshop:~/hello_python$ git commit

*** Please tell me who you are.

Run

  git config --global user.email "you@example.com"
  git config --global user.name "Your Name"

to set your account's default identity.
Omit --global to set the identity only in this repository.

fatal: unable to auto-detect email address (got 'alice@git-workshop.(none)')
}}}

Uh oh ... Alice hat vor lauter Eifer git gar nicht konfiguriert. Für einen
Commit braucht git nämlich mindestens einen Namen und eine E-Mail-Adresse.
Schließlich soll die ganze Welt sehen können, wer dieses Programm geschrieben
hat und an wen die Lobeshymnen zu schicken sind.

Dies holt sie schnell nach:

{{{#!highlight sh
alice@git-workshop:~/hello_python$ vim ~/.gitconfig
alice@git-workshop:~/hello_python$ cat ~/.gitconfig
[user]
        name = Alice Testington
        email = alice@git-workshop
}}}

Danach versucht sie erneut zu committen:

{{{#!highlight sh
alice@git-workshop:~/hello_python$ git commit
}}}

Es öffnet sich ihr Lieblingseditor und der fordert sie zum Eingeben einer
''commit message'' auf:

{{{#!highlight sh

 # Please enter the commit message for your changes. Lines starting
 # with '#' will be ignored, and an empty message aborts the commit.
 # On branch master
 #
 # Initial commit
 #
 # Changes to be committed:
 #       new file:   hello.py
 #
}}}

Alice erfreut sich daran, dass git ihr sagt, dass eine neue Datei namens
{{{hello.py}}} erzeugt wurde und schreibt ihre commit message:

{{{#!highlight sh
Fügt hello.py hinzu, welches "Hello, world." ausgibt.
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
#
# Initial commit
#
# Changes to be committed:
#       new file:   hello.py
#
}}}

Sie speichert und schließt die Datei und git gibt Informationen über ihren
Commit aus:

{{{#!highlight sh
alice@git-workshop:~/hello_python$ git commit
[master (root-commit) a9b1eee] Fügt hello.py hinzu, welches "Hello, world." ausgibt.
 1 file changed, 1 insertion(+)
 create mode 100644 hello.py
}}}

Alice möchte sich jetzt die Geschichte ihres Repository ansehen:

{{{#!highlight sh
alice@git-workshop:~/hello_python$ git log
commit a9b1eee7a74fae95caa21f488a7d050f0e20d262
Author: Alice Testington <alice@git-workshop>
Date:   Fri Apr 10 16:19:22 2015 +0000

    Fügt hello.py hinzu, welches "Hello, world." ausgibt.
}}}

Die kurze Geschichte besteht aus einem einzigen Commit. Dieser wird durch einen
SHA1-Wert gekennzeichnet
({{{a9b1eee7a74fae95caa21f488a7d050f0e20d262}}}). Weiterhin enthält der
Commit Informationen über den Autor, das Datum, und die commit message, die
Alice eingegeben hat.

Jetzt möchte Alice den aktuellen Status des Repository begutachten:
{{{#!highlight sh
alice@git-workshop:~/hello_python$ git status
On branch master
nothing to commit, working directory clean
}}}
Git sagt ihr, dass sie sich gerade auf dem master-branch befindet und dass
nichts zu committen oder sonst was zu sehen gäbe. Sie bleibt ruhig und macht
weiter.

In einem Shell-Workshop ihres lokalen Hackspaces hat Alice gelernt, dass man
einem Shellskript immer eine Shebangzeile hinzufügt. Sie ändert
{{{hello.py}}} wieder mit ihrem Lieblingseditor und testet ihre
Änderungen:
{{{#!highlight sh
alice@git-workshop:~/hello_python$ vim hello.py
}}}
{{{#!highlight python
#!/usr/bin/env python3

print("Hello, world.")
}}}
{{{#!highlight sh
alice@git-workshop:~/hello_python$ python hello.py
Hello, world.
alice@git-workshop:~/hello_python$ chmod +x hello.py
alice@git-workshop:~/hello_python$ ./hello.py
Hello, world.
}}}

Alles funktioniert prächtig. Alice ist neugierig, ob git etwas von ihren
Änderungen mitbekommen hat und erfragt den Status des Repository:
{{{#!highlight sh
alice@git-workshop:~/hello_python$ 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:   hello.py

no changes added to commit (use "git add" and/or "git commit -a")
}}}
Git hat bemerkt, dass die Datei {{{hello.py}}} geändert wurde und gibt
Alice ein paar Hinweise, was sie tun muss um die Änderungen zur staging area
hinzuzufügen oder zu verwerfen.

Alice' Neugier geht aber noch weiter. Weiß git, was sich genau geändert hat?
Dafür nutzt Alice {{{git diff}}}
{{{#!highlight sh
alice@git-workshop:~/hello_python$ git diff
diff --git a/hello.py b/hello.py
old mode 100644
new mode 100755
index ac754f5..13b0130
--- a/hello.py
+++ b/hello.py
@@ -1 +1,3 @@
+#!/usr/bin/env python3
+
 print("Hello, world.")
}}}
Git zeigt nicht nur an, dass zwei Zeilen hinzugefügt wurden (die Zeilen mit dem
{{{+}}}), sondern auch, dass sich die Berechtigungen der Datei geändert
haben ({{{new mode … old mode}}}).

Die Neugier von Alice ist damit befriedigt. Sie fügt die Datei zur staging area
hinzu und committet:
{{{#!highlight sh
alice@git-workshop:~/hello_python$ git add hello.py
alice@git-workshop:~/hello_python$ git commit
[master 19e5e3e] Fügt Shebangzeile hinzu.
 1 file changed, 2 insertions(+)
 mode change 100644 => 100755 hello.py
}}}

Wieder möchte sich Alice die Geschichte des Repository ansehen:
{{{#!highlight sh
alice@git-workshop:~/hello_python$ git log
commit 19e5e3ece8832a65cfa8ed2f8c514c14617ffa8d
Author: Alice Testington <alice@git-workshop>
Date:   Fri Apr 10 17:14:47 2015 +0000

    Fügt Shebangzeile hinzu.

commit a9b1eee7a74fae95caa21f488a7d050f0e20d262
Author: Alice Testington <alice@git-workshop>
Date:   Fri Apr 10 16:19:22 2015 +0000

    Fügt hello.py hinzu, welches "Hello, world." ausgibt.
}}}
Das Repository enthält nun zwei Commits, jeweils mit unterschiedlichen
SHA1-Werten. Diese werden in zeitlicher Reihenfolge angezeigt (neueste zuerst).

Fasziniert von der Möglichkeit sich Unterschiede anzeigen lassen zu können,
schaut sich Alice die Unterschiede zwischen ihren beiden Commits an:
{{{#!highlight sh
alice@git-workshop:~/hello_python$ git diff a9b1eee7a74fae95caa21f488a7d050f0e20d262 19e5e3ece8832a65cfa8ed2f8c514c14617ffa8d
diff --git a/hello.py b/hello.py
old mode 100644
new mode 100755
index ac754f5..13b0130
--- a/hello.py
+++ b/hello.py
@@ -1 +1,3 @@
+#!/usr/bin/env python3
+
 print("Hello, world.")
}}}
Diesen ''Diff'' hat Alice vorhin schon gesehen. Es ist genau der gleiche, den
sie vorhin mit {{{git diff}}} erzeugt hat. In diesem Fall zeigt git die
Unterschiede von Commit {{{a9b1eee7a74fae95caa21f488a7d050f0e20d262}}}
bis zu Commit {{{19e5e3ece8832a65cfa8ed2f8c514c14617ffa8d}}} an.

Alice fragt sich: „Was passiert, wenn ich die Commits vertausche?“
{{{#!highlight sh
alice@git-workshop:~/hello_python$ git diff 19e5e3ece8832a65cfa8ed2f8c514c14617ffa8d a9b1eee7a74fae95caa21f488a7d050f0e20d262
diff --git a/hello.py b/hello.py
old mode 100755
new mode 100644
index 13b0130..ac754f5
--- a/hello.py
+++ b/hello.py
@@ -1,3 +1 @@
-#!/usr/bin/env python3
-
 print("Hello, world.")
}}}
Erfreut stellt Alice fest, dass dies genau das ist, was sie erwartet hat.
Während vom ersten zum zweiten Commit Zeilen hinzugefügt werden, werden vom
zweiten zum ersten Commit Zeilen entfernt (die Zeilen mit dem {{{-}}}).
Auch die Berechtigungen der Datei wurden berücksichtigt.

Alice möchte sich jetzt die Geschichte des Repository mit den dazugehörigen
Änderungen ansehen:
{{{#!highlight sh
alice@git-workshop:~/hello_python$ git log --patch
commit 19e5e3ece8832a65cfa8ed2f8c514c14617ffa8d
Author: Alice Testington <alice@git-workshop>
Date:   Fri Apr 10 17:14:47 2015 +0000

    Fügt Shebangzeile hinzu.

diff --git a/hello.py b/hello.py
old mode 100644
new mode 100755
index ac754f5..13b0130
--- a/hello.py
+++ b/hello.py
@@ -1 +1,3 @@
+#!/usr/bin/env python3
+
 print("Hello, world.")

commit a9b1eee7a74fae95caa21f488a7d050f0e20d262
Author: Alice Testington <alice@git-workshop>
Date:   Fri Apr 10 16:19:22 2015 +0000

    Fügt hello.py hinzu, welches "Hello, world." ausgibt.

diff --git a/hello.py b/hello.py
new file mode 100644
index 0000000..ac754f5
--- /dev/null
+++ b/hello.py
@@ -0,0 +1 @@
+print("Hello, world.")
}}}

Weil Alice sich die Dinge gerne visualisiert, möchte sie sich den Graphen zur
Geschichte anzeigen lassen:
{{{#!highlight sh
alice@git-workshop:~/hello_python$ git log --graph
* commit 19e5e3ece8832a65cfa8ed2f8c514c14617ffa8d
| Author: Alice Testington <alice@git-workshop>
| Date:   Fri Apr 10 17:14:47 2015 +0000
|
|     Fügt Shebangzeile hinzu.
|
* commit a9b1eee7a74fae95caa21f488a7d050f0e20d262
  Author: Alice Testington <alice@git-workshop>
  Date:   Fri Apr 10 16:19:22 2015 +0000

      Fügt hello.py hinzu, welches "Hello, world." ausgibt.
}}}

Alice ist sehr zufrieden mit ihrer Arbeit und erzählt ihrem Freund Bob von dem
Projekt.

== Bob sagt "Hallo, Alice!" ==
Alice' Freund Bob ist ein erfahrener Pythonista und schaut sich ihr Projekt an.
Dazu klont er es in sein Home-Verzeichnis:
{{{#!highlight sh
bob@git-workshop:~$ git clone ~alice/hello_python/
Cloning into 'hello_python'...
done.
bob@git-workshop:~$ cd hello_python/
}}}

Bob kann dank git die gesamte Geschichte von Alice' Projekt sehen. Darüber
hinaus ist in Bobs Repository ein Verweis zu Alice' Repository gespeichert:
{{{#!highlight sh
bob@git-workshop:~/hello_python$ git remote
origin
bob@git-workshop:~/hello_python$ git remote -v
origin  /home/alice/hello_python/ (fetch)
origin  /home/alice/hello_python/ (push)
}}}

Nun schaut sich Bob das Programm von Alice an und stellt fest, dass man das ja
viel besser hätte machen können. Bob probiert folgendes aus:
{{{#!highlight sh
bob@git-workshop:~/hello_python$ python hello.py
Hello, world.
bob@git-workshop:~/hello_python$ python3
Python 3.4.0 (default, Apr 11 2014, 13:05:11)
[GCC 4.8.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import hello
Hello, world.
>>>
}}}
An sich tut das Programm, was es soll. Aber wenn man es als Modul lädt, dann
wird ebenfalls "Hello, world." ausgegeben. Um es "modularer" zu machen, ändert
Bob das Programm wie folgt:
{{{#!highlight sh
bob@git-workshop:~/hello_python$ vim hello.py
}}}
{{{#!highlight python
#!/usr/bin/env python3

def hello():
        print("Hello, world.")

if __name__ == '__main__':
        hello()
}}}

Bob wiederholt sein Experiment von vorhin:
{{{#!highlight sh
bob@git-workshop:~/hello_python$ python3 hello.py
Hello, world.
bob@git-workshop:~/hello_python$ python3
Python 3.4.0 (default, Apr 11 2014, 13:05:11)
[GCC 4.8.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import hello
>>> hello.hello()
Hello, world.
}}}

Jetzt wird "Hello, world." nur ausgegeben, wenn die Datei {{{hello.py}}}
direkt ausgeführt wird. Wenn man sie als Modul lädt, muss man die Funktion
{{{hello()}}} explizit aufrufen.

Bob schaut sich an, was in seinem Repository alles passiert ist:
{{{#!highlight sh
bob@git-workshop:~/hello_python$ git status
On branch master
Your branch is up-to-date with 'origin/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:   hello.py

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        __pycache__/
        hello.pyc

no changes added to commit (use "git add" and/or "git commit -a")
}}}

Einerseits sieht er, dass seine Änderungen an {{{hello.py}}} wahrgenommen
wurden. Andererseits sieht er, dass ein paar Dateien bzw. Ordner hinzugekommen
sind, die noch nicht von git erfasst wurden ("untracked files").

Bob möchte nicht, dass {{{__pycache__}}} und {{{hello.pyc}}} von
git aufgenommen werden. Dazu müsste er ganz einfach nichts tun. Bob möchte
allerdings auch nicht, dass er ständig darauf hingewiesen wird, dass diese
Dateien bzw. Ordner noch nicht überwacht werden.

Dafür legt Bob eine Datei namens {{{.gitignore}}} an und schreibt
folgendes hinein:
{{{#!highlight sh
bob@git-workshop:~/hello_python$ vim .gitignore
bob@git-workshop:~/hello_python$ cat .gitignore
__pycache__/*
*.pyc
}}}

Wenn Bob jetzt den Status seines Repository ansieht,
{{{#!highlight sh
bob@git-workshop:~/hello_python$ git status
On branch master
Your branch is up-to-date with 'origin/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:   hello.py

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        .gitignore

no changes added to commit (use "git add" and/or "git commit -a")
}}}
dann bemerkt er, dass die beiden Einträge von eben verschwunden sind, aber dafür
ein neuer Eintrag ({{{.gitignore}}}) hinzugekommen ist. „Das ist OK“,
denkt sich Bob, „damit erspare ich mir in Zukunft eine Menge Nervereien.“

Jetzt möchte sich Bob den Diff seiner Änderungen ansehen:
{{{#!highlight sh
bob@git-workshop:~/hello_python$ git diff
diff --git a/hello.py b/hello.py
index 13b0130..fd0d179 100755
--- a/hello.py
+++ b/hello.py
@@ -1,3 +1,7 @@
 #!/usr/bin/env python3

-print("Hello, world.")
+def hello():
+       print("Hello, world.")
+
+if __name__ == '__main__':
+       hello()
}}}
Die Zeile von Alice wird gelöscht und dafür werden die Zeilen von Bob
hinzugefügt.

Zufrieden mit seiner Arbeit bereitet Bob den Commit vor. Dazu fügt er die
{{{hello.py}}} und {{{.gitignore}}} zu staging area hinzu:
{{{#!highlight sh
bob@git-workshop:~/hello_python$ git add hello.py .gitignore
}}}

Um wirklich sicherzugehen, dass nur diese Dateien committet werden, überprüft er
nochmal den Status:
{{{#!highlight sh
bob@git-workshop:~/hello_python$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        new file:   .gitignore
        modified:   hello.py
}}}

Eine Datei wird geändert und eine neu hinzugefügt. Ab dafür!
{{{#!highlight sh
bob@git-workshop:~/hello_python$ git commit
[master 100c5c1] Macht hello.py modularer und fügt .gitignore hinzu.
 2 files changed, 7 insertions(+), 1 deletion(-)
 create mode 100644 .gitignore
}}}

Bob sieht seine Arbeit als erledigt an und bittet Alice die Änderungen zu
übernehmen. Dazu teilt er ihr die Adresse seines Repository mit
({{{/home/bob/hello_python/}}}).

== Alice sagt "Hallo, Bob!" ==
Alice freut sich sehr über die Nachricht von Bob und ist neugierig auf die
Änderungen, die Bob an ihrem Programm gemacht hat. Zunächst fügt sie Bobs
Repository als entferntes Repository zu ihrem hinzu:
{{{#!highlight sh
alice@git-workshop:~/hello_python$ git remote add bob /home/bob/hello_python/
}}}

Ohne zu zögern übernimmt sie Bobs Änderungen:
{{{#!highlight sh
alice@git-workshop:~/hello_python$ git pull bob
remote: Counting objects: 6, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (4/4), done.
From /home/bob/hello_python
 * [new branch]      master     -> bob/master
You asked to pull from the remote 'bob', but did not specify
a branch. Because this is not the default configured remote
for your current branch, you must specify a branch on the command line.
}}}

Git teilt ihr mit, dass es das Repository zwar gäbe, aber dass Alice doch bitte
deutlich sagen soll, von welchem Branch die Änderungen übernommen werden sollen.
Da nur ein Branch in Bobs Repository existiert, gibt sie diesen explizit an:
{{{#!highlight sh
alice@git-workshop:~/hello_python$ git pull bob master
From /home/bob/hello_python
 * branch            master     -> FETCH_HEAD
Updating 19e5e3e..100c5c1
Fast-forward
 .gitignore | 2 ++
 hello.py   | 6 +++++-
 2 files changed, 7 insertions(+), 1 deletion(-)
 create mode 100644 .gitignore
}}}

Da Bobs Änderungen auf denen von Alice aufbauen, wird einfach "vorgespult"
(erkennbar an "Fast-forward"). Jetzt möchte sich Alice wieder die Geschichte
anschauen:
{{{#!highlight sh
alice@git-workshop:~/hello_python$ git log
commit 100c5c12faceca13eb35518cc75b8185d25a94fe
Author: Bob McTest <bob@git-workshop>
Date:   Fri Apr 10 19:57:19 2015 +0000

    Macht hello.py modularer und fügt .gitignore hinzu.

commit 19e5e3ece8832a65cfa8ed2f8c514c14617ffa8d
Author: Alice Testington <alice@git-workshop>
Date:   Fri Apr 10 17:14:47 2015 +0000

    Fügt Shebangzeile hinzu.

commit a9b1eee7a74fae95caa21f488a7d050f0e20d262
Author: Alice Testington <alice@git-workshop>
Date:   Fri Apr 10 16:19:22 2015 +0000

    Fügt hello.py hinzu, welches "Hello, world." ausgibt.
}}}

Tatsächlich wurde Bobs Commit einfach angehängt und er wird sogar als Autor
aufgeführt.

Beim Anblick des Commits kommen Alice ein paar Zweifel. „Warum hat Bob in einem
Commit zwei verschiedene Dateien bearbeitet? Hätte er dafür nicht zwei Commits
machen können? Das wäre doch viel schöner gewesen!“ Alice liebt schöne Dinge und
so dankbar sie Bob für die Verbesserung ist – für eine schöne Commit-History
wäre sie noch dankbarer gewesen.

Sie teilt Bob ihre Bedenken respektvoll aber bestimmt mit und möchte, dass Bob
seine Änderungen nochmal überarbeitet. Währenddessen widerruft sie Bobs
Änderungen in ihrem Repository:
{{{#!highlight sh
alice@git-workshop:~/hello_python$ git reset --hard 19e5e3ece8832a65cfa8ed2f8c514c14617ffa8d
HEAD is now at 19e5e3e Fügt Shebangzeile hinzu.
}}}

Damit macht Alice alle Änderungen von Bob rückgängig und bringt ihr Repository
auf ihren letzten Stand. Ihre Geschichte enthält jetzt – wie zuvor – nur noch
zwei Einträge.

Bob respektiert Alice' Wunsch nach einer sauberen Repositorygeschichte und
widerruft auch seine Änderungen in seinem Repository. Allerdings möchte er die
Änderungen nicht ganz verlieren. Er möchte, dass sein Repository auf dem
vorherigen Stand ist, jedoch sollen seine Änderungen in den Dateien erhalten
bleiben, aber nicht in der staging area sein. Dafür reicht folgendes:
{{{#!highlight sh
bob@git-workshop:~/hello_python$ git reset 19e5e3ece8832a65cfa8ed2f8c514c14617ffa8d
Unstaged changes after reset:
M       hello.py
}}}

Bob überprüft nochmal, ob auch wirklich alles so war, wie vor dem Commit:
{{{#!highlight sh
bob@git-workshop:~/hello_python$ git status
On branch master
Your branch is up-to-date with 'origin/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:   hello.py

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        .gitignore

no changes added to commit (use "git add" and/or "git commit -a")
}}}

„Perfekt!“, denkt sich Bob, „Genau so wie ich es haben wollte. Jetzt kann ich
die Dateien einzeln zur staging area hinzufügen und danach jeweils committen.“
Gedacht, getan:
{{{#!highlight sh
bob@git-workshop:~/hello_python$ git add hello.py
bob@git-workshop:~/hello_python$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   hello.py

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        .gitignore
bob@git-workshop:~/hello_python$ git commit
[master 6e6c421] Macht hello.py modularer.
 1 file changed, 5 insertions(+), 1 deletion(-)
}}}

Danach kümmert er sich noch um {{{.gitignore}}}:
{{{#!highlight sh
bob@git-workshop:~/hello_python$ git add .gitignore
bob@git-workshop:~/hello_python$ git commit
[master 6450c35] Fügt .gitignore hinzu.
 1 file changed, 2 insertions(+)
 create mode 100644 .gitignore
}}}

Er sagt Alice bescheid, dass er ihrem Wunsch nachgekommen ist und jetzt zwei
Commits erstellt hat. Alice bedankt sich lieb bei Bob und zieht seine
Änderungen:
{{{#!highlight sh
alice@git-workshop:~/hello_python$ git pull bob master
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /home/bob/hello_python
 * branch            master     -> FETCH_HEAD
 + 100c5c1...6450c35 master     -> bob/master  (forced update)
Updating 19e5e3e..6450c35
Fast-forward
 .gitignore | 2 ++
 hello.py   | 6 +++++-
 2 files changed, 7 insertions(+), 1 deletion(-)
 create mode 100644 .gitignore
alice@git-workshop:~/hello_python$ git log
commit 6450c350496a4e87cd42e3749673a1eac3800ad8
Author: Bob McTest <bob@git-workshop>
Date:   Fri Apr 10 20:28:32 2015 +0000

    Fügt .gitignore hinzu.

commit 6e6c421cff3027537c63e9df0d56f0464d766881
Author: Bob McTest <bob@git-workshop>
Date:   Fri Apr 10 20:26:39 2015 +0000

    Macht hello.py modularer.

commit 19e5e3ece8832a65cfa8ed2f8c514c14617ffa8d
Author: Alice Testington <alice@git-workshop>
Date:   Fri Apr 10 17:14:47 2015 +0000

    Fügt Shebangzeile hinzu.

commit a9b1eee7a74fae95caa21f488a7d050f0e20d262
Author: Alice Testington <alice@git-workshop>
Date:   Fri Apr 10 16:19:22 2015 +0000

    Fügt hello.py hinzu, welches "Hello, world." ausgibt.
}}}

So ist's Alice recht.

== Alice mag Dateien ==
Alice hat eine Idee: „Wie wäre es, wenn man meinem Programm einen Dateinamen
übergeben könnte, der dann ausgegeben wird? Und wenn kein Dateiname übergeben
wird, dann soll einfach {{{hello()}}} aufgerufen werden. Dank Bobs
Änderungen dürfte das einfach werden.“

Auf geht's:
{{{#!highlight sh
alice@git-workshop:~/hello_python$ vim hello.py
}}}
{{{#!highlight python
#!/usr/bin/env python3

import sys

def hello():
        print("Hello, world.")

if __name__ == '__main__':
        if len(sys.argv) == 2:
                filename = sys.argv[1]
                f = open(filename)
                msg = f.read()
                print(msg)
        else:
                hello()
}}}

Weiterhin erstellt sie zu Testzwecken eine Datei namens {{{hello.txt}}}:
{{{#!highlight sh
alice@git-workshop:~/hello_python$ vim hello.txt
alice@git-workshop:~/hello_python$ cat hello.txt
Hallo, Welt.

Dieser Text stammt aus einer Datei.
}}}

Dann probiert sie ihr geändertes Programm aus:
{{{#!highlight sh
alice@git-workshop:~/hello_python$ ./hello.py
Hello, world.
alice@git-workshop:~/hello_python$ ./hello.py hello.txt
Hallo, Welt.

Dieser Text stammt aus einer Datei.

}}}

„An sich tut es, was es soll. Aber die zusätzliche Leerzeile am Ende stört mich.
Vielleicht kann Bob mir dabei helfen.“ denkt sich Alice. Damit Bob das neue
Feature aber überhaupt sieht, muss Alice die Änderungen erst committen:

{{{#!highlight sh
alice@git-workshop:~/hello_python$ git add hello.py
alice@git-workshop:~/hello_python$ git commit
[master 972cf5c] hello.py kann jetzt auch Dateien lesen.
 1 file changed, 9 insertions(+), 1 deletion(-)
}}}

Jetzt sagt sie Bob bescheid, der sich die Änderungen zieht:
{{{#!highlight sh
bob@git-workshop:~/hello_python$ git pull origin
remote: Counting objects: 7, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /home/alice/hello_python
   19e5e3e..972cf5c  master     -> origin/master
Updating 6450c35..972cf5c
Fast-forward
 hello.py | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)
}}}

Bevor Bob aber seine pythonische Weisheit auf Alice' Programm anwenden kann,
kommt Alice eine neue Idee. „Wie wäre es, wenn das Programm nicht "Hello,
world." ausgäbe, sondern "Hello, alice." oder "Hello, bob." – je nachdem, wie
der Benutzername lautet?“ Im Internet hat Alice auch schon die notwendigen
Zeilen gefunden, die sie braucht. Außerdem hat Alice im Internet auch von den
tollen Möglichkeiten zum Branching gelesen. Das will sie jetzt ausprobieren.

Sie schaut sich erstmal an, welche Branches gerade vorhanden sind:
{{{#!highlight sh
alice@git-workshop:~/hello_python$ git branch
* master
}}}
Nur ein Branch namens {{{master}}}. Für ihre Benutzernamenerweiterung
legt sie einen neuen Branch namens {{{gibt_benutzername_aus}}} an:
{{{#!highlight sh
alice@git-workshop:~/hello_python$ git branch gibt_benutzername_aus
alice@git-workshop:~/hello_python$ git branch
  gibt_benutzername_aus
* master
}}}
Jetzt existieren zwei Branches. Sie wechselt zum neu erstellten Branch:
{{{#!highlight sh
alice@git-workshop:~/hello_python$ git checkout gibt_benutzername_aus
Switched to branch 'gibt_benutzername_aus'
alice@git-workshop:~/hello_python$ git branch
* gibt_benutzername_aus
  master
}}}
Der Stern gibt also an, auf welchem Branch sich Alice gerade befindet.

In diesem Branch ändert sie {{{hello.py}}} und schaut sich danach die
Änderungen an:
{{{#!highlight sh
alice@git-workshop:~/hello_python$ vim hello.py
alice@git-workshop:~/hello_python$ git diff
diff --git a/hello.py b/hello.py
index 570eac1..d11424c 100755
--- a/hello.py
+++ b/hello.py
@@ -1,9 +1,11 @@
 #!/usr/bin/env python3

 import sys
+import getpass

 def hello():
-       print("Hello, world.")
+       username = getpass.getuser()
+       print("Hello, %s." % username)

 if __name__ == '__main__':
        if len(sys.argv) == 2:
}}}
Natürlich probiert sie ihr Programm gleich aus:
{{{#!highlight sh
alice@git-workshop:~/hello_python$ ./hello.py
Hello, alice.
}}}
„Bravo!“, denkt sich Alice. Jetzt muss sie ihre Änderungen nur zur staging area
hinzufügen und committen:
{{{#!highlight sh
alice@git-workshop:~/hello_python$ git add hello.py
alice@git-workshop:~/hello_python$ git commit
[gibt_benutzername_aus 7db7d7e] Gibt den Benutzernamen beim Aufruf von hello() aus.
 1 file changed, 3 insertions(+), 1 deletion(-)
}}}

Dieser Commit ist aber nur im Branch "gibt_benutzername_aus" enthalten. Der
master-Branch kennt die Änderungen (noch) nicht. Dies möchte Alice etwas genauer
inspizieren:
{{{#!highlight sh
alice@git-workshop:~/hello_python$ git branch
* gibt_benutzername_aus
  master
alice@git-workshop:~/hello_python$ git log --oneline
7db7d7e Gibt den Benutzernamen beim Aufruf von hello() aus.
972cf5c hello.py kann jetzt auch Dateien lesen.
6450c35 Fügt .gitignore hinzu.
6e6c421 Macht hello.py modularer.
19e5e3e Fügt Shebangzeile hinzu.
a9b1eee Fügt hello.py hinzu, welches "Hello, world." ausgibt.
alice@git-workshop:~/hello_python$ git checkout master
Switched to branch 'master'
alice@git-workshop:~/hello_python$ git branch
  gibt_benutzername_aus
* master
alice@git-workshop:~/hello_python$ git log --oneline
972cf5c hello.py kann jetzt auch Dateien lesen.
6450c35 Fügt .gitignore hinzu.
6e6c421 Macht hello.py modularer.
19e5e3e Fügt Shebangzeile hinzu.
a9b1eee Fügt hello.py hinzu, welches "Hello, world." ausgibt.
}}}

Jetzt hat Alice den Überblick über ihre Branches und die darin enthaltenen
Commits. Aus dem master-Branch heraus übernimmt sie den Commit aus dem
gibt_benutzername_aus-Branch:
{{{#!highlight sh
alice@git-workshop:~/hello_python$ git branch
  gibt_benutzername_aus
* master
alice@git-workshop:~/hello_python$ git merge gibt_benutzername_aus
Updating 972cf5c..7db7d7e
Fast-forward
 hello.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
alice@git-workshop:~/hello_python$ git log --oneline
7db7d7e Gibt den Benutzernamen beim Aufruf von hello() aus.
972cf5c hello.py kann jetzt auch Dateien lesen.
6450c35 Fügt .gitignore hinzu.
6e6c421 Macht hello.py modularer.
19e5e3e Fügt Shebangzeile hinzu.
a9b1eee Fügt hello.py hinzu, welches "Hello, world." ausgibt.
}}}

Jetzt enthält auch der master-Branch den Commit {{{7db7d7e}}}. Sie freut
sich, dass man die SHA1-Werte auch verkürzt ansehen und benutzen kann.

== Währenddessen bei Bob ==
Bob konnte sein anderes Projekt glücklicherweise zur Deadline fertigstellen und
wendet sich wieder Alice' Programmiereifer zu. Beim Durchsehen bemerkt er ein
paar Verbesserungsmöglichkeiten:
{{{#!highlight sh
bob@git-workshop:~/hello_python$ vim hello.py
}}}
{{{#!highlight python
#!/usr/bin/env python3

import sys

def hello():
        print("Hello, world.")

if __name__ == '__main__':
        if len(sys.argv) == 2:
                try:
                        filename = sys.argv[1]
                        with open(filename) as f:
                                msg = f.read().strip()
                                print(msg)
                except:
                        print("Fehler: konnte %s nicht öffnen!" % filename)
        else:
                hello()
}}}
Wohlgemerkt weiß Bob noch nichts von Alice' Benutzernamenverbesserung. Sein
Programm funktioniert aber ganz gut:
{{{#!highlight sh
bob@git-workshop:~/hello_python$ ./hello.py
Hello, world.
bob@git-workshop:~/hello_python$ ./hello.py hello.txt
Fehler: konnte hello.txt nicht öffnen!
bob@git-workshop:~/hello_python$ echo "Dies ist eine Datei." > hello.txt
bob@git-workshop:~/hello_python$ ./hello.py hello.txt
Dies ist eine Datei.
}}}

Beim Überprüfen des Status' fällt ihm auf, dass {{{hello.txt}}} jetzt
auch von git bemerkt wird:
{{{#!highlight sh
bob@git-workshop:~/hello_python$ git status
On branch master
Your branch is up-to-date with 'origin/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:   hello.py

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        hello.txt

no changes added to commit (use "git add" and/or "git commit -a")
bob@git-workshop:~/hello_python$ echo '*.txt' >> .gitignore
bob@git-workshop:~/hello_python$ git status
On branch master
Your branch is up-to-date with 'origin/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:   .gitignore
        modified:   hello.py

no changes added to commit (use "git add" and/or "git commit -a")
}}}

Da Bob weiß wie wichtig Alice' ihre Commits sind, legt er für jede Datei einen
Commit an:
{{{#!highlight sh
bob@git-workshop:~/hello_python$ git add hello.py
bob@git-workshop:~/hello_python$ git commit
[master d79b3aa] Verbessert hello.py.
 1 file changed, 7 insertions(+), 4 deletions(-)
bob@git-workshop:~/hello_python$ git add .gitignore
bob@git-workshop:~/hello_python$ git commit
[master 8615c04] Alle Dateien mit *.txt werden von jetzt an ignoriert.
 1 file changed, 1 insertion(+)
}}}

Bob sagt Alice bescheid, dass er ihr Programm verbessert hat und sie seine
Änderungen ziehen kann.

== Zurück zu Alice ==
Alice freut sich sehr über die Nachricht von Bob und versucht seine Commits in
ihr Repository einzupflegen:
{{{#!highlight sh
alice@git-workshop:~/hello_python$ git pull bob master
From /home/bob/hello_python
 * branch            master     -> FETCH_HEAD
Auto-merging hello.py
Merge made by the 'recursive' strategy.
 .gitignore |  1 +
 hello.py   | 11 +++++++----
 2 files changed, 8 insertions(+), 4 deletions(-)
}}}
Es öffnet sich ihr Lieblingseditor und bittet sie eine commit message einzugeben. Es steht schon folgendes drin:
{{{#!highlight sh
Merge branch 'master' of /home/bob/hello_python
}}}

„Wird schon seine Richtigkeit haben.“, denkt sich Alice. Sie speicherte und
schloss die Datei. Neugierig wie jetzt wohl ihre Repositorygeschichte aussehen
würde, probiert sie folgendes aus:
{{{#!highlight sh
alice@git-workshop:~/hello_python$ git log --oneline --graph
*   5621ae2 Merge branch 'master' of /home/bob/hello_python
|\
| * 8615c04 Alle Dateien mit *.txt werden von jetzt an ignoriert.
| * d79b3aa Verbessert hello.py.
* | 7db7d7e Gibt den Benutzernamen beim Aufruf von hello() aus.
|/
* 972cf5c hello.py kann jetzt auch Dateien lesen.
* 6450c35 Fügt .gitignore hinzu.
* 6e6c421 Macht hello.py modularer.
* 19e5e3e Fügt Shebangzeile hinzu.
* a9b1eee Fügt hello.py hinzu, welches "Hello, world." ausgibt.
}}}
„Ahja“, denkt sich Alice, „jetzt kann man genau sehen, wo sich unsere
Geschichte verzweigte und wo sie wieder zusammengeführt wurde.“ Jedoch behagte
es ihr nicht recht. Könnte man die Commits von Bob nicht so einpflegen, dass sie
alle nacheinander angeordnet würden?

Zunächst verwirft sie die eben gezogenen Commits von Bob und spult die
Geschichte auf Alice' letzten Stand zurück:
{{{#!highlight sh
alice@git-workshop:~/hello_python$ git reset --hard 7db7d7e
HEAD is now at 7db7d7e Gibt den Benutzernamen beim Aufruf von hello() aus.
alice@git-workshop:~/hello_python$ git log --oneline --graph
* 7db7d7e Gibt den Benutzernamen beim Aufruf von hello() aus.
* 972cf5c hello.py kann jetzt auch Dateien lesen.
* 6450c35 Fügt .gitignore hinzu.
* 6e6c421 Macht hello.py modularer.
* 19e5e3e Fügt Shebangzeile hinzu.
* a9b1eee Fügt hello.py hinzu, welches "Hello, world." ausgibt.
}}}

Dann wendet sie die Option {{{--rebase}}} beim Ziehen von Bobs Commits an
und schaut sich wieder die Geschichte mit dazugehörigem Graphen an:
{{{#!highlight sh
alice@git-workshop:~/hello_python$ git pull --rebase bob master
From /home/bob/hello_python
 * branch            master     -> FETCH_HEAD
First, rewinding head to replay your work on top of it...
Applying: Gibt den Benutzernamen beim Aufruf von hello() aus.
alice@git-workshop:~/hello_python$ git log --oneline --graph
* ed605ec Gibt den Benutzernamen beim Aufruf von hello() aus.
* 8615c04 Alle Dateien mit *.txt werden von jetzt an ignoriert.
* d79b3aa Verbessert hello.py.
* 972cf5c hello.py kann jetzt auch Dateien lesen.
* 6450c35 Fügt .gitignore hinzu.
* 6e6c421 Macht hello.py modularer.
* 19e5e3e Fügt Shebangzeile hinzu.
* a9b1eee Fügt hello.py hinzu, welches "Hello, world." ausgibt.
}}}

So ist's Alice recht.

= Cheatsheet =
Ein Cheatsheet mit den gängigsten Befehlen findet ihr unter
[[attachment:git-cheatsheet.pdf]].

= Eigenarbeit =
Finde dich in einem Team mit insgesamt maximal 3 Mitgliedern zusammen.

Entwickelt zusammen ein Trainingsprogramm für arithmetische Aufgaben
(Grundrechenarten). Dabei sollen nur ganzzahlige Operanden vorkommen. Dem Nutzer
soll es ermöglicht werden, die Anzahl und Art der gestellten Aufgaben zu
bestimmen (sprich: wie viele Aufgaben und was für Grundrechenarten). Dabei soll
die Anzahl der richtigen Ergebnisse gezählt und dem Nutzer am Ende eine kurze
Statistik präsentiert werden.

Falls Fragen oder Unklarheiten aufkommen, dann versucht im Team einen Konsens
über die eurer Meinung nach sinnvollste Lösung zu finden und fragt erst dann.
Nutzt git und das interne [[https://git.hack-hro.de|GitLab]] für die
Zusammenarbeit. Die Wahl der Programmiersprache und der Benutzerschnittstelle
sind euch freigestellt.

Ich wäre sehr erfreut, wenn ein Teammitglied das Programm und die Zusammenarbeit
im Team am Ende vorstellt.

= Feedback =
Bevor ihr geht, beantwortet bitte noch schnell
[[http://goo.gl/forms/2nikbRT5Da|diese 4+1 Fragen]]. Danke!