CLI-Adventskalender, Tag 18: git

CLI-Adventskalender: git

Avatar von Eric

In der 18. Ausgabe unseres Adventskalenders beschäftigen wir uns mit git.

Vielleicht ist es merkwürdig ein Tool zu erwähnen, das die meisten Entwickler jeden Tag benutzen. Git ist vielleicht das mächtigste Werkzeug, über das ich im ganzen Kalender rede. Es hat viele versteckte Tiefen, die vielen Entwicklern nicht klar sind und es bietet Nicht-Entwicklern Anwendungsmöglichkeiten, die ihresgleichen suchen.

Skalierung.

Jede Software ist für eine bestimmte Skalierungsstufe optimiert. Manche Software möchte es einzelnen Nutzern ergonomisch erleichtern, ihre einzelnen Dateien zu bearbeiten. Andere sind dafür vorgesehen, zehntausende Dateien über viele Teams hinweg zu versionieren. Je höher die Skalierung ist, desto unbequemer fühlt sich das Tool an. Ein normales Backuptool ist schließlich im Hintergrund und sichert Sachen, während ein Tool wie git sehr präzise Aussagen darüber möchte, was genau gespeichert werden soll, in welchem Commit, auf welchem Branch und zu welchem Remote es soll. Ganz zu schweigen davon, wie es mit Konflikten umgehen soll und der Möglichkeit, die Geschichte jederzeit umzuschreiben.

Commits in ordentlich

git add *
git commit -a -m "Mehr Code bla"
git push
XKCD zum Thema git.
Quelle: Randall Munroe – xkcd.com/1296

Anfänger benutzen git gerne als großen Save-Button und haben auf ihren eigenen Projekten einen main-Branch, der einen Stapel undurchsichtiger Commits enthält.

Oder vielleicht war nur ich das.

Ich habe Git damals als großen Speichern-Knopf benutzt. Je mehr man mit anderen Leuten zusammenarbeitet, desto ordentlicher möchte man irgendwann sein. Des weiteren haben „saubere Commits“ die sich um ein spezifisches Thema drehen viele andere Vorteile. Mehr dazu unten.

git add -p src/MasterManagerContainerManagerContainer.java

Erlaubt einzelne Änderungen innerhalb von Dateien zu stagen, um so saubere Commits zu schnüren. Das geht auch mit git stash --patch, um einen selektiv sauberen Stand hinzubekommen.

Moment … Staging? Was ist das?

Schnelle Git-Grundlagen

Die bittere Pille theoretischer Grundlagen müssen wir schnell schlucken, damit die anderen Befehle irgendeinen Sinn ergeben.

Eine Datei in einem Git-Repository hat grundsätzlich einen von vier Zuständen:

  • Untracked: Git ist aktuell noch nicht für diese Datei zuständig. Mit einem Eintrag in der .gitignore kann man sie rausfiltern.
  • Tracked/Unmodified: Datei wird von Git verwaltet, wurde aber nicht geändert.
  • Modified: Ungespeicherte Änderungen. Kann auch Deleted oder Moved sein. Oder Conflicted, wenn Änderungen aus zwei verschiedenen Branches in ihr kollidieren.
  • Staged: Ergebnis von git add. Die Änderungen in dieser Datei werden in den nächsten Commit gepackt. 

Ein Git-Repository hat ein Verzeichnis namens .git, in dem die ganze Magic passiert. Unter anderem liegen dort:

  • Objects: In .git/objects haben einen Hash und sind Blobs (aus denen sich der Inhalt von Dateien zusammensetzt), Commits oder Trees.
  • References: In .git/ref. In .git/ref/heads liegen die Pointer auf den letzten Commits eines jeden lokalen Branches. In .git/ref/remotes dasselbe für Remote Branches. Tags liegen in .git/ref/tags.
  • Index: Eine Binärdatei, die sagt, was in den nächsten Commit soll. Unsere „Staging Area“, wenn man so will.
  • Configs: Diverse Einstellungen in .git/config.
  • Logs: In .git/logs sind die letzten Änderungen verzeichnet.

Ok, ok. Warum haben wir uns das jetzt alles angeschaut? Weil wir mit diesem Wissen allen möglichen coolen Kram machen können.

Git-Objekte

Alle Elemente eines Branches sind lediglich Objekte, die mit ihrem Hash referenziert sind. Deswegen kann ich mir mit git cherry-pick 50a5cbda3c einen bestimmten Commit auf meinen Branch holen. Und deswegen sollte man saubere Commits machen (ich weiß, Marco. Es tut mir leid.).

Mit git show 50a5cbda3c kann man gucken, was drin ist.

Obgleich git einen Garbage Collector hat, triggert der nicht sofort, wenn Objects unverlinkt ist. Ich kann also mit git reflog hunderte von Commits anschauen. In der Reihenfolge, in denen sie zuletzt der aktuelle Commit (HEAD) gewesen sind oder im Laufe eines Rebases angefasst wurden. Sehr, sehr nützlich, wenn man nach einigem Foo (technischer Fachausdruck, Oberbegriff für eine Reihe von Fehlerzuständen und Konfigurationsarbeit) plötzlich einen Commit vermisst. Neunzig Tage lang ist er lediglich einen Cherry-Pick weit weg.

Mach meine Ansicht mal schön

Wie jedes Kommandozeilentool möchte ich bei Git auch erst einmal herausfinden, was los ist:

git status

… und wo wir sind und wie wir da hin gekommen sind:

git log

Natürlich kann man diese Ansicht auch noch anpassen (und mit einem Alias zum Default machen):

git status -s # Kürzer
git status -vv # Länger

Und noch mehr Optionen bietet git log:

# Fancy
git log --graph --all --decorate --oneline
# So viele Farben
git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit
# Relatives Datum
git log --pretty=format:"%h - %an, %ar : %s" --decorate
# Änderungen an Dateien
git log --stat
# Das Format kann man wirklich hart customized
git log --pretty=format:"%h %s [%an, %cr]"
# Email-Format!
git log --pretty=email --boundary
# Nur die Merges
git log --merges

Wie gesagt, dass man eine Formatzeile dieser Art ad-hoc formuliert sollte eher selten vorkommen. Stattdessen möchte man wahrscheinlich ein Alias definieren.

Das kann man nicht nur in der .bashrc tun, sondern auch in git selbst:

git config --global alias.farben 'log --graph --pretty=format:"%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset" --abbrev-commit'
git farben

Wer hat das verbrochen?

Git ist gnadenlos gründlich darin zu tracken, wer wann was verändert hat. Je nach Firmenkultur kann das auch was Positives sein.

Mit git shortlog kann man alle Commits eines Repos geclustert nach AutorInnen sehen.

Der klassische „Beender“ eines freundlichen Arbeitsklimas ist git blame <file>.

Ansonsten bietet git auch angenehme Suchfunktionen:

git log --author="Eric" --grep="database"
git log --since="2022-01-01" --until="2023-01-01" --committer="Eric"

Und vielleicht will ich einfach nur die letzten zwei Commits haben, die eine bestimmte Datei verändert haben?

git log -p -2 MasterManagerContainerManagerContainer.java

Mach das weg

git reset --soft? git reset --hard? git revert? git checkout <file>? Keine Sorge, ich bin ja da.

git reset --soft HEAD~2

Ändere nix an den Dateien, gehe aber zwei Commits zurück.

„Hä?“ Das ist nützlich, um Commits aufzuräumen und anders zu verpacken.

git reset --hard HEAD~2

Zurück in die Vergangenheit, als das Leben noch einfach war. Alles was nicht committet ist, wird gelöscht. Wir sind jetzt zwei Commits zurück, genau so, wie es vor zwei Commits war.

git reset

Alles was wir mit git add ge-stage-t haben, ist jetzt wieder unstaged. git reset <file> macht das gleiche mit einer bestimmten Datei.

git revert 50a5cbda3c

Mach einen neuen Commit, der genau die Änderungen aus diesem anderen Commit ungeschehen macht.

git checkout -- MasterManagerContainerManagerContainer.java

Stampfe alle Änderungen an dieser Datei ein und bringe sie wieder auf den letzten comitteten Stand.

git checkout .

Rage quit. Alle Änderungen verwerfen, zurück zum letzten committeten Stand.

Zeitreise!

Seit wann gibt es denn einen Bug? Zeit für eine semi-automatische Binärsuche:

git bisect start

Wir testen den Stand und sagen dann entweder

git bisect good

oder 

git bisect bad

bis wir den Commit gefunden haben, bei dem sich ein Bug eingeschlichen hat.

Ich bin ja faul.

Ich schreibe einfach ein Skript.

git bisect run tolles_test_skript.sh

Das Skript muss 0 für gute Commits und Nicht-0 für schlechte Commits zurückgeben und git macht den Rest.

Hail Hydra!

Schon mal erlebt? Ihr habt zwei Branches im Review, arbeitet am nächsten Ticket im eigenen Branch und bekommt Feedback in Richtung „Das hier noch kurz ändern, bitte gleich, dann können wir es sofort mergen“?

Aaaah. Alles stashen und wegpacken und argh. Und jetzt soll ich auch noch schnell auf Branch 2 was testen, ob es bei mir geht?

Wie die Theorie oben schon vermuten lässt, ist der aktuelle Zustand des Dateisystems in einem Repo das Ergebnis zusammengebastelter Git-Objekte. Man kann dasselbe Repo ja auch mehrfach clonen. Kann man vielleicht aus demselben Repo mehrere Dateisysteme machen und muss dann main nur einmal pullen?

Aber ja.

git clone [URL] projekt
cd projekt
git checkout main
git worktree add ../BUG-123 feature/BUG-123
cd ../BUG-123

Jeder Worktree git worktree list ist ein eigener, vollständiger Verzeichnisbaum mit eigenem Index und HEAD.

Mit

rm -rf ../BUG-123
git worktree prune

wird man einen Worktree wieder los.

Das ist fantastisch, wenn man schnell zwischen Branches wechseln möchte.

Vergangenheit, Gegenwart und Zukunft

Wenn mich Werkstudierende und Juniors fragen was sie lernen sollen, schlage ich immer zuerst Git vor. Es ist kein gutes Gefühl, die Arbeit von Stunden zu verlieren oder nach einem Rebase oder Merge in einem komplett ungeklärten Zustand zu hängen.

Ich habe mich auch nicht sonderlich kompetent gefühlt als ich ein Repo neu ge-clone-t und dann meine Änderungen manuell rein-pastiert habe. (Die meisten von uns waren wahrscheinlich schon einmal an diesen Punkt.)

Beim Debuggen von „Git-Foo“ helfen zu können ist ein nützlicher Skill, den man in jedes Team einbringen kann – ganz gleich was sie sonst für Technologien einsetzen.

Shell-Weisheit des Tages
One of us! One of us!

Als ich meinen ersten Computer hatte (486er mit MS-DOS), hatte ich kein Internet, kein Handbuch und nur meinen grummeligen Vater, der mir hier und da ein paar Sachen erklärt hat. Es ist leicht, sich alleine zu fühlen – auch heute, wenn man jedes Problem googlen oder von einer LLM-AI erklären lassen kann.

Aber man muss nicht alleine sein!

Es gibt Foren, Subreddits, IRC Channel, GitHub, unzählige soziale Medien und (gruselig, ich weiß) unzählige Meetups und den einen oder anderen Verein in der analogen Welt. Ganz zu schweigen von … Arbeitskollegen!

Hätte ich nicht einen Kollegen im Team gehabt der mir gut zugeredet hätte (Hallo Andi!), würde ich heute kein Arch Linux verwenden.

Was ich wieder und wieder in den letzten 15 Jahren gelernt habe, ist dass die meisten Nerds unglaublich hilfsbereit sind und sehr gerne ihr Wissen teilen. Diese willkommenheißende Atmosphäre hat sich in den letzten Jahren immer stärker kodifiziert. Wir haben Codes of Coduct, wir haben Diversity als Kernwert von unzähligen Konferenzen und Unternehmen.

Es ist etwas, was ich unbedingt noch hier reinschreiben will, auch wenn es vielleicht für die meisten vollkommen offensichtlich ist: Es gibt ein paar arrogante Nerds, die sich auf ihrer kleinen Eisscholle aus Kompetenz einen Bunker bauen und alle runterschubsen, die an ihre Tür klopfen. Computer funktionieren aber nicht mit Magie. Man braucht auch keine Zertifikate oder Abschlüsse, um „dazu zu gehören“ (Vollzeitanstellung zu einem vernünftigen Gehalt mal außen vor gelassen.) Meistens reicht ein ehrliches Interesse. Meistens reicht das Selbstvertrauen sagen zu können „Ich verstehe das hier noch nicht, kannst Du es mir erklären?“. Und vielleicht. Vielleicht! Hast Du liebe lesende Person ja ein paar Sachen mit anderen Leuten, die sich auf derselben Straße befinden: Neugier, Offenheit für Neues, Freiheitsliebe, Motivation etwas zu bauen, Respekt vor Kompetenz unabhängig von sozialer Herkunft, Alter, Gender, Behinderungen, etc.

Die Tür ist offen. Tritt Dir die Füße ab und komm rein.

Avatar von Eric

Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert


Für das Handling unseres Newsletters nutzen wir den Dienst HubSpot. Mehr Informationen, insbesondere auch zu Deinem Widerrufsrecht, kannst Du jederzeit unserer Datenschutzerklärung entnehmen.