Merge branch 'master' of ssh://gitea.schneiderserver.site:2222/mathias2784/it-know-how
This commit is contained in:
@@ -16,6 +16,7 @@ Sammlung wichtiger BASH-Befehle und Konzepte für die Linux-Konsole.
|
||||
## Dateiverwaltung
|
||||
|
||||
- [[IT-Know-How/Linux/BASH Basics/ls]] - Dateien auflisten und Größen anzeigen
|
||||
- [[IT-Know-How/Linux/BASH Basics/fd]] - Dateien schnell suchen und gesammelt verarbeiten
|
||||
- [[IT-Know-How/Linux/BASH Basics/find]] - Dateien suchen, filtern und verarbeiten (inkl. Datum, Löschen)
|
||||
- [[IT-Know-How/Linux/BASH Basics/Symlinks]] - Symbolische Links erstellen und verwalten
|
||||
|
||||
|
||||
@@ -0,0 +1,392 @@
|
||||
# fd - Dateien und Verzeichnisse schnell suchen
|
||||
|
||||
`fd` ist ein moderner, benutzerfreundlicher Ersatz für `find`. Die Standards
|
||||
sind für die meisten interaktiven Suchen praktischer: Die Suche ist rekursiv,
|
||||
Groß-/Kleinschreibung wird automatisch berücksichtigt, versteckte Dateien und
|
||||
von Git ignorierte Dateien werden standardmäßig ausgelassen und die Ausgabe ist
|
||||
farbig formatiert.
|
||||
|
||||
`fd` ist kein vollständiger Ersatz für jede komplexe `find`-Abfrage. Für
|
||||
interaktive Dateisuche ist es aber oft kürzer und übersichtlicher.
|
||||
|
||||
## Installation
|
||||
|
||||
Debian/Ubuntu:
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install fd-find
|
||||
```
|
||||
|
||||
Bei Debian und Ubuntu heißt das Programm je nach Distribution `fdfind` statt
|
||||
`fd`:
|
||||
|
||||
```bash
|
||||
fdfind --version
|
||||
alias fd=fdfind # nur für die aktuelle Shell
|
||||
```
|
||||
|
||||
Arch Linux:
|
||||
|
||||
```bash
|
||||
sudo pacman -S fd
|
||||
```
|
||||
|
||||
Fedora:
|
||||
|
||||
```bash
|
||||
sudo dnf install fd-find
|
||||
```
|
||||
|
||||
openSUSE:
|
||||
|
||||
```bash
|
||||
sudo zypper install fd
|
||||
```
|
||||
|
||||
Installation prüfen:
|
||||
|
||||
```bash
|
||||
fd --version
|
||||
fd --help
|
||||
```
|
||||
|
||||
## Grundsyntax
|
||||
|
||||
```bash
|
||||
fd [OPTIONEN] [MUSTER] [SUCHPFAD...]
|
||||
```
|
||||
|
||||
Ohne Argumente zeigt `fd` die Dateien und Verzeichnisse unterhalb des
|
||||
aktuellen Verzeichnisses:
|
||||
|
||||
```bash
|
||||
fd
|
||||
fd README.md # nach README.md suchen
|
||||
fd README ~/Projekte # in ~/Projekte suchen
|
||||
fd 'backup.*' /var/tmp # Suchmuster angeben
|
||||
```
|
||||
|
||||
Das Muster wird standardmäßig als regulärer Ausdruck interpretiert. Für eine
|
||||
wortgetreue Suche kann `-g` (Glob) verwendet werden.
|
||||
|
||||
## Nach Namen suchen
|
||||
|
||||
```bash
|
||||
fd report # enthält "report" im Namen
|
||||
fd -i report # ausdrücklich ohne Beachtung der Großschreibung
|
||||
fd -g '*.pdf' # Glob-Muster, alle PDF-Dateien
|
||||
fd -e pdf # nach der Endung pdf
|
||||
fd -e jpg -e png # mehrere Endungen
|
||||
fd -t f -e log # nur Dateien mit der Endung log
|
||||
fd -t d config # nur Verzeichnisse mit "config" im Namen
|
||||
fd -H '.env' # versteckte Dateien einbeziehen
|
||||
```
|
||||
|
||||
Bei Globs müssen Wildcards quotiert werden, damit die Shell sie nicht vorab
|
||||
expandiert. `-e pdf` ist meist angenehmer als `-g '*.pdf'`, weil die Endung
|
||||
ohne Punkt angegeben wird.
|
||||
|
||||
### Reguläre Ausdrücke
|
||||
|
||||
```bash
|
||||
fd '^README' # Namen, die mit README beginnen
|
||||
fd 'v[0-9]+\.tar\.gz$' # z. B. v1.tar.gz
|
||||
fd -I 'node_modules|target' # auch ignorierte Dateien suchen
|
||||
fd -g 'bild-??.jpg' # Glob: genau zwei Zeichen vor .jpg
|
||||
```
|
||||
|
||||
Mehrere Suchpfade sind möglich:
|
||||
|
||||
```bash
|
||||
fd -t f -e md ~/Dokumente ~/Projekte
|
||||
```
|
||||
|
||||
## Suchbereich einschränken
|
||||
|
||||
```bash
|
||||
fd -d 1 # nur direkt unterhalb des aktuellen Ordners
|
||||
fd -d 3 -e log # höchstens drei Ebenen tief
|
||||
fd -E node_modules # node_modules ausschließen
|
||||
fd -E '*.min.js' # Dateien per Glob ausschließen
|
||||
fd -E .git -E build # mehrere Ausschlüsse
|
||||
fd -p 'src/lib' # Muster als vollständigen Pfad behandeln
|
||||
fd -a report # absolute Pfade ausgeben
|
||||
```
|
||||
|
||||
Standardmäßig werden versteckte Pfade, `.gitignore`-Treffer und `.git`
|
||||
Verzeichnisse ausgelassen. Das Verhalten lässt sich mit diesen Optionen ändern:
|
||||
|
||||
```bash
|
||||
fd -H pattern # versteckte Dateien einbeziehen
|
||||
fd -I pattern # ignorierte Dateien einbeziehen
|
||||
fd -u pattern # unbeschränkte Suche: versteckt + ignoriert
|
||||
```
|
||||
|
||||
## Dateitypen und Eigenschaften
|
||||
|
||||
```bash
|
||||
fd -t f # reguläre Dateien
|
||||
fd -t d # Verzeichnisse
|
||||
fd -t l # symbolische Links
|
||||
fd -t x # ausführbare Dateien
|
||||
fd -t e # leere Dateien oder Verzeichnisse
|
||||
fd -S +100M -t f # Dateien größer als 100 MB
|
||||
fd -S -10k -t f # Dateien kleiner als 10 KB
|
||||
fd --changed-within 7d # in den letzten sieben Tagen geändert
|
||||
fd --changed-before 30d # vor mehr als 30 Tagen geändert
|
||||
```
|
||||
|
||||
Größen-Suffixe sind unter anderem `k`, `M`, `G` und `T`. Zeitangaben können
|
||||
beispielsweise `10m`, `2h`, `7d` oder `1w` sein. Die genaue Unterstützung hängt
|
||||
von der installierten `fd`-Version ab; `fd --help` zeigt die verfügbaren
|
||||
Optionen.
|
||||
|
||||
## Ausgabe und Zählen
|
||||
|
||||
```bash
|
||||
fd -l pattern # Long-Format wie bei ls
|
||||
fd --color never pattern # ohne ANSI-Farbcodes
|
||||
fd --print0 -e jpg # NUL-separiert, sicher für Sonderzeichen
|
||||
fd -e log | wc -l # Treffer zählen
|
||||
fd -e log | sort # Treffer sortieren
|
||||
fd -e log | xargs -r grep -n ERROR
|
||||
```
|
||||
|
||||
Für robuste Pipelines mit beliebigen Dateinamen immer `--print0` zusammen mit
|
||||
`xargs -0` verwenden:
|
||||
|
||||
```bash
|
||||
fd -0 -t f -e txt | xargs -0 -r wc -l
|
||||
```
|
||||
|
||||
## Befehle auf Treffer anwenden
|
||||
|
||||
`-x` führt einen Befehl für jeden Treffer aus. `{}` ist der Platzhalter für den
|
||||
aktuellen Pfad:
|
||||
|
||||
```bash
|
||||
fd -t f -e sh -x chmod +x '{}'
|
||||
fd -t f -e log -x gzip '{}'
|
||||
fd -t f -e txt -x grep -nH 'TODO' '{}'
|
||||
```
|
||||
|
||||
`-X` übergibt alle Treffer gemeinsam an den Befehl. Das ist für Programme
|
||||
geeignet, die mehrere Dateien in einem Aufruf verarbeiten können:
|
||||
|
||||
```bash
|
||||
fd -t f -e jpg -X identify
|
||||
fd -t f -e sh -X shellcheck
|
||||
```
|
||||
|
||||
Vor einer Aktion zuerst einen Dry-Run ausführen:
|
||||
|
||||
```bash
|
||||
fd -t f -e tmp -x echo rm -- '{}'
|
||||
fd -t f -e jpg -x echo cp -- '{}' ~/Bilder/
|
||||
```
|
||||
|
||||
`--` trennt Optionen des externen Befehls vom Dateinamen. Die Quoting-Regeln
|
||||
des Beispiels schützen auch Dateinamen mit Leerzeichen.
|
||||
|
||||
## Dateien kopieren, verschieben und löschen
|
||||
|
||||
### Löschen
|
||||
|
||||
```bash
|
||||
# Treffer prüfen
|
||||
fd -t f -e tmp
|
||||
|
||||
# Dry-Run
|
||||
fd -t f -e tmp -x echo rm -- '{}'
|
||||
|
||||
# Tatsächlich löschen
|
||||
fd -t f -e tmp -x rm -- '{}'
|
||||
```
|
||||
|
||||
Leere Verzeichnisse:
|
||||
|
||||
```bash
|
||||
fd -t d -e old -x echo rmdir -- '{}'
|
||||
fd -t d -e old -x rmdir -- '{}'
|
||||
```
|
||||
|
||||
`rmdir` löscht nur leere Verzeichnisse. Für nichtleere Verzeichnisse wäre
|
||||
`rm -r` erforderlich und sollte besonders vorsichtig eingesetzt werden.
|
||||
|
||||
### Kopieren
|
||||
|
||||
```bash
|
||||
mkdir -p ~/Archiv/logs
|
||||
fd -t f -e log -x echo cp -n -- '{}' ~/Archiv/logs/
|
||||
fd -t f -e log -x cp -n -- '{}' ~/Archiv/logs/
|
||||
```
|
||||
|
||||
`cp -n` verhindert das Überschreiben vorhandener Dateien. Für das Beibehalten
|
||||
von Zeitstempeln und Berechtigungen kann `cp -p` ergänzt werden.
|
||||
|
||||
### Verschieben
|
||||
|
||||
```bash
|
||||
mkdir -p ~/Archiv/alte-logs
|
||||
fd -t f -e log --changed-before 30d -x echo mv -i -- '{}' ~/Archiv/alte-logs/
|
||||
fd -t f -e log --changed-before 30d -x mv -i -- '{}' ~/Archiv/alte-logs/
|
||||
```
|
||||
|
||||
`mv -i` fragt vor dem Überschreiben nach. Mit `mv -n` werden bestehende
|
||||
Zieldateien ohne Rückfrage nicht ersetzt.
|
||||
|
||||
## Dateien gesammelt umbenennen
|
||||
|
||||
Für einfache Ersetzungen ist `mv` mit Shell-Parameterexpansion ausreichend.
|
||||
Zuerst die geplanten Befehle anzeigen:
|
||||
|
||||
```bash
|
||||
fd -t f -e jpeg -x sh -c 'for f do printf "mv -- %q %q\\n" "$f" "${f%.jpeg}.jpg"; done' sh
|
||||
```
|
||||
|
||||
Wenn das Ergebnis stimmt:
|
||||
|
||||
```bash
|
||||
fd -t f -e jpeg -x sh -c 'for f do mv -i -- "$f" "${f%.jpeg}.jpg"; done' sh
|
||||
```
|
||||
|
||||
Weitere Beispiele für Namensänderungen:
|
||||
|
||||
```bash
|
||||
# Suffix _alt ergänzen
|
||||
fd -t f -e txt -x sh -c 'for f do mv -i -- "$f" "${f%.txt}_alt.txt"; done' sh
|
||||
|
||||
# Präfix backup- ergänzen
|
||||
fd -t f -e conf -x sh -c 'for f do mv -i -- "$f" "$(dirname "$f")/backup-$(basename "$f")"; done' sh
|
||||
```
|
||||
|
||||
Bei vielen Umbenennungen ist `-x` mit einer kleinen Shell-Schleife sicherer
|
||||
als eine Pipe, weil Dateinamen nicht an Leerzeichen oder Zeilenumbrüchen
|
||||
aufgeteilt werden.
|
||||
|
||||
## Bulk-Bearbeitung von Dateien
|
||||
|
||||
### Textdateien mit sed bearbeiten
|
||||
|
||||
```bash
|
||||
# Erst anzeigen, welche Dateien betroffen sind
|
||||
fd -t f -e conf -e cfg
|
||||
|
||||
# Dry-Run: Ausgabe prüfen, Dateien bleiben unverändert
|
||||
fd -t f -e conf -e cfg -x sh -c 'for f do sed "s/alt/neu/g" "$f" > "$f.tmp" && diff -u "$f" "$f.tmp"; rm -- "$f.tmp"; done' sh
|
||||
|
||||
# In-place mit Backup-Datei
|
||||
fd -t f -e conf -e cfg -x sed -i.bak 's/alt/neu/g' '{}'
|
||||
```
|
||||
|
||||
### Dateinamen mit Textsuche kombinieren
|
||||
|
||||
```bash
|
||||
fd -t f -e md -x rg -nH 'TODO|FIXME' '{}'
|
||||
fd -t f -e log -x sh -c 'for f do grep -l ERROR "$f"; done' sh
|
||||
```
|
||||
|
||||
### Dateien anhand des Namens einsortieren
|
||||
|
||||
```bash
|
||||
mkdir -p ~/Sortiert/{pdf,bilder}
|
||||
|
||||
# Dry-Run
|
||||
fd -t f -e pdf -x echo mv -i -- '{}' ~/Sortiert/pdf/
|
||||
fd -t f -e jpg -e png -x echo mv -i -- '{}' ~/Sortiert/bilder/
|
||||
|
||||
# Ausführen
|
||||
fd -t f -e pdf -x mv -i -- '{}' ~/Sortiert/pdf/
|
||||
fd -t f -e jpg -e png -x mv -i -- '{}' ~/Sortiert/bilder/
|
||||
```
|
||||
|
||||
### Robuste Schleife mit NUL-Trennung
|
||||
|
||||
Wenn zusätzlich komplexe Shell-Logik oder mehrere Variablen benötigt werden,
|
||||
ist eine NUL-separierte Schleife eine gute Alternative:
|
||||
|
||||
```bash
|
||||
while IFS= read -r -d '' file; do
|
||||
printf 'Bearbeite: %s\n' "$file"
|
||||
# command -- "$file"
|
||||
done < <(fd -0 -t f -e txt)
|
||||
```
|
||||
|
||||
## Praktische Anwendungsfälle
|
||||
|
||||
```bash
|
||||
# Die zehn größten Dateien unterhalb des aktuellen Ordners
|
||||
fd -t f -S +1M -x du -h '{}' | sort -rh | head -10
|
||||
|
||||
# Alle ausführbaren Shell-Skripte finden
|
||||
fd -t x -e sh
|
||||
|
||||
# Backups und temporäre Dateien ausschließen
|
||||
fd -t f -E '*.bak' -E '*.tmp' -E .cache
|
||||
|
||||
# Markdown-Dateien der letzten Woche archivieren
|
||||
fd -t f -e md --changed-within 7d -X tar -czf aktuelle-doku.tar.gz
|
||||
|
||||
# Prüfsummen für ISO-Dateien erzeugen
|
||||
fd -t f -e iso -x sha256sum '{}'
|
||||
```
|
||||
|
||||
Bei `fd -X` werden die Treffer als Argumente an `tar` übergeben. Für sehr
|
||||
große Trefferlisten kann ein Befehl durch die Systemgrenze für Argumente
|
||||
begrenzt sein; dann besser `fd -0` mit `xargs -0` oder `tar`-Optionen für
|
||||
Dateilisten verwenden.
|
||||
|
||||
## Vergleich mit find
|
||||
|
||||
| Aufgabe | `find` | `fd` |
|
||||
|---------|--------|------|
|
||||
| Alle Logs finden | `find . -type f -name '*.log'` | `fd -t f -e log` |
|
||||
| Verzeichnis suchen | `find . -type d -name 'src'` | `fd -t d src` |
|
||||
| Tiefe begrenzen | `find . -maxdepth 2` | `fd -d 2` |
|
||||
| Verzeichnis ausschließen | `find . -path './node_modules' -prune` | `fd -E node_modules` |
|
||||
| Befehl ausführen | `find . -type f -exec cmd {} +` | `fd -t f -X cmd` |
|
||||
| Änderungszeit | `find . -mtime -7` | `fd --changed-within 7d` |
|
||||
|
||||
Komplexe Bedingungen nach Besitzer, Berechtigungen oder exakten Zeitstempeln
|
||||
bleiben eine Stärke von `find`.
|
||||
|
||||
## Wichtige Optionen
|
||||
|
||||
| Option | Bedeutung |
|
||||
|--------|-----------|
|
||||
| `-t f` | nur reguläre Dateien |
|
||||
| `-t d` | nur Verzeichnisse |
|
||||
| `-t l` | nur symbolische Links |
|
||||
| `-e EXT` | Dateiendung suchen |
|
||||
| `-g` | Glob statt regulärem Ausdruck verwenden |
|
||||
| `-i` | Groß-/Kleinschreibung ignorieren |
|
||||
| `-H` | versteckte Dateien einbeziehen |
|
||||
| `-I` | Git-ignorierte Dateien einbeziehen |
|
||||
| `-u` | versteckte und ignorierte Dateien einbeziehen |
|
||||
| `-d N` | maximale Tiefe |
|
||||
| `-E PATH` | Pfad oder Muster ausschließen |
|
||||
| `-a` | absolute Pfade ausgeben |
|
||||
| `-0` / `--print0` | NUL-separierte Ausgabe |
|
||||
| `-x CMD` | Befehl pro Treffer ausführen |
|
||||
| `-X CMD` | Befehl mit allen Treffern ausführen |
|
||||
| `-S SIZE` | nach Dateigröße filtern |
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Zuerst nur `fd ...` ausführen und die Treffer kontrollieren.
|
||||
2. Vor Änderungen `-x echo ...` als Dry-Run verwenden.
|
||||
3. Dateipfade immer quoten: `"$file"` oder `'{}'`.
|
||||
4. Bei Pipes für beliebige Dateinamen `-0` und `xargs -0` verwenden.
|
||||
5. Bei `rm` und `mv` `--` vor dem Dateipfad setzen.
|
||||
6. Für Lösch-, Verschiebe- und Umbenennungsaktionen Backups oder Versionskontrolle nutzen.
|
||||
7. Mit `fd --help` prüfen, welche Optionen die installierte Version unterstützt.
|
||||
|
||||
## Siehe auch
|
||||
|
||||
- [[IT-Know-How/Linux/BASH Basics/find]] - Klassische Dateisuche mit find
|
||||
- [[IT-Know-How/Linux/BASH Basics/grep]] - Textsuche in Dateien
|
||||
- [[IT-Know-How/Linux/BASH Basics/sed]] - Textdateien gesammelt bearbeiten
|
||||
- [[IT-Know-How/Linux/BASH Basics/awk]] - Textverarbeitung und Datenextraktion
|
||||
- [[IT-Know-How/Linux/BASH Basics/Dateiberechtigungen]] - chmod und chown
|
||||
- [[IT-Know-How/Linux/BASH Basics/rsync]] - Dateien synchronisieren
|
||||
@@ -99,3 +99,58 @@ ssh benutzername@192.168.178.42
|
||||
---
|
||||
|
||||
👉 Soll ich dir ein **kurzes Beispiel-Setup mit zwei Testbenutzern** schreiben, das du direkt im Heimnetz nachmachen kannst?
|
||||
|
||||
---
|
||||
|
||||
## 5. SSH-Tunnel für lokale Datenbankzugriffe
|
||||
|
||||
Mit einem lokalen SSH-Tunnel (`-L`) wird ein Port auf dem eigenen Rechner an
|
||||
einen Zielhost und Zielport weitergeleitet, die aus Sicht des SSH-Servers
|
||||
erreichbar sind:
|
||||
|
||||
```bash
|
||||
ssh -L <lokaler-port>:<zielhost>:<zielport> <benutzer>@<ssh-server>
|
||||
```
|
||||
|
||||
Beispiel für PostgreSQL:
|
||||
|
||||
```bash
|
||||
ssh -L 15432:127.0.0.1:5432 benutzername@vm.example.org
|
||||
```
|
||||
|
||||
Danach kann ein lokaler Client über `127.0.0.1:15432` auf PostgreSQL zugreifen.
|
||||
Die Verbindung verläuft dabei so:
|
||||
|
||||
```text
|
||||
Datenbank-Client auf dem eigenen Rechner
|
||||
|
|
||||
| 127.0.0.1:15432
|
||||
v
|
||||
SSH-Tunnel zur VM
|
||||
|
|
||||
| 127.0.0.1:5432 aus Sicht der VM
|
||||
v
|
||||
PostgreSQL auf der VM
|
||||
```
|
||||
|
||||
`<zielhost>` wird aus Sicht der VM aufgelöst. Wenn PostgreSQL in einem
|
||||
Docker-Container läuft, kann das je nach Setup beispielsweise `127.0.0.1`,
|
||||
ein Docker-Service-Name oder eine Container-IP sein. Eine Container-IP wie
|
||||
`172.19.0.2` sollte nicht dauerhaft vorausgesetzt werden, weil sie sich nach
|
||||
dem Neuerstellen des Containers ändern kann. Siehe auch
|
||||
[[DBeaver PostgreSQL via SSH-Tunnel]] und [[Docker]].
|
||||
|
||||
### Adressen nicht verwechseln
|
||||
|
||||
`hostname -I` gibt alle Adressen des Rechners aus. Darunter können sich auch
|
||||
Docker-Bridge-Adressen befinden. Die Ausgabe zeigt nicht, welche Adresse für
|
||||
einen bestimmten Dienst die richtige ist:
|
||||
|
||||
```text
|
||||
10.2.0.4 Adresse der VM im Azure-Netzwerk
|
||||
172.19.0.1 Docker-Bridge-Gateway
|
||||
172.19.0.2 möglicherweise eine Container-IP
|
||||
```
|
||||
|
||||
Die tatsächlich passende Adresse und der passende Port sollten mit `ss`,
|
||||
`docker inspect`, `nc` oder `psql` geprüft werden.
|
||||
|
||||
@@ -28,3 +28,60 @@ docker exec -i <container_name> psql -U <username> -d <dbname> -A -F, --csv -c "
|
||||
```
|
||||
Erklärung:
|
||||
>The `-A` flag (unaligned output), `-F,` (field separator comma), and `--csv` together produce CSV
|
||||
|
||||
## Verbindung zu einem bestimmten Host und Port
|
||||
|
||||
Für eine Verbindung zu einem PostgreSQL-Server auf einem bestimmten Host und
|
||||
Port werden `-h` und `-p` verwendet:
|
||||
|
||||
```bash
|
||||
psql -h <host> -p <port> -U <username> -d <dbname>
|
||||
```
|
||||
|
||||
Beispiel:
|
||||
|
||||
```bash
|
||||
psql -h 127.0.0.1 -p 15432 -U <username> -d <dbname>
|
||||
```
|
||||
|
||||
Das ist beispielsweise der lokale Endpunkt eines SSH-Tunnels. Bei einem
|
||||
Docker-Setup kann der Host dagegen eine Container-IP, ein Docker-Service-Name
|
||||
oder die Loopback-Adresse der VM sein. Entscheidend ist, von wo aus die
|
||||
Verbindung hergestellt wird.
|
||||
|
||||
## PostgreSQL-Verbindung diagnostizieren
|
||||
|
||||
Eine schrittweise Prüfung hilft dabei, Netzwerk-, Tunnel- und
|
||||
Authentifizierungsprobleme voneinander zu unterscheiden:
|
||||
|
||||
```bash
|
||||
# Ist der TCP-Port erreichbar?
|
||||
nc -vz <host> <port>
|
||||
|
||||
# Lauscht auf der VM ein Prozess auf dem PostgreSQL-Port?
|
||||
sudo ss -ltnp | grep 5432
|
||||
|
||||
# Funktioniert die PostgreSQL-Verbindung mit denselben Parametern?
|
||||
psql -h <host> -p 5432 -U <username> -d <dbname>
|
||||
```
|
||||
|
||||
Wenn `psql` direkt auf der VM funktioniert, ist damit nur bewiesen, dass die
|
||||
VM diesen Endpunkt erreichen kann. Es ist noch nicht bewiesen, dass ein
|
||||
externer Client denselben Weg über den SSH-Tunnel verwendet. Zu prüfen sind
|
||||
deshalb zusätzlich der lokale Tunnel-Port, der entfernte Zielhost, der
|
||||
entfernte Zielport und die SSL-Einstellung des Clients.
|
||||
|
||||
Für eine Docker-Datenbank sind außerdem hilfreich:
|
||||
|
||||
```bash
|
||||
docker ps
|
||||
docker inspect <postgres-container>
|
||||
docker logs --tail 100 <postgres-container>
|
||||
```
|
||||
|
||||
Eine Container-IP kann sich ändern, wenn der Container neu erstellt wird. Für
|
||||
dauerhafte Konfigurationen sind veröffentlichte Host-Ports oder Docker-
|
||||
Service-Namen meist besser geeignet als eine fest eingetragene Container-IP.
|
||||
|
||||
Siehe auch [[SSH Verbindung in Linux]], [[Docker]] und
|
||||
[[DBeaver PostgreSQL via SSH-Tunnel]].
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
# PostgreSQL mit DBeaver über einen SSH-Tunnel
|
||||
|
||||
Ein SSH-Tunnel erlaubt den Zugriff auf eine Datenbank, die nur von einer
|
||||
entfernten VM aus erreichbar ist. Der Tunnel löst dabei nicht automatisch alle
|
||||
Netzwerkprobleme: Der Zielhost und Zielport werden aus Sicht der VM bewertet.
|
||||
|
||||
## Verbindungsweg
|
||||
|
||||
```text
|
||||
DBeaver auf dem eigenen Rechner
|
||||
|
|
||||
| lokaler Tunnel-Endpunkt
|
||||
v
|
||||
SSH-Client ───────────────► SSH-Server auf der VM
|
||||
|
|
||||
| Zielhost:Zielport
|
||||
v
|
||||
PostgreSQL auf der VM
|
||||
oder PostgreSQL-Container
|
||||
```
|
||||
|
||||
Ein möglicher manueller Tunnel sieht so aus:
|
||||
|
||||
```bash
|
||||
ssh -L 15432:127.0.0.1:5432 <user>@<vm-host>
|
||||
```
|
||||
|
||||
Danach ist die Datenbank vom eigenen Rechner aus typischerweise über
|
||||
`127.0.0.1:15432` erreichbar. Bei DBeaver hängen die genauen Felder von der
|
||||
verwendeten SSH-Tunnel-Konfiguration ab. Wichtig ist, die lokale
|
||||
Datenbankadresse nicht mit dem entfernten Ziel der Weiterleitung zu
|
||||
verwechseln.
|
||||
|
||||
## Docker als Datenbank-Host
|
||||
|
||||
Wenn PostgreSQL in Docker läuft, gibt es mehrere mögliche Ziele:
|
||||
|
||||
| Situation | Typisches Ziel aus Sicht der VM |
|
||||
| --- | --- |
|
||||
| PostgreSQL läuft direkt auf der VM | `127.0.0.1:5432` |
|
||||
| PostgreSQL-Port ist veröffentlicht | `127.0.0.1:<veröffentlichter Port>` |
|
||||
| Zugriff aus einem anderen Container | Compose-Service-Name, z. B. `postgres:5432` |
|
||||
| Direkter Zugriff auf einen Container | aktuelle Container-IP, z. B. `172.19.0.2:5432` |
|
||||
|
||||
Eine Container-IP sollte nur zum Testen verwendet werden. Sie kann sich nach
|
||||
dem Neustart oder Neuerstellen des Containers ändern. Für eine dauerhafte
|
||||
Konfiguration sind ein Compose-Service-Name innerhalb des Docker-Netzwerks
|
||||
oder ein veröffentlichter Host-Port vorzuziehen.
|
||||
|
||||
## Schrittweise Diagnose
|
||||
|
||||
### 1. Den aktuellen Container und seine Ports prüfen
|
||||
|
||||
Auf der VM:
|
||||
|
||||
```bash
|
||||
docker ps
|
||||
docker inspect <postgres-container>
|
||||
sudo ss -ltnp | grep 5432
|
||||
```
|
||||
|
||||
Bei `docker ps` auf die Spalte `PORTS` achten. Eine Ausgabe wie
|
||||
`127.0.0.1:5432->5432/tcp` bedeutet, dass PostgreSQL auf der VM lokal
|
||||
veröffentlicht ist. Eine Ausgabe wie `0.0.0.0:15432->5432/tcp` bedeutet, dass
|
||||
der Host-Port `15432` zum Container-Port `5432` weiterleitet.
|
||||
|
||||
### 2. Den Zielendpunkt von der VM aus testen
|
||||
|
||||
```bash
|
||||
nc -vz <zielhost> <zielport>
|
||||
psql -h <zielhost> -p <zielport> -U <user> -d <database>
|
||||
```
|
||||
|
||||
Diese Tests müssen mit dem Host und Port durchgeführt werden, die der
|
||||
SSH-Tunnel aus Sicht der VM erreichen soll. Ein funktionierendes `psql` ohne
|
||||
`-h` kann beispielsweise über einen lokalen Unix-Socket verbinden und sagt
|
||||
nichts über die TCP-Verbindung aus.
|
||||
|
||||
### 3. PostgreSQL-Logs beobachten
|
||||
|
||||
```bash
|
||||
docker logs --tail 100 -f <postgres-container>
|
||||
```
|
||||
|
||||
Dann in DBeaver den Verbindungstest ausführen:
|
||||
|
||||
- **Kein neuer Logeintrag:** DBeaver erreicht PostgreSQL nicht. Tunnel,
|
||||
Zielhost, Zielport oder lokale/entfernte Portzuordnung prüfen.
|
||||
- **Eintrag mit Authentifizierungsfehler:** Netzwerkweg funktioniert; Benutzer,
|
||||
Passwort, Datenbank oder `pg_hba.conf` prüfen.
|
||||
- **Verbindung wird sofort beendet:** SSL-Modus, PostgreSQL-Treiber und
|
||||
Tunnelziel vergleichen.
|
||||
|
||||
## Typische Fehler
|
||||
|
||||
### `hostname -I` liefert mehrere Adressen
|
||||
|
||||
`hostname -I` zeigt alle Adressen der VM. Dazu können die Azure-Adresse und
|
||||
mehrere Docker-Bridge-Adressen gehören. Die Ausgabe ist keine Liste von
|
||||
gleichwertigen Datenbankadressen:
|
||||
|
||||
```text
|
||||
10.2.0.4 VM-Adresse im privaten Azure-Netzwerk
|
||||
172.19.0.1 Docker-Bridge-Gateway
|
||||
172.19.0.2 möglicherweise PostgreSQL-Container
|
||||
```
|
||||
|
||||
Mit `docker network inspect <network-name>` lässt sich prüfen, welchem
|
||||
Container eine Docker-Adresse aktuell zugeordnet ist.
|
||||
|
||||
### SSH funktioniert, DBeaver meldet `EOF Exception`
|
||||
|
||||
Ein erfolgreicher SSH-Aufbau beweist nur, dass der SSH-Server erreichbar ist.
|
||||
Der weitergeleitete TCP-Stream kann trotzdem am falschen Ziel oder Port enden.
|
||||
Prüfen:
|
||||
|
||||
1. Welcher lokale Port wird von DBeaver verwendet?
|
||||
2. Welcher entfernte Host und Port werden durch den Tunnel angesprochen?
|
||||
3. Ist der PostgreSQL-Container noch unter derselben IP erreichbar?
|
||||
4. Lauscht PostgreSQL auf TCP und nicht nur auf einem Unix-Socket?
|
||||
5. Verwendet DBeaver denselben SSL-Modus wie `psql`?
|
||||
6. Erscheint der Test in den PostgreSQL-Logs?
|
||||
|
||||
Die Verbindung sollte zunächst mit einer expliziten TCP-Verbindung geprüft
|
||||
werden:
|
||||
|
||||
```bash
|
||||
psql -h 127.0.0.1 -p <port> -U <user> -d <database>
|
||||
```
|
||||
|
||||
Dabei muss `<port>` dem tatsächlich veröffentlichten Port oder dem lokalen
|
||||
Port des SSH-Tunnels entsprechen.
|
||||
|
||||
## Sicherheits- und Wartungshinweise
|
||||
|
||||
- Keine Passwörter oder produktiven Zugangsdaten in Notizen oder
|
||||
Verbindungsdefinitionen im Klartext speichern.
|
||||
- Container-IPs nicht als langfristige Infrastrukturkonfiguration verwenden.
|
||||
- Für dauerhafte Zugriffe DNS-Namen, Compose-Service-Namen oder stabile
|
||||
Host-Port-Zuordnungen bevorzugen.
|
||||
- Für einen Diagnosefall die verwendeten Hosts, Ports, SSL-Einstellungen und
|
||||
den Verbindungsweg dokumentieren, aber sensible Werte anonymisieren.
|
||||
|
||||
Siehe auch [[SSH Verbindung in Linux]], [[psql]] und [[Docker]].
|
||||
@@ -0,0 +1,473 @@
|
||||
# Spickzettel: Gestapelten Feature-Branch nach einem Squash-Merge aktualisieren
|
||||
|
||||
Ausgangssituation:
|
||||
|
||||
```text
|
||||
main: A----------------S
|
||||
\ /
|
||||
feature1: B------------C
|
||||
\
|
||||
feature2: D---E
|
||||
```
|
||||
|
||||
- `feature1` wurde von `main` abgezweigt.
|
||||
- `feature2` wurde von `feature1` abgezweigt.
|
||||
- `feature1` wurde anschließend per **Squash-Merge** in `main` integriert.
|
||||
- `feature2` enthält sowohl die Änderungen von `feature1` als auch eigene Änderungen.
|
||||
- Jetzt soll `feature2` auf dem aktuellen `main` basieren und nur noch seine eigenen Änderungen zusätzlich enthalten.
|
||||
|
||||
Ziel:
|
||||
|
||||
```text
|
||||
main: A----------------S
|
||||
\
|
||||
feature2: D'---E'
|
||||
```
|
||||
|
||||
Die Commits `D` und `E` werden dabei als neue Commits `D'` und `E'` auf die neue Basis gesetzt.
|
||||
|
||||
---
|
||||
|
||||
## 1. Arbeitsstand prüfen
|
||||
|
||||
Zuerst sicherstellen, dass keine ungesicherten Änderungen vorhanden sind:
|
||||
|
||||
```bash
|
||||
git status
|
||||
```
|
||||
|
||||
Idealerweise lautet die Ausgabe sinngemäß:
|
||||
|
||||
```text
|
||||
nothing to commit, working tree clean
|
||||
```
|
||||
|
||||
Falls noch Änderungen vorhanden sind, entweder committen oder vorübergehend weglegen:
|
||||
|
||||
```bash
|
||||
git stash push -m "temporäre Änderungen vor Rebase"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Remote-Branches aktualisieren
|
||||
|
||||
```bash
|
||||
git fetch origin
|
||||
```
|
||||
|
||||
Damit werden beispielsweise diese Referenzen aktualisiert:
|
||||
|
||||
```text
|
||||
origin/main
|
||||
origin/feature1
|
||||
origin/feature2
|
||||
```
|
||||
|
||||
Wichtig: `origin/main` ist der aktuelle Stand des Remote-Branches. Er enthält jetzt den Squash-Commit von `feature1`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Prüfen, auf welchem Branch man arbeitet
|
||||
|
||||
```bash
|
||||
git switch feature2
|
||||
```
|
||||
|
||||
Anschließend:
|
||||
|
||||
```bash
|
||||
git status
|
||||
```
|
||||
|
||||
Es sollte erkennbar sein, dass man sich auf `feature2` befindet.
|
||||
|
||||
Optional kann man sich die Historie grafisch ansehen:
|
||||
|
||||
```bash
|
||||
git log --oneline --graph --decorate --all
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Den alten Basis-Commit identifizieren
|
||||
|
||||
Für den Rebase muss Git wissen:
|
||||
|
||||
> Ab welchem Commit beginnen die Änderungen, die ausschließlich zu `feature2` gehören?
|
||||
|
||||
Wenn der Branch `feature1` noch existiert und unverändert auf seinem ursprünglichen Stand steht, ist der Standardfall:
|
||||
|
||||
```bash
|
||||
git rebase --onto origin/main origin/feature1 feature2
|
||||
```
|
||||
|
||||
Das bedeutet:
|
||||
|
||||
```text
|
||||
git rebase --onto <neue-Basis> <alte-Basis> <Branch>
|
||||
```
|
||||
|
||||
Konkret:
|
||||
|
||||
- `origin/main`: neue Basis
|
||||
- `origin/feature1`: alte Basis, die nicht erneut übernommen werden soll
|
||||
- `feature2`: Branch, dessen eigene Commits neu aufgesetzt werden
|
||||
|
||||
Git nimmt also alle Commits, die in `feature2`, aber nicht in `feature1` enthalten sind, und wendet sie auf `origin/main` an.
|
||||
|
||||
Das ist in der Regel genau das gewünschte Verhalten.
|
||||
|
||||
---
|
||||
|
||||
## 5. Vorher ein Sicherheitsbackup anlegen
|
||||
|
||||
Ein Rebase schreibt die Branch-Historie um. Deshalb ist ein Backup empfehlenswert:
|
||||
|
||||
```bash
|
||||
git switch feature2
|
||||
git branch backup/feature2-vor-rebase
|
||||
```
|
||||
|
||||
Falls etwas schiefgeht, kann man jederzeit auf diesen Stand zurückgreifen:
|
||||
|
||||
```bash
|
||||
git switch backup/feature2-vor-rebase
|
||||
```
|
||||
|
||||
Oder den ursprünglichen Stand wiederherstellen:
|
||||
|
||||
```bash
|
||||
git switch feature2
|
||||
git reset --hard backup/feature2-vor-rebase
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Rebase durchführen
|
||||
|
||||
Mit weiterhin ausgechecktem `feature2`:
|
||||
|
||||
```bash
|
||||
git rebase --onto origin/main origin/feature1
|
||||
```
|
||||
|
||||
Die Kurzform funktioniert hier, weil Git den aktuell ausgecheckten Branch verwendet.
|
||||
|
||||
Alternativ explizit:
|
||||
|
||||
```bash
|
||||
git rebase --onto origin/main origin/feature1 feature2
|
||||
```
|
||||
|
||||
Nach dem erfolgreichen Rebase sieht die Historie ungefähr so aus:
|
||||
|
||||
```text
|
||||
main: A----------------S
|
||||
\
|
||||
feature2: D'---E'
|
||||
```
|
||||
|
||||
Die Änderungen aus `feature1` werden nicht noch einmal als eigene Commits übernommen. Sie sind bereits im Squash-Commit auf `main` enthalten.
|
||||
|
||||
---
|
||||
|
||||
# Falls Konflikte auftreten
|
||||
|
||||
Konflikte sind bei diesem Vorgang möglich, insbesondere wenn:
|
||||
|
||||
- der Squash-Merge in GitLab manuell angepasst wurde,
|
||||
- `feature2` dieselben Dateien wie `feature1` weiter verändert,
|
||||
- sich `main` seit dem Merge weiterentwickelt hat.
|
||||
|
||||
Git zeigt die betroffenen Dateien an:
|
||||
|
||||
```bash
|
||||
git status
|
||||
```
|
||||
|
||||
Die Konfliktmarkierungen sehen beispielsweise so aus:
|
||||
|
||||
```text
|
||||
<<<<<<< HEAD
|
||||
Stand von main
|
||||
=======
|
||||
Stand aus feature2
|
||||
>>>>>>> <commit>
|
||||
```
|
||||
|
||||
Die Datei muss manuell in den gewünschten Endzustand gebracht werden. Danach:
|
||||
|
||||
```bash
|
||||
git add pfad/zur/datei
|
||||
```
|
||||
|
||||
Wenn alle Konflikte des aktuellen Rebase-Schritts gelöst sind:
|
||||
|
||||
```bash
|
||||
git rebase --continue
|
||||
```
|
||||
|
||||
Git kann anschließend weitere Konflikte melden. Dann wiederholt man:
|
||||
|
||||
```bash
|
||||
git status
|
||||
# Dateien bearbeiten
|
||||
git add <dateien>
|
||||
git rebase --continue
|
||||
```
|
||||
|
||||
## Einen Rebase abbrechen
|
||||
|
||||
Wenn der Rebase nicht sinnvoll fortgesetzt werden kann:
|
||||
|
||||
```bash
|
||||
git rebase --abort
|
||||
```
|
||||
|
||||
Damit wird `feature2` auf den Zustand vor Beginn des Rebases zurückgesetzt.
|
||||
|
||||
---
|
||||
|
||||
# 7. Ergebnis überprüfen
|
||||
|
||||
Nach dem Rebase zunächst die Historie ansehen:
|
||||
|
||||
```bash
|
||||
git log --oneline --graph --decorate -20
|
||||
```
|
||||
|
||||
Außerdem prüfen, welche Änderungen `feature2` gegenüber `main` enthält:
|
||||
|
||||
```bash
|
||||
git diff origin/main...feature2
|
||||
```
|
||||
|
||||
Das sollte im Wesentlichen nur die Änderungen des zweiten Features zeigen.
|
||||
|
||||
Auch die Commit-Liste kann geprüft werden:
|
||||
|
||||
```bash
|
||||
git log --oneline origin/main..feature2
|
||||
```
|
||||
|
||||
Dort sollten nur die eigenen Commits von `feature2` auftauchen, nicht mehr die ursprünglichen Commits von `feature1`.
|
||||
|
||||
Zusätzlich:
|
||||
|
||||
```bash
|
||||
git status
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 8. Den umgeschriebenen Branch pushen
|
||||
|
||||
Da ein Rebase die Commit-IDs ändert, ist ein normaler Push meist nicht möglich. Verwenden Sie:
|
||||
|
||||
```bash
|
||||
git push --force-with-lease origin feature2
|
||||
```
|
||||
|
||||
Nicht bevorzugt:
|
||||
|
||||
```bash
|
||||
git push --force origin feature2
|
||||
```
|
||||
|
||||
`--force-with-lease` ist sicherer, weil Git den Push ablehnt, wenn zwischenzeitlich jemand anderes neue Änderungen auf `origin/feature2` veröffentlicht hat.
|
||||
|
||||
Danach sollte der Merge Request von `feature2` nach `main` aktualisiert sein. Im Diff sollten nun normalerweise nur noch die Änderungen des zweiten Features erscheinen.
|
||||
|
||||
---
|
||||
|
||||
# Der wichtigste Standardbefehl
|
||||
|
||||
Wenn `feature1` noch existiert und auf dem alten Stand steht:
|
||||
|
||||
```bash
|
||||
git fetch origin
|
||||
git switch feature2
|
||||
git branch backup/feature2-vor-rebase
|
||||
git rebase --onto origin/main origin/feature1
|
||||
git push --force-with-lease origin feature2
|
||||
```
|
||||
|
||||
Dabei sollte man den Rebase-Schritt nicht blind überspringen, sondern Konflikte und den anschließenden Diff prüfen.
|
||||
|
||||
---
|
||||
|
||||
# Wenn `feature1` bereits gelöscht wurde
|
||||
|
||||
Wenn der Remote-Branch `feature1` nach dem Merge gelöscht wurde, funktioniert dieser Befehl nicht mehr:
|
||||
|
||||
```bash
|
||||
git rebase --onto origin/main origin/feature1
|
||||
```
|
||||
|
||||
Dann braucht man den alten Stand von `feature1`, also den Commit, auf dem `feature2` ursprünglich basierte.
|
||||
|
||||
## Möglichkeit A: Lokale Referenz vorhanden
|
||||
|
||||
Prüfen:
|
||||
|
||||
```bash
|
||||
git branch -a
|
||||
```
|
||||
|
||||
Vielleicht existiert noch ein lokaler Branch:
|
||||
|
||||
```text
|
||||
feature1
|
||||
```
|
||||
|
||||
Dann kann man verwenden:
|
||||
|
||||
```bash
|
||||
git rebase --onto origin/main feature1 feature2
|
||||
```
|
||||
|
||||
## Möglichkeit B: Commit aus der Historie verwenden
|
||||
|
||||
Mit der Historie:
|
||||
|
||||
```bash
|
||||
git log --oneline --graph --all
|
||||
```
|
||||
|
||||
den letzten Commit von `feature1` vor den eigenen `feature2`-Commits suchen.
|
||||
|
||||
Angenommen, dieser Commit heißt:
|
||||
|
||||
```text
|
||||
abc1234
|
||||
```
|
||||
|
||||
Dann:
|
||||
|
||||
```bash
|
||||
git rebase --onto origin/main abc1234 feature2
|
||||
```
|
||||
|
||||
Bedeutung:
|
||||
|
||||
> Alle Commits nach `abc1234`, die zu `feature2` gehören, auf `origin/main` setzen.
|
||||
|
||||
## Möglichkeit C: Reflog verwenden
|
||||
|
||||
Wenn der Commit nicht mehr über die normale Historie sichtbar ist:
|
||||
|
||||
```bash
|
||||
git reflog
|
||||
```
|
||||
|
||||
Dort kann man nach dem ursprünglichen Abzweigpunkt oder dem früheren Stand von `feature1` suchen.
|
||||
|
||||
---
|
||||
|
||||
# Variante ohne `rebase --onto`: eigene Commits per Cherry-Pick übernehmen
|
||||
|
||||
Wenn die Branch-Historie unklar ist, kann man auch einen neuen Branch auf Basis von `main` erstellen und nur die eigenen `feature2`-Commits übernehmen.
|
||||
|
||||
Beispiel:
|
||||
|
||||
```bash
|
||||
git fetch origin
|
||||
git switch -c feature2-neu origin/main
|
||||
git cherry-pick <commit-D>^..<commit-E>
|
||||
```
|
||||
|
||||
Dabei werden die eigenen Commits von `feature2` einzeln übernommen.
|
||||
|
||||
Danach kann der neue Branch entweder als neuer Merge Request verwendet oder nach sorgfältiger Prüfung an die Stelle von `feature2` gepusht werden.
|
||||
|
||||
Diese Variante ist nützlich, wenn:
|
||||
|
||||
- `feature1` gelöscht wurde,
|
||||
- die ursprüngliche Branch-Basis nicht mehr eindeutig ist,
|
||||
- `feature2` nur wenige eigene Commits enthält.
|
||||
|
||||
---
|
||||
|
||||
# Was man nicht tun sollte
|
||||
|
||||
Nach einem Squash-Merge von `feature1` ist dieser Ablauf problematisch:
|
||||
|
||||
```bash
|
||||
git switch feature2
|
||||
git merge origin/main
|
||||
```
|
||||
|
||||
Das kann funktionieren, muss aber nicht. Git sieht die ursprünglichen Commits von `feature1` weiterhin in der Historie von `feature2`, während `main` einen neuen Squash-Commit enthält. Dadurch können Änderungen doppelt betrachtet oder Konflikte erzeugt werden.
|
||||
|
||||
Für diesen speziellen Fall ist besser:
|
||||
|
||||
```bash
|
||||
git rebase --onto origin/main origin/feature1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Alternative: `feature1` nicht squashen
|
||||
|
||||
Wenn häufig gestapelte Branches verwendet werden, kann es sinnvoll sein, den übergeordneten Branch `feature1` nicht zu squashen, sondern per normalem Merge zu integrieren.
|
||||
|
||||
Dann bleibt die Commit-Abstammung erhalten:
|
||||
|
||||
```text
|
||||
main: A---------M
|
||||
\ /
|
||||
feature1: B-----C
|
||||
\
|
||||
feature2: D---E
|
||||
```
|
||||
|
||||
Git kann in diesem Fall besser erkennen, dass `B` und `C` bereits in `main` enthalten sind.
|
||||
|
||||
Eine mögliche Teamregel wäre daher:
|
||||
|
||||
- unabhängige Feature-Branches: Squash-Merge erlaubt
|
||||
- voneinander abhängige oder gestapelte Feature-Branches: normaler Merge ohne Squash oder anschließend gezielter Rebase
|
||||
|
||||
---
|
||||
|
||||
# Zusammenfassung
|
||||
|
||||
Für Ihren Fall ist der typische Ablauf:
|
||||
|
||||
```bash
|
||||
git status
|
||||
git fetch origin
|
||||
git switch feature2
|
||||
git branch backup/feature2-vor-rebase
|
||||
git rebase --onto origin/main origin/feature1
|
||||
```
|
||||
|
||||
Bei Konflikten:
|
||||
|
||||
```bash
|
||||
# Dateien bearbeiten
|
||||
git add <dateien>
|
||||
git rebase --continue
|
||||
```
|
||||
|
||||
Bei Abbruch:
|
||||
|
||||
```bash
|
||||
git rebase --abort
|
||||
```
|
||||
|
||||
Nach erfolgreichem Rebase prüfen:
|
||||
|
||||
```bash
|
||||
git diff origin/main...feature2
|
||||
git log --oneline origin/main..feature2
|
||||
```
|
||||
|
||||
Dann pushen:
|
||||
|
||||
```bash
|
||||
git push --force-with-lease origin feature2
|
||||
```
|
||||
|
||||
Der entscheidende Punkt ist: **Nach einem Squash-Merge muss ein davon abgeleiteter Branch auf die neue `main`-Basis neu aufgebaut werden.** Ein einfacher Merge von `main` in den alten gestapelten Branch ist dafür nicht die zuverlässigsten Variante.
|
||||
@@ -0,0 +1,164 @@
|
||||
# Git-History kuerzen
|
||||
|
||||
Wenn ein Repository zu gross geworden ist und die alte Historie nicht mehr benoetigt wird, kann die Git-History ab einem bestimmten Commit abgeschnitten werden. Dabei wird die Historie **umgeschrieben**: Der Commit-Hash des neuen Anfangs und die Hashes aller folgenden Commits aendern sich.
|
||||
|
||||
> **Achtung:** Das ist eine destruktive Operation fuer die bisherige Historie. Vorher unbedingt ein Backup erstellen und mit allen anderen Personen abstimmen, die mit dem Repository arbeiten.
|
||||
|
||||
## Nicht verwechseln: Shallow Clone
|
||||
|
||||
Ein flacher Clone verbirgt alte Historie nur lokal:
|
||||
|
||||
```bash
|
||||
git clone --shallow-since="2024-01-01" <REPOSITORY_URL>
|
||||
```
|
||||
|
||||
Das Repository selbst wird dadurch nicht gekuerzt. Fuer eine dauerhafte Kuerzung muss die Historie umgeschrieben und anschliessend mit Force-Push auf das Remote uebertragen werden.
|
||||
|
||||
## Vorbereitung: Backup erstellen
|
||||
|
||||
Am sichersten arbeitet man mit einem Mirror-Clone:
|
||||
|
||||
```bash
|
||||
git clone --mirror <REPOSITORY_URL> repository-backup.git
|
||||
cd repository-backup.git
|
||||
```
|
||||
|
||||
Den urspruenglichen Clone bzw. das Backup erst loeschen, wenn das Ergebnis geprueft wurde.
|
||||
|
||||
## `git-filter-repo` unter Arch Linux installieren
|
||||
|
||||
Das Arch-Paket heisst `git-filter-repo`:
|
||||
|
||||
```bash
|
||||
sudo pacman -S git-filter-repo
|
||||
```
|
||||
|
||||
Pruefen:
|
||||
|
||||
```bash
|
||||
git filter-repo --version
|
||||
```
|
||||
|
||||
Ein systemweites `pip install` funktioniert auf Arch Linux wegen PEP 668 normalerweise nicht. Die Paketinstallation ist vorzuziehen.
|
||||
|
||||
## Einen Commit zum neuen Root machen
|
||||
|
||||
Angenommen, dieser Commit soll der neue Anfang der Historie werden:
|
||||
|
||||
```text
|
||||
464e39c1234567890abcdef1234567890abcdef
|
||||
```
|
||||
|
||||
Dann werden seine Eltern entfernt:
|
||||
|
||||
```bash
|
||||
git filter-repo --force \
|
||||
--commit-callback 'if commit.original_id == b"464e39c1234567890abcdef1234567890abcdef": commit.parents = []'
|
||||
```
|
||||
|
||||
`commit.original_id` enthaelt die vollstaendige 40-stellige Commit-ID. Eine kurze ID wie `464e39c` funktioniert mit `==` nicht. Alternativ kann ein eindeutiger Prefix so verwendet werden:
|
||||
|
||||
```bash
|
||||
git filter-repo --force \
|
||||
--commit-callback 'if commit.original_id.startswith(b"464e39c"): commit.parents = []'
|
||||
```
|
||||
|
||||
Die vollstaendige ID kann vorher ermittelt werden:
|
||||
|
||||
```bash
|
||||
git rev-parse 464e39c
|
||||
```
|
||||
|
||||
Aus:
|
||||
|
||||
```text
|
||||
A -- B -- C -- D -- E
|
||||
```
|
||||
|
||||
wird dadurch sinngemaess:
|
||||
|
||||
```text
|
||||
C' -- D' -- E'
|
||||
```
|
||||
|
||||
Der Commit `C` bleibt inhaltlich und mit seinen Metadaten erhalten, bekommt aber wegen der entfernten Eltern einen neuen Hash. Auch `D` und `E` erhalten neue Hashes.
|
||||
|
||||
## Den Cutoff-Commit ebenfalls verwerfen
|
||||
|
||||
Wenn der Cutoff-Commit selbst nicht erhalten bleiben soll, kann ein neuer Root-Commit mit seinem Dateistand erstellt und die spaetere Historie darauf rebased werden:
|
||||
|
||||
```bash
|
||||
git checkout --orphan temp <CUTOFF_COMMIT>
|
||||
git commit -m "Start truncated history"
|
||||
git rebase --onto temp <CUTOFF_COMMIT> main
|
||||
git branch -D temp
|
||||
```
|
||||
|
||||
Dabei ist `<CUTOFF_COMMIT>` der **letzte Commit, der verworfen werden soll**. Der neue Root-Commit enthaelt den Dateistand dieses Commits, ist aber ein neuer Commit. Bei Merge-Commits oder komplexen Branches kann die Rebase-Variante Konflikte verursachen. In solchen Faellen ist `git-filter-repo` in der Regel die bessere Wahl.
|
||||
|
||||
## Ergebnis pruefen
|
||||
|
||||
Vor dem Push die neue Historie kontrollieren:
|
||||
|
||||
```bash
|
||||
git log --all --graph --oneline --decorate
|
||||
git status
|
||||
```
|
||||
|
||||
Der neue Root-Commit darf keine `parent`-Zeile haben:
|
||||
|
||||
```bash
|
||||
git show --no-patch --pretty=raw <NEUER_ROOT_COMMIT>
|
||||
```
|
||||
|
||||
Auch der aktuelle Dateistand sollte unveraendert sein. Einen Vergleich kann man beispielsweise vor dem Umschreiben speichern oder mit dem Backup pruefen.
|
||||
|
||||
## Remote wieder hinzufuegen
|
||||
|
||||
`git-filter-repo` entfernt das Remote oft absichtlich als Sicherheitsmassnahme. Deshalb zuerst pruefen:
|
||||
|
||||
```bash
|
||||
git remote -v
|
||||
```
|
||||
|
||||
Falls kein `origin` vorhanden ist:
|
||||
|
||||
```bash
|
||||
git remote add origin <REPOSITORY_URL>
|
||||
git ls-remote origin
|
||||
```
|
||||
|
||||
`origin/main` ist eine Branch-Referenz und kein Remote-Name. Der Push verwendet daher `origin main`, nicht `origin/main`.
|
||||
|
||||
## Umschriebene Historie pushen
|
||||
|
||||
Nur den Hauptbranch pushen:
|
||||
|
||||
```bash
|
||||
git push --force origin main
|
||||
```
|
||||
|
||||
Alle lokalen Branches und Tags pushen:
|
||||
|
||||
```bash
|
||||
git push --force --all origin
|
||||
git push --force --tags origin
|
||||
```
|
||||
|
||||
Das Remote kann den Push wegen Branch-Schutzregeln ablehnen. In diesem Fall muss der Schutz voruebergehend angepasst werden.
|
||||
|
||||
## Nacharbeiten und wichtige Folgen
|
||||
|
||||
- Alle Commit-Links, Pull Requests und Tags koennen durch die neuen Hashes ungueltig werden.
|
||||
- Andere Clone enthalten weiterhin die alte Historie und muessen neu geklont oder manuell angepasst werden.
|
||||
- Andere Branches oder Tags koennen die alte Historie weiterhin erreichbar machen.
|
||||
- Ein Force-Push kann nicht ohne Weiteres rueckgaengig gemacht werden; das Backup aufbewahren.
|
||||
|
||||
Alte, nicht mehr erreichbare Objekte koennen nach der Pruefung lokal bereinigt werden:
|
||||
|
||||
```bash
|
||||
git reflog expire --expire=now --all
|
||||
git gc --prune=now
|
||||
```
|
||||
|
||||
`git gc --aggressive` ist normalerweise nicht notwendig und kann bei grossen Repositories lange dauern. Auf einem Hosting-Dienst werden nicht mehr erreichbare Objekte eventuell erst spaeter durch dessen eigene Garbage Collection geloescht.
|
||||
@@ -778,3 +778,67 @@ docker-compose up --build
|
||||
- ✅ Persistente Datenspeicherung
|
||||
|
||||
**Das ist die Macht von Docker! 🐳**
|
||||
|
||||
---
|
||||
|
||||
## Docker-Netzwerk bei Datenbankproblemen untersuchen
|
||||
|
||||
Bei einer Datenbank in einem Docker-Container muss zwischen verschiedenen
|
||||
Netzwerkperspektiven unterschieden werden:
|
||||
|
||||
- **Innerhalb eines Docker-Netzwerks:** Container erreichen sich normalerweise
|
||||
über den Compose-Service-Namen, zum Beispiel `postgres:5432`.
|
||||
- **Auf dem Docker-Host:** Ein veröffentlichter Port wie `5432:5432` ist über
|
||||
den Host erreichbar, häufig über `127.0.0.1:5432`.
|
||||
- **Von außerhalb des Hosts:** Ein SSH-Tunnel kann einen lokalen Port auf den
|
||||
Host-Port oder auf einen aus Sicht des Hosts erreichbaren Container-Endpunkt
|
||||
weiterleiten.
|
||||
|
||||
### Docker-Bridge-Adressen
|
||||
|
||||
Adressen wie `172.19.0.1` und `172.19.0.2` gehören häufig zu einem Docker-
|
||||
Bridge-Netzwerk:
|
||||
|
||||
- `172.19.0.1` ist typischerweise das Gateway des Netzwerks auf dem Host.
|
||||
- `172.19.0.2` kann die aktuelle IP eines Containers sein.
|
||||
|
||||
Container-IPs sind nicht als dauerhafte Konfiguration geeignet. Nach einem
|
||||
`docker compose up`, einer Neuerstellung oder einem Deployment kann derselbe
|
||||
Container eine andere IP erhalten. Innerhalb von Compose-Netzwerken sollte der
|
||||
Service-Name verwendet werden. Von außerhalb sollte nach Möglichkeit ein
|
||||
veröffentlichter Host-Port verwendet werden.
|
||||
|
||||
### Nützliche Diagnosebefehle
|
||||
|
||||
```bash
|
||||
# Laufende Container und veröffentlichte Ports anzeigen
|
||||
docker ps
|
||||
|
||||
# Netzwerkdetails und aktuelle Container-IP anzeigen
|
||||
docker inspect <container-name>
|
||||
|
||||
# Vorhandene Docker-Netzwerke anzeigen
|
||||
docker network ls
|
||||
|
||||
# Teilnehmer und IPs eines Netzwerks anzeigen
|
||||
docker network inspect <network-name>
|
||||
|
||||
# Prüfen, ob der Host lokal auf dem PostgreSQL-Port lauscht
|
||||
sudo ss -ltnp | grep 5432
|
||||
|
||||
# TCP-Erreichbarkeit eines bestimmten Ziels testen
|
||||
nc -vz <host> 5432
|
||||
|
||||
# PostgreSQL-Logs während eines Verbindungsversuchs beobachten
|
||||
docker logs --tail 100 -f <postgres-container>
|
||||
```
|
||||
|
||||
Wenn ein Client eine Verbindung mit `EOF` beendet, sollte zuerst geprüft
|
||||
werden, ob überhaupt ein neuer Eintrag in den PostgreSQL-Logs erscheint. Ohne
|
||||
Logeintrag liegt das Problem wahrscheinlich vor PostgreSQL, beispielsweise am
|
||||
Tunnel, am Zielhost oder am Zielport. Ein Logeintrag zeigt, dass PostgreSQL
|
||||
erreicht wurde; dann kommen unter anderem SSL- oder
|
||||
Authentifizierungsparameter als Ursache infrage.
|
||||
|
||||
Siehe auch [[psql]], [[SSH Verbindung in Linux]] und
|
||||
[[DBeaver PostgreSQL via SSH-Tunnel]].
|
||||
|
||||
Reference in New Issue
Block a user