---
# System prepended metadata

title: Git-Einführung

---

---

type: slide

progress: true
keyboard: true
defaultTiming: 0

[//]: # doc: https://hackmd.io/s/how-to-create-slide-deck

---

# Git-Einführung

Geschichte, Theorie, Basic Workflow

---

## Versionskontrollsysteme

Ziel: Änderungen nachvollziehbar abspeichern

Vorgänger von Git:
1. Subversion (2000)
2. CVS (1990)
3. RCS (1982)

---

### Zentral vs. Dezentralisiert

- Die Vorgänger sind zentralisierte Systeme, d.h. sie brauchen einen Server. 
  Ohne Server bzw. Datenverbindung können keine Änderungen abgespeichert werden.

- Git arbeitet per se ohne Server bzw. **lokal**. Also kann ohne Netzwerk auch versioniert werden.
  Wenn man wieder online ist, kann man den Stand synchronisieren.

---

## Theorie

- Git verwaltet alle Änderungen in einer Datei-basierten Datenbank. Jede Änderung ist eine
  signierte Differenz (diff datei_neu datei_alt | sha256sum), welche in Abhängigkeit zu ihrem Vorgänger steht.

- Dadurch kann die komplette Historie einer Datei nachvollzogen werden, indem jeweils der Stand t zu t - 1 aus der Datenbank gezogen wurde.

---

Das git command, um die Historie anzuzeigen lautet:

   ```git log [-p (Änderungen als Differenz anzeigen)] -- mein_pfad (Nur Änderungen betreffend diesem Pfad)```

- Damit können wir sehen, welche Patches bzw. Differenzen im Laufe der Zeit eingespielt wurden, und von wem und wann.
- Einen in Git eingetragenen Patch nennen wir commit (engl. bestätigen).

---

- Die Historie von Commits speichert man überlicherweise auf einem Branch ab.
- Ein Branch ist nichts anderes als eine (nicht-lineare) Sammlung von Commits. Jeder Branch hat einen eindeutigen Namen.
- Laut Github Konvention ist der Name des Standardbranches "main" (früher "master").

---

### Datenbank / "Working copy"

- Die eingangs erwähnte Datenbank auf Dateibasis kann von jedem Client kopiert werden. Damit hat jeder User eine komplette Kopie der gesamten Historie. Da die Historie signiert ist, kann man diese nicht einfach so manipulieren,
  ohne dass dies auffällt.
- Es ist also ein Sicherheitskonzept, welches die Integrität des Verlaufs sichert.

---

- So kann jeder auch davon ausgehen, dass seine Änderungen zum Zeitpunkt t kompatibel sind.
- Auch zum Zeitpunkt *t + n* (sagen wir *n* Änderungen in der Zukunft) kann Git wieder zurück zum Zeitpunkt t springen,
  um die Änderungen von einem User wieder nachvollziehbar zu machen.

---

### Objekte in Git 
(nur für den Hinterkopf)

![Commit graph](https://i.imgur.com/E9qy7Jy.png)
- Die kleinste Einheit hier ist der "Blob", was dem Inhalt einer Datei bzw. einer Änderung enthält.
- Ein Baum (engl. "tree") verweist auf einen oder mehrere Blobs, um Änderungen
  an mehreren Dateien zusammen zu fassen.
- Die nächst höhere Einheit ist der Commit, welcher auf einen Baum verweißt.
  Dem Commit sind die Daten zum Autor (Name, E-Mail) und Zeitstempel zugeordnet.
- Die Commits können zu einem, keinem (oh-oh) oder mehreren "Branches" gehören.

---

### Gut und was bringt uns das jetzt?
- Alice und Bob arbeiten beide an dem selben Projekt und haben jeweils ihre eigene Git Kopie der Projektdateien. Bob fährt zwei Wochen in den Urlaub und Alice arbeitet fleißig weiter.

- Als Bob aus dem Urlaub kommt (*t + n*) sind alle seine Änderungen, die er vor dem Urlaub nicht mit Alice
  synchronisiert hat auf der Basis von *t*.

---

#### Bob holt auf...

- Nun möchte er seine Historie auf die Basis von Alice Arbeit heben,
  um der Basis ihrer Änderungen weiter zu arbeiten.

---

#### Bob holt auf... continued

- Er aktualisiert seine Datenbank/Repository/Working-Copy mit dem Stand von Alice und führt einen **"Merge"** per Git durch.
- Da seine Arbeit und die von Alice die selbe Historie teilen, kann Git **automatisch** (außer bei Konflikten, dh. beide haben widersprüchliche Änderungen durch geführt)
  diesen sonst aufwendigen Arbeitsschritt der Synchronisation vollziehen.

---

## Vorgehensweise / Workflow

1. Repository/lokales Git-Verzeichnis erstellen

```
   git init my_repo  # erzeugen
   cd my_repo # in das neue Verzeichnis wechseln
   git checkout -b main  # Neuen Branch namens "main" erzeugen und auf diesen wechseln.
```

---

2. Dateien erstellen/modifizieren/Änderungen eintragen

- Hinzufügen von Dateien:
```
   git add my_file.txt  # Datei "my_file.txt" zum **Index** hinzufügen, das kann beliebig oft mit anderen Dateien gemacht werden.
```

- Eintragen ins Logbuch:
```
   git commit -m "First import"  # Den aktuellen Index mit dem Logbucheintrag "First import" bestätigen
```
Damit haben wir die Änderungen an der Datei my_file.txt auf den main branch eingetragen. Commit zu deutsch: "bestätigen".

Im späteren Verlauf merken wir, dass wir da einen Fehler gemacht haben und wollen (auch wenn wir schon mehrere Commits später angelangt sind),
die Änderung, die den Fehler eingefügt hat rückgängig machen.

---

- Rückgängig machen:

```
   git log  # Wir schauen ins Logbuch, welche Änderung das war und kopieren die Logbuchnummer bzw. Hash 
   git revert $logbuch_nummer  # Erzeugt einen neuen Commit, welcher die Änderungen des vorherigen Commits mit der angegeben Nummer **rückgängig** macht.
```

---

- Tricks:
Mehrere Dateien sind schon auf dem Index, eine davon wieder löschen (z.B. wenn es sinnvoller ist, das in zwei Commits aufzuteilen). Das ist der Gegenpart zu "git add"
```
git add my_file.txt myfile_2.txt
git reset my_file2.txt  # Datei "my_file2.txt" wieder vom Index löschen. Behält aber Änderungen in dieser Datei!
git status  # Ansehen, welche Dateien jetzt wirklich committed werden. 
git commit -m "Updated my_file.txt"
```

---

3. Arbeiten mit **Branches**
- Wechsele zu Branch "branch_name".
  ```
  git checkout branch_name  
  ```
- Zeige Logbuch für "branch_name".
  ```
  git log branch_name
  ```

- Füge Änderungen von Alice Branch ein.
    ```
    git merge alice_branch  
    ```

---


4. Synchronisation der Lokalen Kopie mit einem Server

- Runterladen:
 Ziehe Änderungen von Server "origin" in lokalen Branch.
```git pull origin branch_name```

- Hochladen:
Lade Änderungen von branch auf den Server "origin".
```git push origin branch_name  ```

---

### Was ist "origin"?
Wenn man eine Git-Kopie mit "git clone" von einem Server geladen hat, wird automatisch
der Name dieses Server als "origin" (deutsch: Ursprung) angelegt.

Möchte man einen Server für das Veröffentlichen/Synchronisieren anlegen, kann man dies mit dem Kommando
```
   git remote add myfork https://github.com/marscher/fancy_project.git
```
erledigen. Hier wird unter dem Namen "myfork" (wie auch "origin") ein neuer Server angelegt, auf den
push und pull Änderungen ausgetauscht werden können.

---

## Best practices
- Write **meaningful** commit-**messages**
  Kurze prägnante Logbucheinträge (bei git commit). Das macht nicht nur anderen Mitarbeitern das Leben einfacher,
  sondern in aller Linie einem selbst, wenn man nach ein paar Wochen nachvollziehen muss, was man getan hat.

- Commit **early**, commit **often**
  Es fällt leichter Logbucheinträge zu schreiben, welche eine zusammenhängende Änderung beschreiben, als
  am Ende des Arbeitstags einmal alles zu commiten (und zu beschreiben).

---

- **Don't alter published** history. ...
  Wenn die Änderungen auf einen Server geladen wurden, ist an der Historie nicht mehr zu rütteln.
  Technisch wäre das möglich, macht aber die Historie inkompatibel mit den anderen Kopien und
  sollte deshalb vermieden werden.

- **Don't commit generated files**.
  Wenn die Ausgabe generiert wurde (durch ein Program), gehört das generierende Script in die Git-Verwaltung
  und nicht die Aussgabe selbst. Das hält die Datenbank (und das Logbuch) schmal und erleichtert das Arbeiten.

---

- **Don't panic**
  Sobald die Änderungen in Git bestätigt bzw. committed sind, ist es schwer
  bis nahezu unmöglich diese wieder kaputt zu machen. Das heißt, selbst wenn
  mal etwas nicht wie gewohnt funktioniert, ist die Arbeit nicht fort,
  sondern kann wieder hergestellt werden (@marscher fragen).
  
---

## Jupyter notebooks versionieren

Am besten immer den Output löschen **VOR** commit. 


---

## Merge-Konflikte

- Ein merge-Konflikt entsteht, wenn es zwei sich widersprechende Änderungen gibt (Überschrift des Dokuments von Alice anders benannt als von Bob).
- Git kann nicht automatisch entscheiden, welche Version übernommen werden soll.
- Für das manuelle Beheben der Konflikte gibt git aber Hilfestellung.

---

### Merge-Konflikte beheben

- Empfohlene Vorgehensweise: Drei-Wege Diff / Mergetool verwenden. Dieses hat drei Unterfenster für die Änderungen von Local, Remote und was der User aus beiden herausgezogen hat.
- [Meld auf Windows/Linux einrichten](https://stackoverflow.com/a/48979939)
- Beispieltool Meld: ![](https://meldmerge.org/images/meld-merge-full.png =x300)

---

### Merge-Konflikte beheben (Beispiel)

1. Wir ziehen widersprüchliche Änderungen 
  `git pull origin main`
1. Beheben der Konflikte lokal (z.B mit Meld). Dies arbeitet eine Datei nach der anderen ab.
 `git mergetool`
1. Merge commit anlegen
  `git commit -m "merge origin/main (resolved following conflicts)"`
1. Wieder hochladen
 `git push origin main`

---

## Resourcen
1. [Cheat-Sheet](https://education.github.com/git-cheat-sheet-education.pdf)
2. [Offizielle Git-Referenz](https://git-scm.com/docs)
3. [Stack-Overflow, Git Thema](https://stackoverflow.com/search?q=%5Bgit%5D)
4. [Meld auf Windows einrichten](https://stackoverflow.com/a/48979939)

---

Danke für eure Aufmerksamkeit! 
Jetzt etwas Praxis :-)
