Conflictele de fuziune apar atunci când doi dezvoltatori modifică aceleași linii dintr-un fișier sau când unul redenumește/șterge un fișier pe care celălalt îl modifică. În ciuda reputației de a fi frustrante, conflictele sunt modul prin care Git refuză să șteargă sau să suprascrie silențios munca unei persoane, protejând integritatea bazei de cod.
1. Anatomia unui Conflict și Algoritmul Three-Way Merge
Git nu compară doar două fișiere (versiunea ta și versiunea colegului), ci folosește un algoritm 3-Way Merge ce implică trei puncte cheie:
- Baza comună (Base): Cel mai recent strămoș comun al celor două ramuri (identificat automat prin
git merge-base ramura-A ramura-B). - Versiunea noastră (Ours / Current change): Starea fișierului pe ramura pe care vă aflați în momentul fuziunii (HEAD).
- Versiunea lor (Theirs / Incoming change): Starea fișierului de pe ramura pe care încercați să o integrați.
Dacă linia 40 a fost modificată doar de o parte față de baza comună, Git o integrează automat. Doar dacă ambele părți au modificat diferit linia 40 față de baza comună, Git oprește procesul și semnalează un conflict.
2. Îmbunătățirea Vizibilității Markerilor: Setarea zdiff3
În mod implicit, Git arată doar ce aveți voi și ce vine din cealaltă ramură, ascunzând codul original din strămoșul comun. Activarea stilului zdiff3 adaugă secțiunea ||||||| base, făcând decizia de rezolvare de zece ori mai ușoară:
# Activare la nivel global în Git:
git config --global merge.conflictstyle zdiff3
Iată cum arată un fișier în conflict cu zdiff3:
<<<<<<< HEAD (Modificarea voastră curentă)
const MAX_ATTEMPTS = 5;
||||||| 3f8b1a9 (Codul original din strămoșul comun)
const MAX_ATTEMPTS = 3;
=======
const MAX_ATTEMPTS = Config.get('login_limit', 10);
>>>>>>> feature/dynamic-config (Modificarea colegului)
Văzând codul din mijloc (|||||||), înțelegeți imediat că valoarea inițială a fost 3, voi ați mărit-o la 5, iar colegul a refactorizat-o să citească dintr-o configurație. Rezoluția logică este combinarea ambelor idei: păstrarea configurației dinamice cu valoarea implicită 5.
3. Tehnici Rapide de Rezolvare în Terminal
# SCENARIUL 1: Vreți să abandonați complet încercarea de fuziune și să reveniți la starea curată:
git merge --abort
# sau în caz de rebase:
git rebase --abort
# SCENARIUL 2: Știți sigur că versiunea voastră este cea 100% corectă:
git checkout --ours cale/catre/fisier.json
git add cale/catre/fisier.json
# SCENARIUL 3: Știți sigur că fișierul venit din cealaltă ramură este cel dorit:
git checkout --theirs cale/catre/fisier.json
git add cale/catre/fisier.json
# SCENARIUL 4: Lansarea utilitarului grafic de mediere (VS Code, Meld sau KDiff3):
git mergetool
4. Rebase Interactiv și Curățarea Istoriei
Atunci când faceți rebase pe o ramură activă (git rebase origin/main), fiecare comitere din ramura voastră este reluată una câte una peste noul vârf al ramurii main. Dacă apare un conflict:
- Git întrerupe procesul la comiterea exactă care cauzează conflictul.
- Editați fișierul și eliminați markerii de conflict.
- Salvați și adăugați fișierul în staging:
git add fisier.txt(Important: NU rulațigit commit!). - Continuați procesul cu:
git rebase --continue.
5. Automatizarea Re-Rezolvării Conflictelor cu git rerere
Dacă mențineți o ramură de lungă durată pe care faceți rebase frecvent, sau dacă fuzionați periodic o ramură de test, este extrem de obositor să rezolvați manual exact aceleași conflicte de fiecare dată. Mecanismul rerere (Reuse Recorded Resolution) învață decizia luată o dată și o aplică automat în viitor:
# Activare globală permanentă:
git config --global rerere.enabled true
# La primul conflict:
# 1. Rezolvați conflictul în fișier și rulați git add.
# 2. La rularea comenzii git commit sau git rebase --continue,
# Git salvează automat o amprentă (hash) a conflictului și rezoluția aleasă în .git/rr-cache/
# La următorul rebase sau merge unde apare același conflict:
# Git afișează: "Resolved 'fisier.txt' using previous resolution."
# Conflictul este rezolvat instantaneu și fără efort manual!
6. Bune Practici pentru Minimizarea Conflictelor
- Sincronizare continuă (Rebase frecvent): Rulați în fiecare dimineață
git pull --rebase origin mainpe ramura activă de lucru. Rezolvarea unui conflict de 2 linii este mult mai ușoară decât rezolvarea a 50 de conflicte la sfârșitul sprintului. - Evitați reformatele cosmetice masive în PR-uri de logică: Nu rulați Prettier / PHP-CS-Fixer / Black pe întreg proiectul în timp ce schimbați logica de business. Schimbările de formatare trebuie izolate în comiteri separate dedicate.
- Modularizare: Împărțiți fișierele gigantice monolitice în componente mici și independente. Două persoane care lucrează pe fișiere diferite nu vor genera niciodată conflicte.