Economisiți 15% la toate serviciile de găzduire

Testează-ți abilitățile și obține Reducere la orice plan de găzduire

Utilizați codul: Skills Începeți
Secțiuni
Linux Servere virtuale

Structura Depozitului Git: Un Ghid Tehnic Complet

Git este un sistem distribuit de control al versiunilor care stochează istoricul proiectului ca un graf aciclic direcționat (DAG) de obiecte snapshot imuabile. Fiecare repository Git este construit din trei zone logice — directorul de lucru, indexul de staging și depozitul de obiecte din .git/ — plus un set de pointeri ușori (ramuri, etichete, remote-uri) care navighează prin acel istoric. Înțelegerea modului în care aceste straturi interacționează face diferența dintre utilizarea Git mecanic și utilizarea lui cu precizie chirurgicală.

Dacă îți găzduiești propriile repository-uri pe un VPS, stăpânirea acestei structuri interne îți permite să te recuperezi din dezastre, să proiectezi pipeline-uri CI/CD eficiente și să auditezi fiecare byte din istoricul proiectului tău fără a te baza pe o platformă terță.

Modelul cu Trei Zone: Cum Mișcă Git Datele

Înainte de a analiza componentele individuale, interiorizează modelul de flux de date care guvernează fiecare operațiune Git:

Working Directory  -->  Staging Area (Index)  -->  .git/ Object Store
     (edit)               (git add)                  (git commit)

Modificările călătoresc de la stânga la dreapta când construiești un commit, și de la dreapta la stânga când restaurezi sau resetezi. Fiecare comandă Git este în esență o operațiune de citire sau scriere pe una sau mai multe dintre aceste zone.

Directorul de Lucru

Directorul de lucru (numit și arborele de lucru) este vizualizarea sistemului de fișiere a proiectului tău la o stare specifică de checkout. Când rulezi git clone sau git checkout, Git reconstruiește fișierele din obiectele comprimate din .git/objects/ și le scrie în acest director.

Fișierele din directorul de lucru există în una din patru stări:

  • Netracked — Git nu a văzut niciodată acest fișier; există doar pe disc.
  • Tracked, nemodificat — fișierul corespunde exact cu ultimul snapshot committed.
  • Tracked, modificat — fișierul diferă de ultimul snapshot committed, dar nu a fost staged.
  • Tracked, șters — fișierul a fost eliminat de pe disc, dar ștergerea nu a fost staged.

O nuanță critică care îi încurcă pe mulți dezvoltatori: directorul de lucru nu este o simplă copie a repository-ului. Git îl reconstruiește citind obiecte tree și decomprimând obiecte blob. Dacă .git/ este intact, poți regenera întotdeauna directorul de lucru de la zero — inversul nu este adevărat.

Sparse Checkout pentru Monorepo-uri Mari

Pe repository-uri cu zeci de mii de fișiere (comune în arhitecturile monorepo), poți limita căile pe care Git le materializează în directorul de lucru:

git sparse-checkout init --cone
git sparse-checkout set services/api services/auth

Acest lucru este de neprețuit pe un VPS cu I/O de disc limitat, deoarece Git sare peste decomprimarea blob-urilor pentru căile din afara conului.

Zona de Staging (Index)

Zona de staging, numită intern index, este un fișier binar localizat la .git/index. Acționează ca un commit următor propus — un snapshot mutabil care se află între directorul tău de lucru și depozitul permanent de obiecte.

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 HEAD

De Ce Există Indexul

Indexul rezolvă o problemă pe care instrumentele VCS mai simple o ignoră: commit-urile parțiale. Poți fi modificat cinci fișiere, dar vrei doar trei dintre ele în următorul commit. Indexul îți permite să compui exact snapshot-ul pe care intenționezi să îl înregistrezi, independent de ce are editorul tău deschis.

Caz limită — coruperea indexului: Dacă un crash de sistem întrerupe un git add, fișierul index poate deveni corupt. Simptomele includ blocarea git status sau raportarea unor rezultate bizare. Recuperare:

rm .git/index
git reset

Git reconstruiește indexul din HEAD fără a atinge directorul tău de lucru.

Indexul ca Registru de Conflicte de Merge

În timpul unui conflict de merge, indexul stochează trei versiuni ale fiecărui fișier conflictual simultan (etapele 1, 2 și 3 — baza, a noastră, a lor). De aceea git diff --cached nu arată nimic util în mijlocul unui conflict; ai nevoie de git diff --cc sau de un instrument de merge pentru a inspecta toate cele trei etape.

Directorul .git/: Anatomia Depozitului de Obiecte

Directorul .git/ este repository-ul. Orice altceva — directorul de lucru, clonele remote — este derivat din el. Ștergerea .git/ transformă un repository într-un director simplu fără istoric.

.git/
├── HEAD
├── config
├── description
├── index
├── COMMIT_EDITMSG
├── hooks/
├── info/
├── logs/
│   ├── HEAD
│   └── refs/
├── objects/
│   ├── info/
│   └── pack/
└── refs/
    ├── heads/
    ├── remotes/
    └── tags/

HEAD este un fișier text simplu care conține fie o referință simbolică (care indică spre o ramură) fie un hash SHA-1 brut (starea HEAD detașat).

cat .git/HEAD
# ref: refs/heads/main        <-- on a branch
# a3f1c9d...                  <-- detached HEAD

HEAD detașat nu este o stare de eroare — este intenționat când faci checkout la un tag sau la un commit specific pentru inspecție. Pericolul este să faci commit-uri în HEAD detașat: acele commit-uri sunt accesibile doar prin reflog până le atașezi la o ramură.

git checkout -b rescue-branch   # Attach detached commits to a new branch

config

Fișierul de configurare al repository-ului local. Suprascrie setările globale (~/.gitconfig) și de sistem (/etc/gitconfig). Intrări comune:

[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/main

Pe un server găzduit propriu, vei edita frecvent acest fișier direct când rotești URL-urile remote sau configurezi uploadpack.allowReachableSHA1InWant pentru clone parțiale.

refs/

Directorul refs/ conține fișiere text simple, fiecare conținând un singur hash SHA-1. Aceștia sunt pointerii numiți care fac DAG-ul Git navigabil.

Tip RefCaleDescriere
Ramură localărefs/heads/<name>Indică spre commit-ul de vârf al unei ramuri
Ramură de urmărire remoterefs/remotes/<remote>/<name>Cache local al vârfului ramurii unui remote
Tag ușorrefs/tags/<name>Indică direct spre un obiect commit
Tag adnotatrefs/tags/<name>Indică spre un obiect tag, care indică spre un commit
Stashrefs/stashIndică spre commit-ul stash

Pentru performanță, Git împachetează ref-urile în .git/packed-refs odată ce un repository acumulează multe dintre ele. Verifică întotdeauna ambele locații când scrii scripturi care lucrează cu ref-uri.

Obiecte Git: Nucleul Imuabil

Tot ce este stocat în .git/objects/ este adresat prin conținut: numele fișierului este hash-ul SHA-1 (sau SHA-256 în versiunile mai noi de Git) al conținutului obiectului. Acest lucru face Git în mod inerent rezistent la manipulare — schimbarea oricărui byte schimbă hash-ul, rupând lanțul.

Cele Patru Tipuri de Obiecte

Tip ObiectCe StocheazăIndică Spre
BlobConținut brut al fișierului (fără nume de fișier, fără permisiuni)Nimic
TreeListarea directorului: nume de fișiere, permisiuni, SHA-uri blob/treeBlob-uri și alți arbori
CommitAutor, committer, timestamp, mesaj, SHA-uri părinteUn tree + zero sau mai multe commit-uri părinte
TagIdentitatea tagger-ului, timestamp, mesaj, semnătură GPGDe obicei un commit

Inspectarea Obiectelor Direct

# 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.py

Obiecte Loose vs. Fișiere Pack

Inițial, fiecare obiect este stocat ca un fișier comprimat individual sub .git/objects/<2-char-prefix>/<38-char-suffix>. Acestea sunt obiecte loose. În timp, Git rulează git gc (colectare de gunoi) pentru a grupa obiectele loose în fișiere pack (.git/objects/pack/*.pack) cu un index corespunzător (.pack.idx).

Fișierele pack folosesc compresia delta — stocând diferența dintre obiecte similare în loc de copii complete. Un repository cu mii de fișiere text similare poate scădea dramatic în dimensiune după împachetare. Pe un VPS cu capacitate NVMe limitată, rularea git gc --aggressive pe repository-uri mari înainte de arhivare este o practică standard.

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 pack

Istoricul Commit-urilor: Graful Aciclic Direcționat

Fiecare obiect commit conține exact un pointer spre un obiect tree (snapshot-ul directorului rădăcină) și zero sau mai mulți pointeri spre commit-uri părinte. Aceasta formează un DAG unde:

  • Zero părinți = commit-ul inițial (commit rădăcină)
  • Un părinte = un commit normal
  • Doi părinți = un commit de merge
  • Trei sau mai mulți părinți = un merge octopus (rar, folosit pentru integrarea simultană a multor ramuri de funcționalitate)
git log --oneline --graph --all    # Visualize the full DAG
git log --format="%H %P"           # Show each commit's SHA and parent SHA(s)

Imuabilitatea Commit-urilor și Rescrierea Istoricului

Deoarece SHA-ul unui commit este derivat din conținutul său (inclusiv SHA-urile părinților), orice rescriere creează un nou commit cu un nou SHA. Operațiuni precum git rebase, git commit --amend și git filter-repo nu modifică istoricul — creează un istoric paralel. Commit-urile vechi rămân în depozitul de obiecte până sunt colectate de garbage collector.

De aceea, forțarea push-ului istoricului rescris pe o ramură partajată este distructivă: ramurile locale ale colaboratorilor indică în continuare spre lanțul vechi de commit-uri.

Ramuri: Pointeri Ușori

O ramură nu este altceva decât un fișier de 41 de bytes care conține un hash SHA-1. Crearea unei ramuri este instantanee indiferent de dimensiunea repository-ului, deoarece Git scrie doar un singur fișier mic.

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)

Structura Internă a Ramurilor

cat .git/refs/heads/main
# a3f1c9d8e2b1f4c7d9e0a1b2c3d4e5f6a7b8c9d0

Când faci un commit pe o ramură, Git scrie noul SHA al commit-ului în acest fișier. Aceasta este totalitatea „avansării unui pointer de ramură.”

Ramuri de Urmărire și Configurarea Upstream

O relație de urmărire îi spune lui Git cu ce ramură remote ar trebui să compare o ramură locală pentru raportarea divergenței git status și comportamentul git pull.

git branch --set-upstream-to=origin/main main
git branch -vv    # Show tracking relationships and ahead/behind counts

Tag-uri: Markeri Permanenți în Istoric

Tag-urile marchează commit-uri specifice ca semnificative — de obicei lansări de software. Spre deosebire de ramuri, tag-urile nu sunt mutate de commit-uri noi.

CaracteristicăTag UșorTag Adnotat
StocareUn fișier ref care indică spre un commitUn obiect tag în depozitul de obiecte
MetadateNiciunaNumele tagger-ului, email, dată, mesaj
Semnare GPGNu este posibilSuportat prin git tag -s
Recomandat pentru lansăriNuDa
Transfer cu git push --tagsDaDa
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 tag

Capcană critică: git push nu trimite tag-urile implicit. Echipele uită frecvent acest lucru și publică note de lansare care fac referire la un tag ce nu există pe remote.

Remote-uri: Colaborare Distribuită

Un remote este un URL cu nume stocat în .git/config. Ramurile de urmărire remote (sub refs/remotes/) sunt snapshot-uri locale read-only ale ramurilor remote-ului, actualizate doar când faci fetch explicit.

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 tracking

Multiple Remote-uri

Un singur repository poate urmări mai multe remote-uri — comun când menții un fork alături de upstream:

git remote add upstream git@github.com:original/repo.git
git fetch upstream
git merge upstream/main

Când găzduiești propriile repository-uri bare pe un server dedicat pentru echipa ta, fiecare dezvoltator adaugă serverul ca remote și folosește autentificarea prin cheie SSH pentru acces push.

Hook-uri: Automatizare la Fiecare Eveniment Git

Hook-urile sunt scripturi executabile în .git/hooks/. Git le apelează la puncte definite în fluxul de lucru. Ele nu sunt transferate prin git clone sau git push — fiecare dezvoltator (sau server) trebuie să le instaleze independent. Aceasta este o sursă frecventă de confuzie în mediile de echipă.

Hook-uri pe Partea Clientului

HookDeclanșatorUtilizare Comună
pre-commitÎnainte de promptul mesajului de commitLinting, scanare secrete, execuție teste
prepare-commit-msgDupă crearea mesajului implicitInjectarea numelui ramurii în mesaj
commit-msgDupă ce utilizatorul scrie mesajulImpunerea formatului conventional commit
post-commitDupă înregistrarea commit-uluiNotificări locale
pre-pushÎnainte de executarea git pushRularea suitei complete de teste
pre-rebaseÎnainte de începerea rebasePrevenirea rebase-ului pe ramuri publicate

Hook-uri pe Partea Serverului

HookDeclanșatorUtilizare Comună
pre-receiveÎnainte de actualizarea ref-urilorImpunerea protecției ramurilor, respingerea force-push
updatePer-ref în timpul primiriiImpunerea politicii per-ramură
post-receiveDupă actualizarea tuturor ref-urilorDeclanșarea CI/CD, trimiterea notificărilor

Exemplu: Hook Pre-commit pentru Detectarea Secretelor

#!/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 0

Fă-l executabil:

chmod +x .git/hooks/pre-commit

Pentru distribuirea hook-urilor la nivelul echipei, folosește un instrument precum Husky (proiecte Node.js) sau stochează hook-urile într-un director hooks/ la rădăcina repository-ului și creează symlink-uri către ele în timpul configurării proiectului.

Reflog: Plasa de Siguranță

Reflog-ul înregistrează fiecare mișcare a pointerilor HEAD și ai ramurilor, inclusiv operațiunile care par să distrugă istoricul (resetări hard, rebase-uri, commit-uri amendate). Este stocat în .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 entry

Intrările din reflog expiră după 90 de zile implicit (gc.reflogExpire). Pe un server de producție, ia în considerare extinderea acestei perioade:

git config gc.reflogExpire 180
git config gc.reflogExpireUnreachable 30

Repository-uri Bare: Găzduire pe Partea Serverului

Un repository bare nu are director de lucru. Conține doar conținutul .git/ la nivelul rădăcină. Repository-urile bare sunt formatul corect pentru găzduirea centralizată — acceptă push-uri fără complicațiile unei ramuri checked-out.

git init --bare /srv/repos/myproject.git

Când faci push pe GitHub, GitLab sau un server Git găzduit propriu, faci push pe un repository bare. Dacă îți găzduiești propriul server Git pe un VPS cu cPanel sau un VPS Linux simplu, repository-urile bare sub /srv/repos/ cu acces SSH sunt arhitectura standard.

Inițializarea unui Repository Bare Partajat

# 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 main

Stocarea Obiectelor Git: Dimensiune, Integritate și Întreținere

Verificarea Sănătății Repository-ului

git fsck --full          # Verify object integrity (finds dangling and corrupt objects)
git fsck --lost-found    # Write dangling objects to .git/lost-found/

Găsirea și Eliminarea Obiectelor Mari

Fișierele binare mari comise accidental sunt o cauză comună a repository-urilor supradimensionate. Identifică-le înainte de a folosi git filter-repo pentru a le elimina:

# 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-paths

După filtrare, toți colaboratorii trebuie să recloneze — repository-urile lor locale fac referire la hash-uri SHA care nu mai există în istoricul rescris.

Comparație: Concepte Cheie ale Repository-ului Git

ConceptTipMutabilStocat ÎnTransferat prin Push/Fetch
BlobObiectNu.git/objects/Da (când este accesibil)
TreeObiectNu.git/objects/Da (când este accesibil)
CommitObiectNu.git/objects/Da (când este accesibil)
Tag AdnotatObiectNu.git/objects/Doar cu --tags
RamurăRefDa.git/refs/heads/Da
Ramură de urmărire remoteRefDa (la fetch).git/refs/remotes/Nu (cache local)
Tag UșorRefNu.git/refs/tags/Doar cu --tags
HEADSymref/hashDa.git/HEADNu
IndexFișier binarDa.git/indexNu
Hook-uriScripturiDa.git/hooks/Nu
ReflogJurnalDa (expiră automat).git/logs/Nu

Matricea de Decizie Practică și Concluzii Cheie

Folosește această listă de verificare când configurezi sau auditezi un repository Git pe infrastructura ta:

Inițializarea repository-ului

  • Folosește git init --bare --shared=group pentru orice repository care va primi push-uri de la mai mulți utilizatori.
  • Stochează repository-urile bare în afara directoarelor accesibile web (niciodată sub /var/www/).

Sănătatea depozitului de obiecte

  • Rulează git fsck --full după orice incident de stocare sau eroare de sistem de fișiere.
  • Programează git gc periodic pe repository-uri cu viață lungă; automatizează-l prin cron pe serverul tău.
  • Monitorizează dimensiunea fișierelor pack cu git count-objects -vH; investighează dacă numărul obiectelor loose depășește 1.000.

Igiena ramurilor și ref-urilor

  • Șterge ramurile merged prompt; ref-urile vechi se acumulează și încetinesc operațiunile git fetch --prune.
  • Folosește git fetch --prune în pipeline-urile CI pentru a evita acțiunile pe ramurile remote șterse.

Implementarea hook-urilor

  • Nu te baza niciodată pe .git/hooks/ pentru politici la nivelul echipei — hook-urile nu sunt clonate. Folosește în schimb hook-uri pre-receive pe partea serverului sau o poartă CI.
  • Auditează hook-urile de pe partea serverului după fiecare upgrade al serverului Git; căile interpretorului de hook-uri se pot schimba.

Securitate pe serverele găzduite propriu

  • Restricționează accesul SSH la utilizatorul git cu comenzi forțate (command= în authorized_keys).
  • Folosește git-shell ca shell de autentificare pentru utilizatorul git pentru a preveni execuția de comenzi arbitrare.
  • Asociază serverul tău de repository cu un certificat SSL valid dacă expui orice interfață web (Gitea, GitLab, cgit).

Rescrierea istoricului

  • Nu rescrie niciodată istoricul pe ramuri partajate cu alții fără un plan de migrare coordonat.
  • După git filter-repo, toți colaboratorii trebuie să recloneze; actualizează imediat URL-urile remote CI/CD.

Recuperare în caz de dezastru

  • Extinde expirarea reflog pe serverele de producție (gc.reflogExpire = 180).
  • Păstrează o clonă bare secundară pe un host separat ca backup; un simplu git fetch de la cel primar este suficient.

Întrebări Frecvente

Care este diferența dintre un repository Git bare și unul non-bare?

Un repository non-bare are un director de lucru unde fișierele sunt checked out, plus un subdirector .git/ care conține depozitul de obiecte. Un repository bare conține doar depozitul de obiecte la rădăcina sa (fără director de lucru) și este formatul corect pentru un server partajat care primește push-uri.

Pot recupera commit-urile după rularea git reset --hard?

Da, atât timp cât commit-urile nu au fost colectate de garbage collector. Rulează git reflog pentru a găsi SHA-ul commit-ului pe care vrei să îl recuperezi, apoi git checkout -b recovery-branch <SHA> pentru a-l atașa la o nouă ramură. Intrările din reflog sunt păstrate timp de 90 de zile implicit.

De ce git push nu transferă tag-urile mele?

Prin design, git push transferă doar commit-urile accesibile din ref-urile pe care le trimiți explicit. Tag-urile sunt ref-uri separate și trebuie trimise cu git push origin --tags (toate tag-urile) sau git push origin <tagname> (un tag specific).

Ce se întâmplă cu indexul în timpul unui conflict de merge?

Indexul stochează toate cele trei versiuni ale fiecărui fișier conflictual simultan: etapa 1 (ancestorul comun/baza), etapa 2 (versiunea ta) și etapa 3 (versiunea lor). git add normal scrie doar etapa 0 (rezolvată). Până când toate conflictele sunt rezolvate și staged, git commit va refuza să continue.

Cum diferă hook-urile Git între implementările pe client și pe server?

Hook-urile pe partea clientului rulează pe mașina dezvoltatorului și nu sunt impuse central — orice dezvoltator le poate ocoli ștergând fișierul hook. Hook-urile pe partea serverului (pre-receive, update, post-receive) rulează pe serverul de găzduire și nu pot fi ocolite de client, făcându-le punctul corect de impunere pentru politicile de protecție a ramurilor, cerințele de code review și declanșatoarele CI/CD.

Linux
Linux Securitate Servere virtuale
Administrație Linux