Git ist eine freie Versionsverwaltungssoftware.
Für die folgenden Arbeiten ist es wichtig, dass Ihr auf hopper arbeitet und dort die Verzeichnisse bares und repos korrekt - insbesondere mit korrekten Dateirechten - einrichtet:
mkdir bares repos
chmod o-rwx bares repos
ls -ld bares repos
drwx------ 4 demo demo 4096 Jul 2 10:16 bares
drwx------ 4 demo demo 4096 Jul 2 10:16 repos
In bares finden sich die eigenen, zentralen - später remote - Repositories, in denen direkt nicht gearbeitet wird. In repos hingegen ist ein guter Ort, die konkreten eigenen oder geteilten Repositories als Kopien zu bearbeiten. Beide Verzeichnisse sollten von anderen nicht lesbar sein.
Zudem sollte vorab git lokal konfiguriert werden, damit es nicht mitten im ersten Prozess geschieht. Dazu muss einmalig die Email-Adresse und der Username konfiguriert werden.
git config --global user.email "you@smail.hs-bremerhaven.de"
git config --global user.name "Your Name"
Die Abfolge für das Anlegen und erste Bearbeiten eines Repositories, wie wir es gleich brauchen, ist wie folgt:
$ git init --bare ~/bares/first.git
Leeres Git-Repository in /home/demo/bares/first.git/ initialisiert
$ cd ~/repos
$ git clone ~/bares/first.git
$ cd first
$ touch readme.md
$ git add readme.md
$ git commit -m 'initial commit'
[main (Root-Commit) b183a4e] initial
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 readme.md
$ git push
Objekte aufzählen: 3, fertig.
Zähle Objekte: 100% (3/3), fertig.
Schreibe Objekte: 100% (3/3), 193 Bytes | 193.00 KiB/s, fertig.
Gesamt 3 (Delta 0), Wiederverwendet 0 (Delta 0), Pack wiederverwendet 0
To /home/tbd-demo-01/repos/../bares/first.git/
* [new branch] main -> main
Ab jetzt geht Ihr jeweils in den Zyklus:
Nun ist ein Versionskontrollsystem auch dazu da, dass wir nicht regelmäßig große Kopien von Ständen irgendwo ablegen müssen. Wir können jederzeit einen Commit auschecken und uns dort umschauen:
git log --oneline | head -n20
# Kopiere einen Commit
git checkout <COMMITHASH>
# umschauen und dann zurück
git switch main
Hier hilft der QuickSelect-Mode von Wezterm besonders weiter: mit SHIFT+STRG+SPACE werden sinnvolle Marker (hier die Commit-Hashes) mit Buchstaben belegt, wobei die jeweiligen Großbuchstaben den markierten Bereich sofort reinkopieren. Das spart den Griff zur Maus oder das Abtippen...
Wenn alles gut läuft, könnt Ihr diesen Zyklus regelmäßig durchführen. Allerdings kann es sein - insbesondere wenn Ihr all zu lange wartet, bis Ihr Eure Änderungen in das Remote-Repository zurückspielt, dass jemand anderes vor Euch gepusht hat. Dann gibt es drei Stände: Euren, den von dem Ihr weiterentwickelt habt, und den der jetzt von jemand anderem gepusht wurde. Damit müsst Ihr umgehen können.
Im Schritt git push kann es sein, dass etwa folgende Meldung kommt:
$ git push
To /home/gitterbett/tfw-2023-z/
! [rejected] main -> main (fetch first)
error: failed to push some refs to '/home/gitterbett/tfw-2023-z/'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
Dann müsst Ihr Euch zuerst mit git pull den Zustand vom Remote holen. Wenn die dann folgende Meldung mit Successfully rebased and updated refs/heads/main endet, konnte das Problem automatisch aufgelöst werden und Ihr könnt weiterarbeiten.
Gelegentlich wurde aber eine Datei so verändert, dass die Änderungen nicht automatisch aufgelöst werden konnten. Dann sucht die Änderungen mit git diff und korrigiert ersteinmal mit dem Editor direkt die entsprechenden Änderungen. Fügt dann die geänderten Dateien mit git add ... wieder hinzu, bis alle Änderungen aufgelöste wurden. Danach könnt Ihr mit git rebase --continue die unterbrochene Gesamtaktion fortsetzen und mit einem abschließenden git push (hoffentlich) erfolgreich den Stand wieder synchronisieren.
Am Besten klont Ihr, um das einzuüben, Euer bare-Repo auf Euer Notebook und spielt dann selber all die Situationen einmal durch. Denkt aber daran, dass Ihr auch auf Eurem lokalen Rechner die Grundkonfiguration vorab durchführen solltet:
mkdir repos; chmod o-rwx repos; cd repos
git config --global user.email "you@smail.hs-bremerhaven.de"
git config --global user.name "Your Name"
git clone hopper:/home/demo/bares/first.git # das funktioniert nur, wenn ssh korrekt eingerichtet ist.
(bzw: git clone hopper:bares/first.git)
Bevor es richtig zur branch-/merge-/rebase-Strategie geht, noch einmal ein Wort zur Warnung vor all zu selbstgewissen Macht-Man-Sos. Git ist ein sehr mächtiges Werkzeug und damit eines, das auf vielfältige Weisen zu nutzen ist. Das lockt auch Leute, die immer ganz genau zu wissen glauben, was genau und einzig richtig ist. Wie zum Beispiel, dass man auf keinen Fall Commits rebasen darf, wenn sie außerhalb des eigenen Repos existieren (the golden rule of rebasing). Nicht, dass es nicht vielleicht auch gute Ratschläge und Strategien sein können - es ist nur so, dass die Selbstgewissheit bisweilen irritierend ist.
Natürlich benötigen wir Ratschläge und Strategien für Anfänger:innen, um sie durch die ersten Wogen zu begleiten. Aber was, wenn dann sich im Engineering ein anderer Blick entwickelt? Beispielsweise scheint die goldene Regel des Rebasings durchaus brechbar, ohne dass gleich alles einstürzt: Git Rebasing Public Branches Works Much Better Than You’d Think.
Keineswegs möchte ich hier empfehlen, diese Regel zu brechen. Mir liegt aber daran, dass wir die Dinge, die wir tun, tatsächlich durchdenken und dadurch begreifen. Wenn wir dann Werkzeuge nutzen, die wir noch nicht wirklich begreifen können, ist es sinnvoll sich das einzugestehen und nicht lauthals auf Regeln zu pochen, die vielleicht bei Nachfragen von grüblerischeren Geistern keinen Sinn mehr ergeben.
Andere wichtige git-Kommandos:
git config -l # shows config
git log --pretty=oneline # shows list of commits
git checkout ... # checkout older commits
git switch - # back to head
git tag -a tagname # creates a tag
git push origin --tags # pushes tags to origin
git checkout tagname # back to tag tagname
git tag # list tags
git stash push # push working directory diffs on stack / with -u : include untracked
git stash pop # apply changes
git diff --staged # diff staged
git fetch origin # fetch state from origin without applying
git diff --name-only master origin/master # show changed files
Dateien, die nicht in das Repository sollen, werden in der Datei .gitignore im Hauptverzeichnis beschrieben. Die Datei selbst muss committed und gepushed werden, damit sie für alle gilt.
log/
.*.sw[a-z]
Lokale git-hooks liegen in dem Verzeichnis .git/hooks/. Will man sie miteinander teilen, ist es sinnvoll, mit Softlinks unter Unix zu arbeiten.
mkdir hooks
echo '#!/usr/bin/env bash
message=$(cat $1)
if test "$message" = "m"; then
echo "$message is not enough ..."
exit 1
fi' >hooks/commit-msg
chmod +x hooks/commit-msg
cd .git/hooks/
ln -s ../../hooks/commit-msg commit-msg
Das Verknüpfen über Softlinks sollte jeweils lokal über ein spezielles Skript ausgeführt werden.
Bisweilen soll ein git-Repo lediglich als Vorlage dienen, um daraus ein eigenes zu erzeugen oder um gewissermaßen Sofware herunterzuladen. Eine Variante ist - zumindest über ssh - git-archive zu verwenden:
git archive --remote=hopper:bares/brotundbutter-git-b --format tar heads/main | tar -tf-
Bisweilen ist die Abfolge empfohlen:
git clone --depth=1 hopper:bares/brotundbutter-git-b brotundbutter
cd brotundbutter
rm -rf .git