#language de #format wiki || <> || {{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 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 ..." to update what will be committed) (use "git checkout -- ..." 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 Date: Fri Apr 10 17:14:47 2015 +0000 Fügt Shebangzeile hinzu. commit a9b1eee7a74fae95caa21f488a7d050f0e20d262 Author: Alice Testington 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 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 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 | Date: Fri Apr 10 17:14:47 2015 +0000 | | Fügt Shebangzeile hinzu. | * commit a9b1eee7a74fae95caa21f488a7d050f0e20d262 Author: Alice Testington 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 ..." to update what will be committed) (use "git checkout -- ..." to discard changes in working directory) modified: hello.py Untracked files: (use "git add ..." 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 ..." to update what will be committed) (use "git checkout -- ..." to discard changes in working directory) modified: hello.py Untracked files: (use "git add ..." 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 ..." 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 Date: Fri Apr 10 19:57:19 2015 +0000 Macht hello.py modularer und fügt .gitignore hinzu. commit 19e5e3ece8832a65cfa8ed2f8c514c14617ffa8d Author: Alice Testington Date: Fri Apr 10 17:14:47 2015 +0000 Fügt Shebangzeile hinzu. commit a9b1eee7a74fae95caa21f488a7d050f0e20d262 Author: Alice Testington 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 ..." to update what will be committed) (use "git checkout -- ..." to discard changes in working directory) modified: hello.py Untracked files: (use "git add ..." 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 ..." to unstage) modified: hello.py Untracked files: (use "git add ..." 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 Date: Fri Apr 10 20:28:32 2015 +0000 Fügt .gitignore hinzu. commit 6e6c421cff3027537c63e9df0d56f0464d766881 Author: Bob McTest Date: Fri Apr 10 20:26:39 2015 +0000 Macht hello.py modularer. commit 19e5e3ece8832a65cfa8ed2f8c514c14617ffa8d Author: Alice Testington Date: Fri Apr 10 17:14:47 2015 +0000 Fügt Shebangzeile hinzu. commit a9b1eee7a74fae95caa21f488a7d050f0e20d262 Author: Alice Testington 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 ..." to update what will be committed) (use "git checkout -- ..." to discard changes in working directory) modified: hello.py Untracked files: (use "git add ..." 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 ..." to update what will be committed) (use "git checkout -- ..." 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!