Im ersten Teil meines zweiteiligen Blogbeitrags habe ich euch von den absoluten Grundlagen bis hin zu Git-Aliassen geführt. Ich habe gezeigt, wie sie lange und komplexe Befehle ersetzen können, und einige Konzepte vorgestellt, die den meisten Nutzern nicht bekannt sind – etwa Aliasse, die Parameter direkt an Git übergeben, sowie erweiterte Log-Formatierung mit benutzerdefinierten Pretty-Formaten.
Jan Krag
Jan has been with us since 2014 as a Continuous Improvement Agent. He is an accredited trainer for Docker and Github and is also a certified life coach. Jan has a truly diverse range of interests. He breeds oriental cats, builds and flies his own kites, collects Rubik’s cubes, and enjoys snowboarding. Wow!
Bevor ihr weiterlest, empfehle ich euch dringend, zuerst Teil eins zu lesen.
In Teil zwei stelle ich noch fortgeschrittenere Konzepte vor und wage mich an wirklich „abgefahrene“ Einsatzmöglichkeiten von Git-Aliasen – darunter einige geradezu verrückte Beispiele. Zum Schluss stelle ich einige Alternativen vor, die dieselben Anforderungen erfüllen.
Also, legen wir los.
Level 6: ! Nicht-Git-Befehle
Mehr fürs Geld.
Mit Git-Aliasen können wir unser Repertoire an Aktionen im Git-Kontext erweitern. In diesem und den meisten folgenden Abschnitten geht es darum, was wir tun können, wenn wir die Grenzen von Git-Subcommands verlassen.
Die „Bang“-Funktion erweitert die Möglichkeiten von Git-Aliasen erheblich, denn sie erlaubt uns, die Shell aufzurufen und beliebige Shell-Befehle auf unserem System auszuführen. Dazu stellen wir der Alias-Erweiterung einfach ein Ausrufezeichen voran – in der Unix-/Shell-Welt auch „Bang“ genannt.
Beginnen wir mit einem sehr einfachen Beispiel, das das Konzept klar zeigt und tatsächlich nützlich ist.
Auf einigen Plattformen wird Git mit zwei nützlichen GUI-Tools vorinstalliert: „Git gui“ zum Stagen und Committen sowie „Gitk“ zum Anzeigen der Historie. Aber warum ist git gui ein Git-Subcommand und gitk nicht? Ziemlich verwirrend, aber mit einem Alias leicht zu beheben:
Und wenn wir schon dabei sind: Ich hatte ein ähnliches Problem. Wenn mein Gehirn tief im „Git“-Modus steckt, tippen meine Finger oft „Git“, bevor ich mich für einen Befehl entschieden habe. Dann entscheide ich plötzlich, dass ich die Dateien im Ordner auflisten muss, und tippe schnell ll <enter> (mein täglicher Shell-Alias für ls -al) – und plötzlich erhalte ich:
Jetzt funktioniert der „Tippfehler“ git ll einfach, führt aber zu einer sehr wichtigen Einschränkung der Bang-Funktion, die – wie gezeigt – sowohl Segen als auch Fluch sein kann.
Mein Alias git ll scheint also zwar zu funktionieren, zeigt mir aber immer den Inhalt des Root-Ordners des Repositories an, selbst wenn ich mich in einem Unterordner befinde.
Der Shell-Befehl pwd gibt das aktuelle Arbeitsverzeichnis aus; dieser Alias bietet eine schnelle Möglichkeit, den Pfad zu meinem Repository anzuzeigen.
Zugegeben, dieses spezielle Problem ließe sich besser mit einem normalen Git-Alias lösen, ohne eine neue Shell-Session zu erstellen (wobei noch nicht abschließend geklärt ist, welche Variante schneller läuft).
Aber ich habe mindestens eine sehr gute Verwendung für diesen Nebeneffekt. Manchmal befindet ihr euch aus anderen Gründen per cd tief in einem Unterordner. Dann wird es ziemlich lästig, die Git-Statusausgabe zu lesen, weil alle anderen Pfade relativ zu eurem aktuellen Ordner ausgegeben werden. Änderungen werden dann beispielsweise so angezeigt:
Nutzen wir also den Nebeneffekt des „Root-Ordners“ und ergänzen wir den regulären st-Alias um Alternativen:
Diese zeigen die Statusausgabe immer relativ zum Root des Repositories an.
(-s steht lediglich für das weniger ausführliche „short“-Ausgabeformat.)
Beachtet, dass sich ein ähnlicher Effekt mit der Konfigurationsvariablen status.relativePaths und der im ersten Teil dieses Blogbeitrags in „Level 5“ erläuterten -c-Funktion erzielen ließe.
Zum Abschluss dieses Abschnitts noch etwas Spaß: Wenn ich unbewusst Git vor ll tippe, kann es auch passieren, dass ich Git eintippe, bevor ich mich beispielsweise für Git status entscheide. Das führt dann leider zu:
$ git git statusgit: 'git' is not a git command.Mein Gehirn kam also auf die Idee, Folgendes zu erstellen:
Ja, dadurch funktioniert git git status . Und aufgrund der rekursiven Natur von Aliasen funktioniert jetzt sogar git git git git git status … zumindest auf dem Papier.
In der Praxis ist das eher ein guter Witz als eine gute Idee, denn es hat zwei wichtige Folgen. Das erste Problem ist, wie oben gezeigt, dass der Befehl nun aus dem Root-Ordner des Repositories ausgeführt wird, was verwirrend sein kann. Das zweite Problem: Es beeinträchtigt die recht wichtige Möglichkeit, mit git help git die Hilfeseite für den eigentlichen Git-Befehl aufzurufen. Stattdessen würde nun die deutlich weniger nützliche Ausgabe erscheinen:
Level 7: Git-Aliase wiederverwenden
Auf dem aufbauen, was vorher da war … Das verwenden, was vorher da war
In älteren Git-Versionen vor 2.20 durften Git-Aliase nicht auf andere Git-Aliase verweisen. Das führte oft zu vielen ähnlichen Aliasen in der Konfigurationsdatei, wie wir bei den benutzerdefinierten Git-Log-Befehlen gesehen haben.
Die einzige verfügbare Lösung bestand darin, die Bang-Funktion zu nutzen, denn damit konnte man durchaus Folgendes verwenden:
Dadurch wird kein bestehender Alias rekursiv aufgerufen, sondern eine neue Shell-Session gestartet, die lediglich Git mit einem Alias verwendet.
Glücklicherweise sind seit Git 2.20 (2018) rekursive Aliase erlaubt, und seit 2.30 (Q1 2021) versteht und übersetzt sie sogar bash-completion (Tab-Vervollständigung).
Für eine deutlich übersichtlichere Git-Konfiguration kann ich jetzt also Folgendes verwenden:
Level 8: Aktionen per Pipeline verketten
Unix-Befehle für mehr Kontrolle (oder Wahnsinn) verketten
Ich hatte bereits erwähnt, dass die Bang-Funktion alles verändert, aber wir haben bisher nur an der Oberfläche gekratzt. Der nächste Schritt folgt, wenn ihr erkennt, dass der Aufruf einer Shell es ermöglicht, mehrere Befehle miteinander zu verketten.
Die einfachste Anwendung sind Aliase, die mit der Shell und dem Operator && mehrere voneinander getrennte Aktionen nacheinander ausführen.
Hinweis: Der obige Alias wurde aus Gründen der Lesbarkeit umgebrochen. In der Konfiguration muss er in einer einzigen Zeile stehen. Mehr dazu später.
Wir können sogar vorhandene Umgebungsvariablen verwenden oder neue direkt in einem Alias definieren.
Ich druckte eine Kopie für unser Büro aus, was eine lange und humorvolle Diskussion auf Slack darüber auslöste, warum das nicht funktionieren würde und wie es verbessert werden müsste.
Das Endergebnis war folgendes Alias-Schmuckstück, das die Verwendung von Variablen anschaulich demonstrierte:
Die wahre Stärke zeigt sich jedoch, wenn wir noch einen Schritt weitergehen und Unix-Pipes verwenden, um die Ausgabe eines Befehls an den nächsten weiterzugeben.
Zuerst definiere ich einen schnellen Alias namens remoteurl, um die URL meines Git-Remotes einfacher abzurufen. Aber was ist, wenn ich das Repository per SSH geklont habe und eine URL im Format „git@“ erhalte, obwohl ich damit die Website des Repositorys finden möchte?
Senden wir die Ausgabe des ersten Befehls an sed, das Suchen und Ersetzen ausführt und git@ durch https:// ersetzt.
Zum Abschluss fügen wir einen Alias hinzu, der diese Ausgabe mit dem Mac-Befehl pbcopy direkt in meine Zwischenablage kopiert. (Unter Windows könnten wir dasselbe mit clip tun.)
Ähnlich können wir andere Shell-Funktionen nutzen, etwa um Ausgaben in Dateien umzuleiten. Manchmal möchte ich in meinen Repositories eine Datei namens .mailmap erstellen. Damit könnt ihr beispielsweise die alte E-Mail-Adresse einiger Mitwirkender ihrer neuen zuordnen oder sicherstellen, dass unterschiedliche Schreibweisen eines Benutzers zusammengeführt werden.
Als guten Ausgangspunkt für eine Mailmap benötige ich eine Liste von Autoren mit ihren Namen und E-Mail-Adressen im Standardformat „Jan Krag <jan.krag@example.com>“, ähnlich wie bei git shortlog -sne, aber ohne die erste Zahlenspalte.
Dafür habe ich die folgenden Aliase erstellt:
[alias]mm = "!git log --format='%aN ' | sort -u"mmm = "!git mm >> .mailmap"mmme = "!git mmm && code .mailmap"
Der Log-Befehl gibt für jeden Commit einfach Autor und E-Mail-Adresse aus und verwendet dann die Option „unique“ des Shell-Befehls sort, um Duplikate zu entfernen und die Ausgabe zu sortieren. Der erste Alias gibt sie auf der Konsole aus, während der zweite die Ausgabe umleitet und eine bestehende .mailmap-Datei erstellt oder ergänzt.
Der dritte ist die praktische „Ich habe es eilig“-Version, die die neue .mailmap-Datei sofort in meinem VSCode-Editor öffnet. Hier kombinieren wir also Pipes, Umleitungen und die &&-Funktion in einem Alias.
Level 9: Funktionen
Bash-Funktionen sind einfach großartig!
Lasst uns noch einmal einen Schritt weitergehen und schauen, was wir tun können. Jetzt, da wir diesen Shell-Kontext haben, steht uns eine weitere Funktion zur Verfügung: Wir können Funktionen definieren. Warum sollte ich das wollen, fragt ihr euch vielleicht? Einerseits lassen sich komplexe Aliase damit etwas übersichtlicher gestalten. Noch wichtiger ist aber, dass wir Kommandozeilenargumente auslesen und kontrollierter verwenden können.
Bei normalen Aliasen könnt ihr nach dem Alias beliebige Optionen und Argumente anhängen; diese werden nach der Auflösung ergänzt. Wenn wir zum Beispiel zu meinem Git-slog-Alias aus dem vorherigen Beitrag zurückkehren, könnt ihr problemlos git slog -3 oder git slog --all. verwenden. Was aber, wenn ich einen Alias erstellen möchte, bei dem ich ein Argument an mehreren Stellen benötige?
Ein sehr einfaches Beispiel dafür ist mein Alias, um einen Branch sowohl lokal als auch auf dem Remote zu löschen:
Beachtet die Syntax. In meinem Shell-Kontext definiere ich zunächst eine Funktion f(), die die Anweisungen von der öffnenden { bis zur abschließenden }; ausführt.
Anschließend rufe ich diese Funktion einfach über ihren Namen f auf. Das Schöne daran ist, dass wir nun Argumente an die Funktion übergeben können. Diese stehen im Gültigkeitsbereich der Funktion als „Positionsparameter“ zur Verfügung, nummeriert ab eins. ${1} verweist also einfach auf das erste Argument, das der Benutzer beim Aufruf der Funktion übergibt. Wie ihr im Beispiel seht, können wir einen Parameter beliebig oft verwenden.
Schauen wir uns ein weiteres einfaches Beispiel an:
[alias] # GitHub-Repository einfach als neues Remote hinzufügen ghremote= "!f(){ git remote add $1 https://github.com/$2.git; }; f"
In diesem Beispiel verwende ich eine Funktion nicht, um ein Argument wiederzuverwenden, sondern weil ich mehrere Argumente entgegennehmen und sie an ganz bestimmten Stellen direkt im Alias verwenden möchte.
Vielleicht ist euch aufgefallen, dass dieses Beispiel die Positionsparameter ohne geschweifte Klammern verwendet. Das soll nur verdeutlichen, dass die Klammern nach Bash-Standard erst bei Positionsparametern über neun erforderlich sind. Es ist also eine Frage eurer Präferenz.
Probieren wir diesen Alias ghremote aus:
Level 10: Übertreiben
Mehrzeilige Formatierung einführen.
Wir haben bereits einige Beispiele für Aliase gesehen, die in eurer Konfiguration zu sehr langen und damit fast „unlesbaren“ Zeilen werden. Es gibt jedoch eine Möglichkeit für echte mehrzeilige Aliase. Fügt einfach am Ende einen Backslash hinzu, denn damit wird der Zeilenumbruch maskiert.
Das beantwortet allerdings nicht wirklich die Frage: „Sollte ich das tun?“
Irgendwann stoßen wir an die Grenzen dessen, was mit Aliasen noch sinnvoll ist. Dann solltet ihr einige der alternativen Optionen in Betracht ziehen, die ich in „Level 11“ vorstelle. Aber schließlich ist dies ein Blogbeitrag über Aliase. Verschieben wir die Grenzen also ein wenig – ihr könnt dann selbst urteilen.
Beginnen wir diesen Abschnitt mit einem recht komplexen, aber manchmal nützlichen Beispiel, das ich nicht im Detail erklären werde.
Die Kombination mit dem symbolic-ref-Trick aus dem Alias swm, um den Standard-Branch zu finden, überlasse ich euch, liebe Leser, als Übung.
Es gibt nicht viel mehr zu erklären. Schauen wir uns daher noch einige Beispiele an, die euch dazu inspirieren können, eigene Aliase zu schreiben.
„Ich möchte einfach nur die Website für dieses Repository öffnen.“
Aber was ist zu tun, wenn das Git-Remote SSH verwendet?
Schauen wir uns das in Aktion an:
Welche Dateien bekommen am meisten Aufmerksamkeit?
Ich habe diesen Alias ursprünglich in einem .gitconfig-Blogbeitrag von Michael Wales gefunden und leicht an meine Präferenzen angepasst.
[alias]churn = !git -p log --all -M -C --name-only\ --format='format:' $@ \ | sort \ | grep -v '^$' \ | uniq -c \ | sort -r \ | awk 'BEGIN {print count,file} {print $1 , $2}'
Und in Verwendung im Repository git-katas:
$ git churn42 README.md24 basic-commits/README.md21 basic-branching/README.md19 ignore/README.md19 basic-staging/README.md17 submodules/README.md17 3-way-merge/README.md16 configure-git/README.md15 Overview.md14 ff-merge/README.md
Warum muss ich wissen, wie ich fortfahre?
Ich halte häufig Git-Schulungen, und jedes Mal, wenn wir über das Beheben von Merge-/Rebase-Konflikten sprechen, habe ich das zweifelhafte Vergnügen, Folgendes vorzustellen:
git merge --continuegit rebase --continuegit cherry-pick --continuegit revert --continue
Irgendwann fragte ich mich: „Warum um alles in der Welt muss ich als Benutzer angeben, ‚was‘ ich fortsetze, wenn Git offensichtlich weiß, dass es sich in einem Merge-, Rebase- oder sonstigen Zustand befindet? Warum gibt es nicht einfach einen continue-Befehl, der ‚das Richtige tut‘?“
Soweit ich mich erinnere, habe ich das getan, was wir vor ChatGPT getan haben: im Internet gesucht und ein Bash-Skript gefunden, das genau das erledigt. Wie im nächsten Kapitel erwähnt, ist das wahrscheinlich auch die vernünftige Lösung. Aber als Fan habe ich getan, was getan werden musste, und es als reinen Alias umgesetzt. Deshalb kann ich euch dieses Monstrum präsentieren, das den Geist des maßlosen Übertreibens perfekt verkörpert:
Git-Aliasse teilen
Zum Abschluss dieses Abschnitts teile ich mit euch meinen „heiligen Gral“ unter den Aliasen, den ich vor etwa zehn Jahren mit harter Arbeit und Entschlossenheit entwickelt habe.
Das Problem, das ich lösen wollte, war das Teilen von Aliasen mit Kollegen, zum Beispiel in Slack. Wenn ich einem weniger erfahrenen Git-Benutzer schnell einen Alias zur Verfügung stellen möchte, ist es viel zu kompliziert, jedes Mal erklären zu müssen, wie er die globale Konfigurationsdatei findet und bearbeitet, wo er den Alias-Code in der Datei einfügen muss und so weiter.
Viel einfacher wäre es, einfach den passenden Befehl git config –global alias.foo "do this Git thing" zu senden, den sie direkt ausführen können. Einfache Aliasse tippe ich einfach aus dem Gedächtnis ein. Bei auch nur leicht komplexen Aliasen mit Anführungszeichen und vielleicht Variablen ist es jedoch nicht trivial, alles korrekt hinzubekommen. Deshalb kam ich auf die Idee, einen Alias namens „exportalias“ zu schreiben. Ich ahnte nicht, wie schwer es sein würde, diesen korrekt umzusetzen. Eines der Erfolgskriterien war unterwegs, dass er sich selbst exportieren können sollte. Ich habe ihn nicht mit all den wirklich verrückten mehrzeiligen Aliasen getestet, aber bei diesen würde ich ohnehin immer den .gitconfig-Ausschnitt teilen.
Diesen habe ich bewusst ohne Zeilenumbrüche gelassen, damit er sicher in einem exportierbaren Format vorliegt.
Probieren wir ihn mit einem nicht ganz einfachen Alias aus einem früheren Abschnitt dieses Beitrags aus – einem, der nicht nur Sonderzeichen, sondern auch maskierte Anführungszeichen enthält.
Level 11: Bonusrunde
Gibt es eine Grenze für den Wahnsinn?
In diesem abschließenden Bonusabschnitt stelle ich euch einige alternative Ansätze zur Erweiterung der Git-Funktionalität vor – Optionen, die sich als vernünftiger erweisen könnten als mehrzeilige Aliasse über eine ganze Seite.
Eigene ausführbare Dateien
Zunächst einmal die Erkenntnis, dass Git klugerweise so aufgebaut ist, dass jede ausführbare Datei in eurem Pfad namens git-something zu einem Befehl git something wird.
Das bedeutet, dass einige unserer längeren Aliasse stattdessen als einfaches Bash-Skript umgesetzt werden könnten. Warum ist das wichtig, fragt ihr euch vielleicht? Unter anderem, weil ihr euch viel Formatierungsaufwand innerhalb der .gitconfig-Datei spart, etwa das Maskieren von Zeilenumbrüchen und unnötiges Hantieren mit Anführungszeichen.
Ein weiterer Vorteil ist, dass wir besser „echte“ Skripte mit ordentlicher Argumentverarbeitung, Fehlerbehandlung, Hilfetexten zur Verwendung und mehr schreiben können.
Tatsächlich verlangt Git nicht einmal, dass ihr Bash verwendet. Ihr könnt eure neuen Git-Befehle in jeder Sprache schreiben, auch wenn ihr wahrscheinlich bei einer Sprache mit einer guten Git-Bibliothek bleiben solltet, etwa Python, Java oder Go-lang.
Git-Erweiterungspakete
Wenn wir eigene Git-Befehle als Binärdateien in unserem Pfad schreiben können, können wir auch ganze Sammlungen davon erstellen und verteilen. Es gibt sogar Open-Source-Projekte, die solche „Git-Erweiterungen“ anbieten – entweder für bestimmte Zwecke oder als allgemeine Sammlungen „nützlicher“ Befehle.
Eine der größten dieser allgemeinen Sammlungen ist https://github.com/tj/git-extras. Nach der Installation erweitert sie euer Portfolio um mehr als 70 neue Befehle, darunter git extras, das alle Befehle auflistet. Die Installation ist mit den meisten gängigen Paketmanagern möglich. Falls das keine Option ist, könnt ihr sie auch manuell klonen, zum Beispiel:$sudo apt-get install git-extras
oder
Ich werde nicht alle 70 Befehle auflisten, aber hier sind einige ausgewählte Beispiele:
git-abort: Aktuellen Git-Vorgang abbrechen.
git-alias: Aliase definieren, suchen und anzeigen.
git-magic: Add/Commit/Push-Routinen automatisieren.
git-repl: Git-Read-Eval-Print-Loop.
git-standup: Nachvollziehen, was du am letzten Arbeitstag gemacht hast.
Ein weiteres Erweiterungspaket, das einen Blick wert sein könnte, ist Git Pastiche. Es enthält eine deutlich kleinere Auswahl an Befehlen, die vielleicht etwas grundlegender sind. Besonders git stats und git activity finde ich jedoch gelegentlich sehr nützlich.
In diesem Zusammenhang ist auch erwähnenswert, dass etwas, das wir als „Kern“-Erweiterung von Git betrachten, wie Git LFS, auch bekannt als Git Large File Storage, ebenfalls nach diesem Konzept als Erweiterung entwickelt und in Go geschrieben wurde. Der zentrale Git-LFS-Befehl ist lediglich eine ausführbare Datei namens git-lfs, die wiederum alle LFS-Unterbefehle an andere Go-Programme weiterleitet.
Hilfe
Abschließend gehe ich kurz auf die Hilfe ein. Wenn du bei deinen eigenen Git-Befehlen wirklich aufs Ganze gehen möchtest, wie es „git extras“ und andere getan haben, kannst du eigene Hilfeseiten bereitstellen.
Wenn du git help mycustom eingibst, prüft Git tatsächlich, ob es auf deinem System eine Manpage für mycustom gibt. Wie man Manpages erstellt, würde den Rahmen dieses Blogbeitrags sprengen, aber gute Ressourcen dazu sind leicht zu finden.
Bei Git-Aliasen ist dir vielleicht aufgefallen, dass etwas wie git help st einfach Folgendes ausgibt:
Je nach deinen Anforderungen kann das nützlich sein – oder auch nicht.
Für einfache Aliase wie diesen, also solche, die nicht Bash aufrufen, kannst du auch das Format git st --help verwenden, um die normale Hilfeseite für den Befehl des Alias aufzurufen – in diesem Fall git status.
Danksagungen und Hinweise
Die Beispiele in diesem Beitrag stammen aus meiner persönlichen Sammlung aus mehr als einem Jahrzehnt Git-Nutzung. Einige habe ich angepasst, um zu zeigen, was ich in den einzelnen Abschnitten vermitteln möchte.
Was die Herkunft dieser Aliase angeht: Einige habe ich selbst geschrieben, um ein eigenes Problem zu lösen, andere wurden mir von Kollegen, Teilnehmern meiner Git-Kurse und Freunden weitergegeben. Manche habe ich im Laufe der Jahre einfach online gefunden und entweder unverändert übernommen oder für meine Zwecke angepasst. Generell gibt es in der Community einen Geist des Teilens, daher hoffe ich, dass dies unter „Fair Use“ fällt.
In letzter Zeit habe ich begonnen, bei sehr komplexen Aliasen Kommentare in meiner Konfiguration hinzuzufügen, die angeben, wo ich sie gefunden habe. Aber selbst dann ist es schwierig, den Ursprung dieser Codebeispiele nachzuverfolgen, da andere genauso vorgehen wie ich: Sie übernehmen etwas von irgendwoher und passen es bei Bedarf an.
Wenn du in diesem Blogbeitrag einen Alias gefunden hast, dessen ursprünglicher Autor du bist, gib mir bitte Bescheid. Ich werde dich dann entweder als Quelle nennen oder ihn auf Wunsch entfernen.
Denk daran, auch Teil eins dieses Blogbeitrags zu lesen, falls du das noch nicht getan hast!
- Softwareentwicklung
- DevOps
- CI/CD
Subscribe to our newsletter
Related blogs