2.2 Git Basics

Git kann auf unterschiedliche Arten eingesetzt werden. Es gibt graphische Oberflächen und die meisten Git-Server Software bieten Weboberflächen, welche das interagieren mit Git Repositories kinderleicht gestalten. Da diese Oberflächen meist sehr Produktspezifisch sind und oft auch nicht alle Funktionen beinhalten, werden hier nur die Kommandozeilen Befehle angeschaut.

Inhalt

Konfiguration von Git

Git kann an verschiedenen Orten konfiguriert werden:

  • /etc/gitconfig enthält die Werte, welche für alle Benutzer auf dem System gelten. Die Datei kann mit git config --system gelesen oder editiert werden. Man braucht Administrator- oder Superuser-Rechte um die Datei zu editieren.
  • ~/.gitconfig oder ~/.config/git/config enthält die Werte, welche für den aktuellen Benutzer konfiguriert sind. Mit git config --global können Werte gesetzt oder ausgelesen werden.
  • Die Datei config im .git Verzeichnis innerhalb eines Repositories enthält die Konfiguration, welche nur für dieses Repository zählt. Das Flag für git config ist in diesem Fall --local, ist jedoch in der Regel die Standardoption und kann weggelassen werden.

Jede dieser Konfigurationen wird von der nächsten überschrieben, sodass die Repository bezogene Konfiguration den höchsten Wert hat. Um zu die aktuelle Konfiguration und ihren Ursprung anzusehen kann folgender Befehl ausgeführt werden:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$ git config --list --show-origin
file:/home/zwiseli/.gitconfig     user.email=zwiseli@puzzle.ch
file:/home/zwiseli/.gitconfig     user.name=Zwiseli Doerex
file:/home/zwiseli/.gitconfig     push.default=simple
file:/home/zwiseli/.gitconfig     merge.tool=meld
file:.git/config        core.repositoryformatversion=0
file:.git/config        core.filemode=true
file:.git/config        core.bare=false
file:.git/config        core.logallrefupdates=true
file:.git/config        remote.origin.url=git@ssh.gitlab.puzzle.ch:pitc_sys/sys-labs.git
file:.git/config        remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
file:.git/config        branch.master.remote=origin
file:.git/config        branch.master.merge=refs/heads/master

Konfigurationsbeispiele

Das wichtigste ist der Name und die Email Adresse, diese Angaben werden an jeden Commit angehängt:

1
2
$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com

Wie wir bereits gelernt haben, schreiben wir mit dem --global Flag in die Konfiguration in unserem home Verzeichnis und sie gilt somit für alle Repositories, welche wir mit unserem User bearbeiten. Wollen wir dies fur ein bestimmtes Repo übersteuern, haben wir immer noch die Möglichkeit dies mit --local zu machen.

Eine weitere Konfiguration die evtl. hilfreich sein kann ist core.editor mit welcher der Texteditor definiert werden kann. Git benutzt, falls hier nichts definiert ist, den Standard-Editor des Systems.

Hilfe

Git bietet mehrere Möglichkeit, wie man an Hilfe gelangt, wenn man einmal nicht weiter weiss:

1
2
3
4
5
$ git help <verb>
$ git <verb> --help
$ man git-<verb>
# Kurzversion:
$ git <verb> -h

Wenn man also nicht mehr genau weiss, wie dies mit der Konfiguration von Git funktioniert kann man sich folgendermassen weiterhelfen:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
$ git help config #ruft die Manpage auf
[..]
$ git config -h
usage: git config [<options>]

Config file location
    --global              use global config file
    --system              use system config file
    --local               use repository config file
    --worktree            use per-worktree config file
    -f, --file <file>     use given config file
    --blob <blob-id>      read config from given blob object

Action
    --get                 get value: name [value-regex]
    --get-all             get all values: key [value-regex]
    --get-regexp          get values for regexp: name-regex [value-regex]
    --get-urlmatch        get value specific for the URL: section[.var] URL
    --replace-all         replace all matching variables: name value [value_regex]
    --add                 add a new variable: name value
    --unset               remove a variable: name [value-regex]
    --unset-all           remove all matches: name [value-regex]
    --rename-section      rename section: old-name new-name
    --remove-section      remove a section: name
    -l, --list            list all
    -e, --edit            open an editor
    --get-color           find the color configured: slot [default]
    --get-colorbool       find the color setting: slot [stdout-is-tty]

Type
    -t, --type <>         value is given this type
    --bool                value is "true" or "false"
    --int                 value is decimal number
    --bool-or-int         value is --bool or --int
    --path                value is a path (file or directory name)
    --expiry-date         value is an expiry date

Other
    -z, --null            terminate values with NUL byte
    --name-only           show variable names only
    --includes            respect include directives on lookup
    --show-origin         show origin of config (file, standard input, blob, command line)
    --show-scope          show scope of config (worktree, local, global, system, command)
    --default <value>     with --get, use default value when missing entry

Ein Git Repository anlegen

Um lokal ein neues Git Repository anzulegen gibt es zwei Varianten:

  • Umwandeln eines Verzeichnis, welches (noch) nicht mit Git verwaltet wird, zu einem Git Repository
  • Klonen eines existierenden Repositories von einem anderen Ort

Ein Verzeichnis zu einem Git Repository verwandeln

Um aus einem Ordner ein Git Repository zu erstellen, muss man zuerst in den Ordner wechseln und anschliessend das Repository initialisieren:

1
2
$ cd ~/path/to/folder
$ git init

Der zweite Befehl erzeugt ein .git Unterverzeichnis, welches die Repository Daten beinhaltet. Im Moment werden noch keine Dateien von Git verwaltet, diese müssen zuerst hinzugefügt (git add) und dann committed werden (git commit). Wenn wir von einem leeren Ordner aus gehen, müssen die Dateien natürlich zuerst noch erstellt werden:

1
2
3
$ vim README.md
$ git add README.md
$ git commit -m 'Initial Commit'

Ein bestehendes Repository klonen

Um ein Repository zu klonen wird der Befehl git clone verwendet:

1
2
3
$ git clone https://github.com/libgit2/libgit2
$ ls
libgit2

Git legt dann automatisch ein Verzeichnis libgit2 an mit allen Dateien und initialisiert das Repository mit einem .git Verzeichnis im Hauptverzeichnis. Wird hinter der URL noch etwas angegeben, wird dies als Name des Zielverzeichnisses interpretiert:

1
2
3
$ git clone https://github.com/libgit2/libgit2 mylibgit
$ ls
mylibgit

Änderungen verfolgen und im Repository speichern

Zur Erinnerung, eine Datei in einem Git Folder kann vier Zustände haben:

  • Untracked: Die Datei liegt im Ordner, wird aber nicht von Git versioniert
  • Unmodified: Die Datei wird von Git versioniert, sie ist seit dem letzten Commit unverändert.
  • Modified: Die Datei wurde seit dem letzten Commit verändert
  • Staged: eine neue oder editierte Datei wurde für den nächsten Commit vorgemerkt.

Der Befehl zum überprüfen, ob man Dateien in einem anderen Zustand als unmodified hat ist git status:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
$  git status
On branch feature/dasmitdiesemgit
Your branch is up to date with 'origin/feature/dasmitdiesemgit'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   2_git/README.md

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   2_git/2_basic/README.md

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

Wollen wir nun die Änderungen der Datei 2_git/2_basic/README.md (oder die Datei .gitignore) zum nächsten Commit hinzufügen, also stagen, können wir dies mit git add <pfad/zur/datei>. Im Beispiel oben sehen wir, dass die Datei 2_git/README.md bereits gestaged ist. Ändern wir diese Datei erneut, wird die Datei sowohl bei Changes to be committed: wie auch bei Changes not staged for commit: auftauchen. Einmal mit den Änderungen, welche wir bereits gestaged haben und einmal mit den Änderungen, welche wir nach dem Stagen (git add) gemacht haben. Wollen wir beide Änderungen im gleichen Commit, können wir die Datei einfach wieder mit git add stagen und anschliessend commiten. Wollen wir stattdessen die Änderungen in separaten Commits, commiten wir zuerst die schon gestagten Änderungen und stagen und committen anschliessend die zweiten Änderungen.

.gitignore

Bevor wir lernen, wie man seine Änderungen commited, wollen wir noch kurz die spezielle Datei .gitignore anschauen. In dieser Datei können Dateien erfasst werden, welche Git nicht als untracked aufzählen soll, respektive ignorieren soll. Die Datei wird ganz normal wie jede andere auch ins Repo eingecheckt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
$ cat .gitignore
# ignore all .a files
*.a

# but do track lib.a, even though you're ignoring .a files above
!lib.a

# only ignore the TODO file in the current directory, not subdir/TODO
/TODO

# ignore all files in any directory named build
build/

# ignore doc/notes.txt, but not doc/server/arch.txt
doc/*.txt

# ignore all .pdf files in the doc/ directory and any of its subdirectories
doc/**/*.pdf

Weitere Informationen bekommt man mit man gitignore oder unter https://github.com/github/gitignore findet man nützliche Beispiele.

Überprüfen der Änderungen

Bevor man seine Änderungen commited, empfiehlt es sich, diese nochmals zu überprüfen. Mit git status sieht man, welche Dateien geändert wurden, jedoch nicht, was sich geändert hat. Dafür gibt es den Befehl git diff:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
diff --git a/2_git/2_basic/README.md b/2_git/2_basic/README.md
index 1e6db46..dd7161b 100644
--- a/2_git/2_basic/README.md
+++ b/2_git/2_basic/README.md
@@ -145,3 +145,60 @@ mylibgit

 ## Änderungen verfolgen und im Repository speichern

+Zur Erinnerung, eine Datei in einem Git Folder kann vier Zustände haben:
-Zur Erinnerung, eine Datei in einem Git Folder kann vier Zustaende haben

git diff vergleicht die Änderungen welche noch nicht gestaged wurden. Will man bereits gestagte Änderungen überprüfen braucht es zusätzlich das Flag --staged oder --cached (die beiden Flags sind Synonyme).

Commiten

Ist man sicher, dass man nur die Änderungen gestaged hat, welche man auch commiten will, kann man dies mit dem Befehl git commit bewerkstelligen. Dadurch wird ein Editor geöffnet und man wird gebeten eine Beschreibung für die Änderungen anzugeben. Darüber was in eine “Commit Message” gehört, gibt es verschiedene Meinungen. Idealerweise probiert man sich vorzustellen, welche Informationen man braucht damit man die Änderungen die man gemacht hat zwei Jahre später immer noch versteht, mindestens aber was und warum das geändert wurde.

Ein paar hilfreiche Flags zu git commit:

  • -m um eine Message gleich anzugeben und nicht den Editor zu öffnen (kann je nach Commit Message Guideline hinderlich sein, da es keine Multiline Kommentare erlaubt):
1
2
3
4
git commit -m "Story 182: added important Information to Readme"
[master 463dc4f] Story 182: added important Information to Readme
 2 files changed, 2 insertions(+)
 create mode 100644 README
  • -a um alle editierten Dateien gleich mitzustagen. (Kurzform von git add --all && git commit) !ACHTUNG! kann gefährlich sein, da so schnell Änderungen in einen Commit rutschen, die nicht rein gehören!

  • -v um ein git diff --staged im Editor angezeigt zu bekommen. So muss man sich nicht merken, was man eigentlich geändert hat und bekommt es beim schreiben der Commit Message noch einmal präsentiert.

WICHTIG: Es ist einiges einfacher Fehler vor dem Commiten als nach dem Commiten zu beheben, ein zweites mal über die Änderungen schauen ist sehr empfehlenswert!

Dateien löschen

Um eine Datei zu löschen, muss diese zuerst entfernt und dann gestaged werden, damit dies in der Git Datenbank ankommt. Um nicht zuerst rm foo und dann git add foo eingeben zu müssen, gibt es den Befehl git rm foo. Will man eine bereits gestagte Datei aus der Versionsverwaltung jedoch nicht vom Filesystem löschen, weil man diese zum Beispiel im .gitignore ergänzen möchte, dann kann man dies mit git rm --cached foo erledigen.

WICHTIG: Jede Datei die irgendwann mal commited wurde ist für immer in der Git Geschichte und kann nur durch neu schreiben der selbigen wieder entfernt werden. Wer also zum Beispiel sein Passwort commited, weil dies in einer secret.yml Datei steht, der sollte sich sofort bei einem 10x-Git-Profi-Engineer Hilfe holen.

Dateien Verschieben

Genauso wie löschen, muss auch beim Verschieben die Änderung Git mitgeteilt werden: mv README.md README && git rm README.md && git add README oder in kurz und hübsch: git mv README.md README.

Anzeigen der Commit Historie

Um die Geschichte eines Git Repositories anzuzeigen gibt es den Befehl git log. Ohne Argumente zeigt git log die Commits in umgekehrter chronologischer Reihenfolge, sprich jüngster Commit zuoberst. Pro Commit wird jeweils der Commit-Hash, der Name und Email Adresse des Authors, das Datum und die Commit-Message angezeigt. Um die effektiven Änderungen anzuzeigen gibt es das -p oder --patch Flag. Da der Output schnell gross wird und man wahrscheinlich auch nicht bis an den Ursprung der Geschichte zurück will, empfiehlt es sich die Anzahl Commits anzugeben. Will man zum Beispiel die letzten drei Commits anschauen macht man dies mit -3. Oder man schränkt die Änderungen basierend auf die Zeit ein mit --since= und --until= ein (es werden die unterschiedlichsten Zeitangaben akzeptiert, am besten probiert man etwas aus. Bsp: --since=2.weeks oder --until="2020-06-03"). Auch mit --grep oder --author kann man die Resultate einschränken. Weitere Information liefert die Manpage man git log und die Hilfefunktion git log -h.

Änderungen rückgängig machen

Wo gehobelt wird fallen Späne, die meisten Schnitzer kann man jedoch selber wieder ausglätten. Am häufigsten committed man wohl zu schnell, hat eine Datei vergessen dazuzufügen oder hat sich bei der Commit Message vertan. Mit git commit --amend kann man den letzten Commit editiern (genaugenommen nicht editieren. Man löscht ihn nur und erstellt eine korrigierte neue Version davon). Dies kann dazuführen, dass ein Remote den Commit nicht mehr annimmt, da die History nicht mehr übereinstimmt. Aber mehr dazu später.

Um eine gestagte Datei wieder zu modified (aber eben nicht gestaged) verschieben, kann man git reset HEAD <file> verwenden. Um die Änderungen einer Datei rückgängig zu machen (also modified zu unmodified) kann sie neu ausgecheckt werden mit git checkout -- <file>. Achtung: Alle Änderungen seit dem letzten Commit gehen damit verloren!!! Wenn man sich an die beiden Befehle gerade mal nicht erinnert, dann kann man einfach ein git status eingeben und den Hilfetext studieren 😉.

Mit Remotes arbeiten

Um mit anderen an einem Git-Projekt mitarbeiten zu können, braucht es Remotes, sprich eine Version des Projekts im Netzwerk, wo alle Beteiligten ihre Änderungen hin pushen und die Änderungen der Anderen pullen können.

Nachdem man ein Repo gecloned hat, sieht man ein Remote, man kann jedoch auch weitere definieren:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
$ git clone git@ssh.gitlab.puzzle.ch:pitc_sys/sys-labs.git
Cloning into 'sys-labs'...
remote: Reusing existing pack: 1857, done.
remote: Total 1857 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (1857/1857), 374.35 KiB | 268.00 KiB/s, done.
Resolving deltas: 100% (772/772), done.
Checking connectivity... done.
$ cd sys-labs
$ git remote
origin
$ git remote -v
origin	git@ssh.gitlab.puzzle.ch:pitc_sys/sys-labs.git (fetch)
origin	git@ssh.gitlab.puzzle.ch:pitc_sys/sys-labs.git (push)
$ git remote add private https://gitlab.puzzle.ch/zwiseli/sys-labs.git
$ git remote -v
origin	git@ssh.gitlab.puzzle.ch:pitc_sys/sys-labs.git (fetch)
origin	git@ssh.gitlab.puzzle.ch:pitc_sys/sys-labs.git (push)
private https://gitlab.puzzle.ch/zwiseli/sys-labs.git (fetch)
private https://gitlab.puzzle.ch/zwiseli/sys-labs.git (push)

Wollen wir nun alle Information holen, welches das Zwiseli hat, jedoch noch nicht im Repo sind, kann man dies mit git fetch private ausführen.

Fetching und Pulling

Es gibt zwei Arten, wie man Daten von einem Remote abholen kann, nachdem man das Repo gecloned hat.

  • git fetch <remote>: holt alle Änderungen vom Remote seit dem letzten abholen und speichert die lokal. Wichtig zu beachten ist, dass git fetch die Änderungen nicht mit den lokalen Änderungen zusammenfügt, sondern dies manuell ausgeführt werden muss.

  • git pull: Wenn der lokale Branch mit einem remote Branch verknüpft ist, holt git pull die Änderungen dieses remotes und fügt sie gleich mit den Änderungen im lokalen Branch zusammen (merge). Was Branches sind und wie ein Merge funktioniert schauen wir uns in kürze an. Nur soviel: Wird ein Repo gecloned, wird der lokale sogenannte master Branch mit dem master Branch des origin Remotes verknüpft.

Pushen

Wenn man seine Änderungen soweit hat, dass man sie mit dem Rest vom Team teilen will, dann kann man diese mit git push <remote> <branch> auf den Server laden. Dies funktioniert jedoch nur, wenn man auf dem Server auch Schreibrechte hat und niemand anderes vor einem gepusht hat.

Zuletzt geändert September 19, 2023: fixed typos etc. (88d01f6)