Struktura Repozytorium Git: Kompletny Przewodnik Techniczny
Git to rozproszony system kontroli wersji, który przechowuje historię projektu jako skierowany graf acykliczny (DAG) niezmiennych obiektów migawkowych. Każde repozytorium Git zbudowane jest z trzech logicznych stref — katalogu roboczego, indeksu tymczasowego i magazynu obiektów wewnątrz .git/ — oraz zestawu lekkich wskaźników (gałęzie, tagi, zdalne repozytoria), które nawigują po tej historii. Zrozumienie, jak te warstwy ze sobą współdziałają, to różnica między mechanicznym używaniem Git a używaniem go z chirurgiczną precyzją.
Jeśli samodzielnie hostujesz swoje repozytoria na VPS, opanowanie tej wewnętrznej struktury pozwala odzyskiwać dane po awariach, projektować wydajne pipeline’y CI/CD i audytować każdy bajt historii projektu bez polegania na platformach zewnętrznych.
Model trzech stref: jak Git przenosi dane
Zanim zagłębisz się w poszczególne komponenty, przyswój model przepływu danych, który rządzi każdą operacją Git:
Working Directory --> Staging Area (Index) --> .git/ Object Store
(edit) (git add) (git commit)Zmiany przemieszczają się od lewej do prawej podczas budowania commita, a od prawej do lewej podczas przywracania lub resetowania. Każde polecenie Git to w istocie operacja odczytu lub zapisu na jednej lub kilku z tych stref.
Katalog roboczy
Katalog roboczy (zwany również drzewem roboczym) to widok systemu plików projektu w określonym stanie checkout. Gdy uruchamiasz git clone lub git checkout, Git rekonstruuje pliki ze skompresowanych obiektów w .git/objects/ i zapisuje je w tym katalogu.
Pliki w katalogu roboczym istnieją w jednym z czterech stanów:
- Nieśledzone — Git nigdy nie widział tego pliku; istnieje tylko na dysku.
- Śledzone, niezmienione — plik dokładnie odpowiada ostatniej zatwierdzonej migawce.
- Śledzone, zmodyfikowane — plik różni się od ostatniej zatwierdzonej migawki, ale nie został dodany do indeksu.
- Śledzone, usunięte — plik został usunięty z dysku, ale usunięcie nie zostało dodane do indeksu.
Istotna subtelność, która myli wielu programistów: katalog roboczy nie jest prostą kopią repozytorium. Git rekonstruuje go, odczytując obiekty drzewa i dekompresując obiekty blob. Jeśli .git/ jest nienaruszony, zawsze możesz odtworzyć katalog roboczy od zera — odwrotna sytuacja nie jest prawdą.
Sparse Checkout dla dużych monorepo
W repozytoriach z dziesiątkami tysięcy plików (typowych w architekturach monorepo) możesz ograniczyć, które ścieżki Git materializuje w katalogu roboczym:
git sparse-checkout init --cone
git sparse-checkout set services/api services/authJest to nieocenione na VPS z ograniczonym I/O dysku, ponieważ Git pomija dekompresję blobów dla ścieżek spoza stożka.
Obszar tymczasowy (indeks)
Obszar tymczasowy, wewnętrznie nazywany indeksem, to plik binarny zlokalizowany pod .git/index. Działa jako proponowany następny commit — zmienna migawka znajdująca się między katalogiem roboczym a stałym magazynem obiektów.
git add <file> # Stage a specific file
git add -p # Interactively stage hunks within a file
git add -u # Stage all tracked modifications and deletions
git status # Compare working directory and index against HEAD
git diff --cached # Show diff between index and HEADDlaczego indeks istnieje
Indeks rozwiązuje problem, który prostsze narzędzia VCS ignorują: częściowe commity. Możesz mieć zmodyfikowane pięć plików, ale chcieć umieścić w następnym commicie tylko trzy z nich. Indeks pozwala skomponować dokładnie taką migawkę, jaką zamierzasz zapisać, niezależnie od tego, co masz otwarte w edytorze.
Przypadek brzegowy — uszkodzenie indeksu: Jeśli awaria systemu przerwie git add, plik indeksu może ulec uszkodzeniu. Objawami są zawieszanie się git status lub wyświetlanie dziwnych wyników. Odzyskiwanie:
rm .git/index
git resetGit odbudowuje indeks z HEAD bez dotykania katalogu roboczego.
Indeks jako rejestr konfliktów scalania
Podczas konfliktu scalania indeks przechowuje jednocześnie trzy wersje każdego pliku z konfliktem (etapy 1, 2 i 3 — baza, nasza wersja, ich wersja). Dlatego git diff --cached nie pokazuje nic użytecznego w trakcie konfliktu; potrzebujesz git diff --cc lub narzędzia do scalania, aby sprawdzić wszystkie trzy etapy.
Katalog .git/: anatomia magazynu obiektów
Katalog .git/ jest repozytorium. Wszystko inne — katalog roboczy, zdalne klony — jest z niego pochodne. Usunięcie .git/ zamienia repozytorium w zwykły katalog bez historii.
.git/
├── HEAD
├── config
├── description
├── index
├── COMMIT_EDITMSG
├── hooks/
├── info/
├── logs/
│ ├── HEAD
│ └── refs/
├── objects/
│ ├── info/
│ └── pack/
└── refs/
├── heads/
├── remotes/
└── tags/HEAD
HEAD to zwykły plik tekstowy zawierający albo symboliczne odwołanie (wskazujące na gałąź), albo surowy skrót SHA-1 (stan odłączonego HEAD).
cat .git/HEAD
# ref: refs/heads/main <-- on a branch
# a3f1c9d... <-- detached HEADOdłączony HEAD nie jest stanem błędu — jest zamierzony, gdy sprawdzasz tag lub konkretny commit w celu inspekcji. Niebezpieczeństwo polega na tworzeniu commitów w odłączonym HEAD: te commity są osiągalne tylko przez reflog, dopóki nie przypiszesz ich do gałęzi.
git checkout -b rescue-branch # Attach detached commits to a new branchconfig
Lokalny plik konfiguracyjny repozytorium. Nadpisuje ustawienia globalne (~/.gitconfig) i systemowe (/etc/gitconfig). Typowe wpisy:
[core]
repositoryformatversion = 0
filemode = true
bare = false
[remote "origin"]
url = git@github.com:user/repo.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
remote = origin
merge = refs/heads/mainNa samodzielnie hostowanym serwerze będziesz często edytować ten plik bezpośrednio podczas rotacji zdalnych URL-i lub konfigurowania uploadpack.allowReachableSHA1InWant dla częściowych klonów.
refs/
Katalog refs/ zawiera zwykłe pliki tekstowe, z których każdy przechowuje pojedynczy skrót SHA-1. Są to nazwane wskaźniki, które umożliwiają nawigację po DAG Git.
| Typ ref | Ścieżka | Opis |
|---|---|---|
| Lokalna gałąź | refs/heads/<name> | Wskazuje na końcowy commit gałęzi |
| Gałąź śledząca zdalne repozytorium | refs/remotes/<remote>/<name> | Lokalna kopia końca gałęzi zdalnej |
| Lekki tag | refs/tags/<name> | Wskazuje bezpośrednio na obiekt commit |
| Opatrzony adnotacją tag | refs/tags/<name> | Wskazuje na obiekt tagu, który wskazuje na commit |
| Schowek | refs/stash | Wskazuje na commit schowka |
Dla wydajności Git pakuje referencje do .git/packed-refs, gdy repozytorium zgromadzi ich wiele. Zawsze sprawdzaj oba miejsca podczas tworzenia skryptów operujących na referencjach.
Obiekty Git: niezmienne jądro
Wszystko przechowywane w .git/objects/ jest adresowane treścią: nazwa pliku to skrót SHA-1 (lub SHA-256 w nowszych wersjach Git) zawartości obiektu. Sprawia to, że Git jest z natury odporny na manipulacje — zmiana dowolnego bajtu zmienia skrót, przerywając łańcuch.
Cztery typy obiektów
| Typ obiektu | Co przechowuje | Wskazuje na |
|---|---|---|
| Blob | Surowa zawartość pliku (bez nazwy pliku, bez uprawnień) | Nic |
| Drzewo | Lista zawartości katalogu: nazwy plików, uprawnienia, SHA blobów/drzew | Bloby i inne drzewa |
| Commit | Autor, osoba zatwierdzająca, znacznik czasu, wiadomość, SHA(s) rodziców | Jedno drzewo + zero lub więcej commitów nadrzędnych |
| Tag | Tożsamość osoby tagującej, znacznik czasu, wiadomość, podpis GPG | Zazwyczaj commit |
Bezpośrednia inspekcja obiektów
# Show the type of any object
git cat-file -t a3f1c9d
# Show the content of any object
git cat-file -p a3f1c9d
# Show the tree of the current HEAD commit
git ls-tree HEAD
# Show a specific blob's content
git show HEAD:src/main.pyLuźne obiekty a pliki pack
Początkowo każdy obiekt jest przechowywany jako pojedynczy skompresowany plik w .git/objects/<2-char-prefix>/<38-char-suffix>. Są to luźne obiekty. Z czasem Git uruchamia git gc (garbage collection), aby zebrać luźne obiekty w pliki pack (.git/objects/pack/*.pack) z odpowiadającym indeksem (.pack.idx).
Pliki pack używają kompresji delta — przechowując różnicę między podobnymi obiektami zamiast pełnych kopii. Repozytorium z tysiącami podobnych plików tekstowych może znacznie zmniejszyć rozmiar po spakowaniu. Na VPS z ograniczoną pojemnością NVMe uruchamianie git gc --aggressive na dużych repozytoriach przed archiwizacją jest standardową praktyką.
git count-objects -vH # Show loose object count and disk usage
git gc --aggressive # Repack aggressively (CPU-intensive)
git verify-pack -v .git/objects/pack/*.idx | sort -k3 -n | tail -20
# Find the 20 largest objects in the packHistoria commitów: skierowany graf acykliczny
Każdy obiekt commit zawiera dokładnie jeden wskaźnik do obiektu drzewa (migawka katalogu głównego) i zero lub więcej wskaźników do commitów nadrzędnych. Tworzy to DAG, w którym:
- Zero rodziców = początkowy commit (commit główny)
- Jeden rodzic = normalny commit
- Dwóch rodziców = commit scalający
- Trzech lub więcej rodziców = scalanie ośmiornicze (rzadkie, używane do integrowania wielu gałęzi funkcji jednocześnie)
git log --oneline --graph --all # Visualize the full DAG
git log --format="%H %P" # Show each commit's SHA and parent SHA(s)Niezmienność commitów i przepisywanie historii
Ponieważ SHA commita jest pochodną jego zawartości (w tym SHA rodziców), każde przepisanie tworzy nowy commit z nowym SHA. Operacje takie jak git rebase, git commit --amend i git filter-repo nie modyfikują historii — tworzą równoległą historię. Stare commity pozostają w magazynie obiektów do czasu garbage collection.
Dlatego wymuszanie przepisanej historii na współdzieloną gałąź jest destrukcyjne: lokalne gałęzie współpracowników nadal wskazują na stary łańcuch commitów.
Gałęzie: lekkie wskaźniki
Gałąź to nic więcej niż 41-bajtowy plik zawierający skrót SHA-1. Tworzenie gałęzi jest natychmiastowe niezależnie od rozmiaru repozytorium, ponieważ Git zapisuje tylko jeden mały plik.
git branch feature/auth # Create branch at current HEAD
git checkout -b feature/auth # Create and switch in one step
git switch -c feature/auth # Modern equivalent (Git 2.23+)
git branch -d feature/auth # Delete (safe: refuses if unmerged)
git branch -D feature/auth # Delete (force: regardless of merge status)Wewnętrzna struktura gałęzi
cat .git/refs/heads/main
# a3f1c9d8e2b1f4c7d9e0a1b2c3d4e5f6a7b8c9d0Gdy zatwierdzasz commit na gałęzi, Git zapisuje nowy SHA commita do tego pliku. To jest całość operacji „przesuwania wskaźnika gałęzi”.
Gałęzie śledzące i konfiguracja upstream
Relacja śledzenia informuje Git, którą zdalną gałąź lokalna gałąź powinna porównywać dla raportowania rozbieżności git status i zachowania git pull.
git branch --set-upstream-to=origin/main main
git branch -vv # Show tracking relationships and ahead/behind countsTagi: trwałe znaczniki w historii
Tagi oznaczają konkretne commity jako istotne — zazwyczaj wydania oprogramowania. W przeciwieństwie do gałęzi, tagi nie są przesuwane przez nowe commity.
| Cecha | Lekki tag | Tag z adnotacją |
|---|---|---|
| Przechowywanie | Plik ref wskazujący na commit | Obiekt tagu w magazynie obiektów |
| Metadane | Brak | Nazwa osoby tagującej, email, data, wiadomość |
| Podpisywanie GPG | Niemożliwe | Obsługiwane przez git tag -s |
| Zalecane dla wydań | Nie | Tak |
Transfer z git push --tags | Tak | Tak |
git tag v2.1.0 # Lightweight tag at HEAD
git tag -a v2.1.0 -m "Release 2.1.0" # Annotated tag
git tag -s v2.1.0 -m "Signed release" # GPG-signed annotated tag
git push origin --tags # Push all tags to remote
git push origin v2.1.0 # Push a specific tagKrytyczna pułapka: git push domyślnie nie przesyła tagów. Zespoły często o tym zapominają i publikują informacje o wydaniu odwołujące się do tagu, który nie istnieje na zdalnym repozytorium.
Zdalne repozytoria: współpraca rozproszona
Zdalne repozytorium to nazwany URL przechowywany w .git/config. Gałęzie śledzące zdalne repozytoria (w refs/remotes/) to lokalne migawki tylko do odczytu gałęzi zdalnych, aktualizowane tylko wtedy, gdy jawnie pobierasz dane.
git remote add origin git@github.com:user/repo.git
git remote -v # List remotes with URLs
git remote set-url origin <new-url> # Change a remote URL
git fetch origin # Update remote-tracking branches
git fetch --prune # Remove stale remote-tracking branches
git push origin main # Push local main to remote
git push -u origin feature/auth # Push and set upstream trackingWiele zdalnych repozytoriów
Pojedyncze repozytorium może śledzić wiele zdalnych repozytoriów — typowe przy utrzymywaniu forka obok repozytorium upstream:
git remote add upstream git@github.com:original/repo.git
git fetch upstream
git merge upstream/mainGdy samodzielnie hostujesz czyste repozytoria na serwerze dedykowanym dla swojego zespołu, każdy programista dodaje serwer jako zdalne repozytorium i używa uwierzytelniania kluczem SSH do dostępu push.
Hooki: automatyczne egzekwowanie przy każdym zdarzeniu Git
Hooki to wykonywalne skrypty w .git/hooks/. Git wywołuje je w określonych punktach przepływu pracy. Nie są transferowane przez git clone ani git push — każdy programista (lub serwer) musi je zainstalować niezależnie. Jest to częste źródło nieporozumień w środowiskach zespołowych.
Hooki po stronie klienta
| Hook | Wyzwalacz | Typowe zastosowanie |
|---|---|---|
pre-commit | Przed monitem o wiadomość commita | Linting, skanowanie sekretów, uruchamianie testów |
prepare-commit-msg | Po utworzeniu domyślnej wiadomości | Wstrzyknięcie nazwy gałęzi do wiadomości |
commit-msg | Po napisaniu wiadomości przez użytkownika | Wymuszanie formatu conventional commit |
post-commit | Po zapisaniu commita | Lokalne powiadomienia |
pre-push | Przed wykonaniem git push | Uruchomienie pełnego zestawu testów |
pre-rebase | Przed rozpoczęciem rebase | Zapobieganie rebase’owaniu opublikowanych gałęzi |
Hooki po stronie serwera
| Hook | Wyzwalacz | Typowe zastosowanie |
|---|---|---|
pre-receive | Przed aktualizacją referencji | Wymuszanie ochrony gałęzi, odrzucanie force-push |
update | Per-ref podczas odbierania | Egzekwowanie polityki per-gałąź |
post-receive | Po aktualizacji wszystkich referencji | Wyzwalanie CI/CD, wysyłanie powiadomień |
Przykład: hook pre-commit do wykrywania sekretów
#!/usr/bin/env bash
# .git/hooks/pre-commit
if git diff --cached --name-only | xargs grep -lE '(AKIA|passwords*=|api_keys*=)' 2>/dev/null; then
echo "ERROR: Potential secret detected in staged files. Commit aborted."
exit 1
fi
exit 0Nadaj mu uprawnienia do wykonania:
chmod +x .git/hooks/pre-commitDo dystrybucji hooków w całym zespole użyj narzędzia takiego jak Husky (projekty Node.js) lub przechowuj hooki w katalogu hooks/ w głównym katalogu repozytorium i twórz do nich dowiązania symboliczne podczas konfiguracji projektu.
Reflog: siatka bezpieczeństwa
Reflog rejestruje każde przesunięcie HEAD i wskaźników gałęzi, w tym operacje, które pozornie niszczą historię (twarde resety, rebase’y, zmienione commity). Jest przechowywany w .git/logs/.
git reflog # Show HEAD movement history
git reflog show main # Show movement history for a specific branch
git checkout HEAD@{3} # Check out the state HEAD was in 3 moves ago
git branch recovered HEAD@{5} # Recover commits by branching from a reflog entryWpisy reflogu wygasają domyślnie po 90 dniach (gc.reflogExpire). Na serwerze produkcyjnym rozważ wydłużenie tego okresu:
git config gc.reflogExpire 180
git config gc.reflogExpireUnreachable 30Czyste repozytoria: hosting po stronie serwera
Czyste repozytorium nie ma katalogu roboczego. Zawiera tylko zawartość .git/ na poziomie głównym. Czyste repozytoria to właściwy format dla scentralizowanego hostingu — akceptują push bez komplikacji związanych z wyewidencjonowaną gałęzią.
git init --bare /srv/repos/myproject.gitGdy wysyłasz dane do GitHub, GitLab lub samodzielnie hostowanego serwera Git, wysyłasz je do czystego repozytorium. Jeśli hostujesz własny serwer Git na VPS z cPanel lub zwykłym Linux VPS, czyste repozytoria w /srv/repos/ z dostępem SSH to standardowa architektura.
Inicjalizacja współdzielonego czystego repozytorium
# On the server
git init --bare --shared=group /srv/repos/project.git
chown -R git:developers /srv/repos/project.git
# On a developer's machine
git remote add origin git@yourserver.com:/srv/repos/project.git
git push -u origin mainPrzechowywanie obiektów Git: rozmiar, integralność i konserwacja
Sprawdzanie kondycji repozytorium
git fsck --full # Verify object integrity (finds dangling and corrupt objects)
git fsck --lost-found # Write dangling objects to .git/lost-found/Znajdowanie i usuwanie dużych obiektów
Duże pliki binarne przypadkowo zatwierdzone są częstą przyczyną rozdętych repozytoriów. Zidentyfikuj je przed użyciem git filter-repo do ich usunięcia:
# Find the 10 largest objects by compressed size
git verify-pack -v .git/objects/pack/*.idx
| sort -k3 -rn
| head -10
| awk '{print $1}'
| xargs -I{} git cat-file -p {}# Remove a file from all history (requires git-filter-repo)
git filter-repo --path path/to/large-file.bin --invert-pathsPo filtrowaniu wszyscy współpracownicy muszą ponownie sklonować repozytorium — ich lokalne repozytoria odwołują się do skrótów SHA, które nie istnieją już w przepisanej historii.
Porównanie: kluczowe koncepcje repozytorium Git
| Koncepcja | Typ | Zmienny | Przechowywany w | Transferowany przez push/fetch |
|---|---|---|---|---|
| Blob | Obiekt | Nie | .git/objects/ | Tak (gdy osiągalny) |
| Drzewo | Obiekt | Nie | .git/objects/ | Tak (gdy osiągalny) |
| Commit | Obiekt | Nie | .git/objects/ | Tak (gdy osiągalny) |
| Tag z adnotacją | Obiekt | Nie | .git/objects/ | Tylko z --tags |
| Gałąź | Ref | Tak | .git/refs/heads/ | Tak |
| Gałąź śledząca zdalne repozytorium | Ref | Tak (przy fetch) | .git/refs/remotes/ | Nie (lokalna kopia) |
| Lekki tag | Ref | Nie | .git/refs/tags/ | Tylko z --tags |
| HEAD | Symref/skrót | Tak | .git/HEAD | Nie |
| Indeks | Plik binarny | Tak | .git/index | Nie |
| Hooki | Skrypty | Tak | .git/hooks/ | Nie |
| Reflog | Dziennik | Tak (automatycznie wygasa) | .git/logs/ | Nie |
Praktyczna macierz decyzyjna i kluczowe wnioski
Użyj tej listy kontrolnej podczas konfigurowania lub audytowania repozytorium Git w swojej infrastrukturze:
Inicjalizacja repozytorium
- Używaj
git init --bare --shared=groupdla każdego repozytorium, które będzie odbierać push od wielu użytkowników. - Przechowuj czyste repozytoria poza katalogami dostępnymi przez sieć (nigdy w
/var/www/).
Kondycja magazynu obiektów
- Uruchamiaj
git fsck --fullpo każdym incydencie związanym z przechowywaniem lub błędzie systemu plików. - Planuj
git gcokresowo na długo używanych repozytoriach; automatyzuj to przez cron na swoim serwerze. - Monitoruj rozmiar pliku pack za pomocą
git count-objects -vH; sprawdzaj, jeśli liczba luźnych obiektów przekracza 1 000.
Higiena gałęzi i referencji
- Usuwaj scalone gałęzie niezwłocznie; nieaktualne referencje gromadzą się i spowalniają operacje
git fetch --prune. - Używaj
git fetch --prunew pipeline’ach CI, aby unikać działania na usuniętych gałęziach zdalnych.
Wdrażanie hooków
- Nigdy nie polegaj na
.git/hooks/dla polityki obowiązującej cały zespół — hooki nie są klonowane. Zamiast tego używaj hookówpre-receivepo stronie serwera lub bramki CI. - Audytuj hooki po stronie serwera po każdej aktualizacji serwera Git; ścieżki interpreterów hooków mogą się zmieniać.
Bezpieczeństwo na samodzielnie hostowanych serwerach
- Ogranicz dostęp SSH do użytkownika
gitza pomocą wymuszonych poleceń (command=wauthorized_keys). - Używaj
git-shelljako powłoki logowania dla użytkownikagit, aby zapobiec wykonywaniu dowolnych poleceń. - Połącz swój serwer repozytorium z ważnym certyfikatem SSL, jeśli udostępniasz jakikolwiek interfejs webowy (Gitea, GitLab, cgit).
Przepisywanie historii
- Nigdy nie przepisuj historii na gałęziach współdzielonych z innymi bez skoordynowanego planu migracji.
- Po
git filter-repowszyscy współpracownicy muszą ponownie sklonować repozytorium; natychmiast zaktualizuj zdalne URL-e CI/CD.
Odzyskiwanie po awarii
- Wydłuż czas wygasania reflogu na serwerach produkcyjnych (
gc.reflogExpire = 180). - Utrzymuj dodatkowy czysty klon na osobnym hoście jako kopię zapasową; prosty
git fetchz głównego serwera jest wystarczający.
FAQ
Jaka jest różnica między czystym a nieczystym repozytorium Git?
Nieczyste repozytorium ma katalog roboczy, w którym pliki są wyewidencjonowane, oraz podkatalog .git/ zawierający magazyn obiektów. Czyste repozytorium zawiera tylko magazyn obiektów w swoim katalogu głównym (bez katalogu roboczego) i jest właściwym formatem dla współdzielonego serwera odbierającego push.
Czy mogę odzyskać commity po uruchomieniu git reset --hard?
Tak, o ile commity nie zostały poddane garbage collection. Uruchom git reflog, aby znaleźć SHA commita, który chcesz odzyskać, a następnie git checkout -b recovery-branch <SHA>, aby przypisać go do nowej gałęzi. Wpisy reflogu są domyślnie przechowywane przez 90 dni.
Dlaczego git push nie transferuje moich tagów?
Z założenia git push transferuje tylko commity osiągalne z referencji, które jawnie wysyłasz. Tagi to osobne referencje i muszą być wysyłane za pomocą git push origin --tags (wszystkie tagi) lub git push origin <tagname> (konkretny tag).
Co dzieje się z indeksem podczas konfliktu scalania?
Indeks przechowuje jednocześnie wszystkie trzy wersje każdego pliku z konfliktem: etap 1 (wspólny przodek/baza), etap 2 (twoja wersja) i etap 3 (ich wersja). Normalny git add zapisuje tylko etap 0 (rozwiązany). Dopóki wszystkie konflikty nie zostaną rozwiązane i dodane do indeksu, git commit odmówi kontynuowania.
Czym różnią się hooki Git po stronie klienta od tych po stronie serwera?
Hooki po stronie klienta działają na maszynie programisty i nie są centralnie egzekwowane — każdy programista może je ominąć, usuwając plik hooka. Hooki po stronie serwera (pre-receive, update, post-receive) działają na serwerze hostingowym i nie mogą być omijane przez klienta, co czyni je właściwym punktem egzekwowania polityk ochrony gałęzi, wymagań dotyczących przeglądu kodu i wyzwalaczy CI/CD.
na wszystkich usługach hostingowych