Konflikt w Git: dlaczego powstaje i jak go rozwiązać krok po kroku

Mateusz Bogolubow

Mateusz Bogolubow

26-10-2026

(aktualizacja: 26-10-2026)

Konflikt w Git to nie awaria, tylko prośba o decyzję. Zobacz, skąd się bierze, jak go przeczytać i rozwiązać oraz jak sprawdzić, że po scaleniu aplikacja nadal działa.

Konflikt w Git: dlaczego powstaje i jak go rozwiązać krok po kroku
Konflikt w Git powstaje, gdy dwie osoby zmieniły te same linie pliku (albo jedna usunęła plik, który druga edytowała) i Git nie wie, której wersji użyć. Otwierasz plik ze znacznikami <<<<<<<, =======, >>>>>>>, wybierasz lub łączysz właściwy kod, usuwasz znaczniki, robisz git add i kończysz scalanie. Po rozwiązaniu uruchom testy, bo Git może scalić zmiany bez konfliktu i mimo to zepsuć aplikację.

Pierwszy konflikt w Git zwykle wygląda jak awaria: w terminalu pojawia się słowo CONFLICT, a w pliku dziwne strzałki. W rzeczywistości to najzwyklejsza sytuacja w pracy z drugą osobą. Git nie psuje Ci kodu, tylko mówi: „Dwie osoby zmieniły to samo miejsce, a ja nie zgaduję, kto ma rację”. Poniżej: skąd się to bierze, jak to rozwiązać krok po kroku i jak sprawdzić, że po scaleniu wszystko nadal działa.

Czym jest konflikt w Git i kiedy powstaje?

Konflikt (ang. merge conflict) pojawia się, gdy Git łączy dwie wersje kodu i nie potrafi automatycznie zdecydować, którą wybrać. Dzieje się tak w trzech sytuacjach:

  • obie osoby zmieniły te same linie tego samego pliku,
  • jedna osoba usunęła lub przeniosła plik, a druga w tym czasie go edytowała,
  • obie osoby dodały plik o tej samej nazwie, ale z różną zawartością.

Git scala zmiany automatycznie, dopóki dotyczą różnych miejsc. Jeśli Anna zmieni trzecią funkcję w pliku, a Piotr dziesiątą, Git połączy to sam. Konflikt pojawia się dopiero tam, gdzie obie zmiany „stoją sobie na drodze”.

Dlaczego konflikty się pojawiają?

Sam Git jest tylko narzędziem, które wykrywa problem. Prawdziwa przyczyna leży w organizacji pracy: kilka osób pracuje równolegle na tej samej bazie kodu i każda z nich ma własną, odizolowaną kopię.

PrzyczynaPrzykład z projektuCzy Git zgłosi konflikt?
Dwie osoby zmieniają ten sam fragmentobie poprawiają tę samą funkcję liczącą cenętak
Plik, który dotyka cały zespółlista tras, plik konfiguracyjny, package.jsonczęsto tak
Długo żyjąca gałąźgałąź funkcji istnieje trzy tygodnie, a main ciągle się zmieniatak, i to wiele naraz
Automatyczne formatowaniejedna osoba ma inne ustawienia edytora i zmienia wcięcia w całym plikutak, w wielu miejscach
Zmiana nazwy lub usunięcie plikujedna osoba przenosi plik, druga go edytujetak
Zmiana w jednym pliku zależna od drugiegojedna osoba zmienia nazwę funkcji, druga używa starej nazwy w nowym plikunie, ale aplikacja przestaje działać

Ostatni wiersz jest najgroźniejszy, bo Git milczy. Wrócimy do niego w sekcji o sprawdzaniu wyniku po scaleniu. To, jak zespół dzieli pracę i jak często łączy zmiany, ma na liczbę konfliktów większy wpływ niż umiejętność obsługi Gita. Opisujemy to osobno w artykule o pracy zespołowej nad aplikacją w Git.

Jak wygląda konflikt w praktyce?

Załóżmy plik cart.js z podatkiem liczonym w koszyku:

const price = 100;
const tax = 0.23;
export const total = price * (1 + tax);

Anna, na swojej gałęzi, zmienia stawkę na 0.19. Piotr, na swojej, zmienia nazwę stałej na VAT i stawkę na 0.08. Zmiana Anny trafia do main jako pierwsza. Kiedy teraz scalasz gałąź Piotra:

git merge piotr

Git odpowiada:

Auto-merging cart.js
CONFLICT (content): Merge conflict in cart.js
Automatic merge failed; fix conflicts and then commit the result.

To jest komunikat, a nie błąd w kodzie: scalanie zostało wstrzymane w połowie i czeka na Twoją decyzję. Polecenie git status pokazuje, których plików to dotyczy:

On branch main
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
	both modified:   cart.js

Zwróć uwagę na dwie podpowiedzi w nawiasach: Git sam mówi, jak kontynuować i jak się wycofać.

Jak czytać znaczniki konfliktu w pliku?

Po otwarciu cart.js zobaczysz:

const price = 100;
<<<<<<< HEAD
const tax = 0.19;
export const total = price * (1 + tax);
=======
const VAT = 0.08;
export const total = price * (1 + VAT);
>>>>>>> piotr
FragmentCo oznacza
<<<<<<< HEADpoczątek konfliktu; poniżej kod z gałęzi, na której jesteś (tu main)
=======granica między dwiema wersjami
>>>>>>> piotrkoniec konfliktu; powyżej kod z gałęzi, którą scalasz (tu piotr)

Wszystko poza znacznikami (tutaj linia const price = 100;) Git scalił sam i nie musisz tego ruszać.

Jeśli sama zawartość dwóch bloków nie wystarcza, żeby zrozumieć, co się stało, włącz styl diff3. Doda trzecią sekcję: wersję wspólnego przodka, czyli kod sprzed obu zmian:

git config merge.conflictstyle diff3

Wtedy ten sam konflikt wygląda tak:

<<<<<<< HEAD
const tax = 0.19;
export const total = price * (1 + tax);
||||||| abe62bd
const tax = 0.23;
export const total = price * (1 + tax);
=======
const VAT = 0.08;
export const total = price * (1 + VAT);
>>>>>>> piotr

Teraz widać, że Anna zmieniła tylko wartość (z 0.23 na 0.19), a Piotr zmienił nazwę i wartość. Bez wersji pierwotnej łatwo uznać, że obie zmiany są równorzędne, i wybrać źle.

Jak rozwiązać konflikt w Git krok po kroku?

  1. Sprawdź, których plików dotyczy konflikt. Uruchom git status albo, jeśli chcesz samą listę, git diff --name-only --diff-filter=U.
  2. Otwórz plik i znajdź znaczniki. Wyszukaj w edytorze ciąg <<<<<<<.
  3. Zdecyduj, jak ma wyglądać wynik. Masz trzy opcje: zostawić wersję z HEAD, zostawić wersję z drugiej gałęzi albo napisać wersję łączącą obie. W naszym przykładzie sensowne może być zachowanie nazwy VAT i wartości od Anny, ale tę decyzję musisz podjąć wspólnie z autorami, bo nikt poza nimi nie wie, czy 0.19 jest nowym wymaganiem, czy pomyłką.
  4. Usuń znaczniki. W pliku nie mogą zostać linie <<<<<<<, ======= ani >>>>>>>. Zostaje czysty kod:
const price = 100;
const VAT = 0.19;
export const total = price * (1 + VAT);
  1. Uruchom aplikację lub testy. Plik musi się zbudować, zanim uznasz konflikt za rozwiązany (więcej w sekcji o konflikcie semantycznym).
  2. Oznacz plik jako rozwiązany. Git pyta tylko o jedno: czy skończyłeś z tym plikiem.
git add cart.js
  1. Dokończ scalanie. Wystarczy git commit (Git podpowie gotowy komunikat „Merge branch …”) albo git merge --continue.

Edytory takie jak VS Code pokazują nad blokiem konfliktu przyciski do wyboru wersji („przyjmij bieżącą”, „przyjmij przychodzącą”, „przyjmij obie”). To wygodny skrót, ale robi dokładnie to samo, co ręczna edycja pliku, więc nadal sam odpowiadasz za wynik.

Jak wycofać się z konfliktu, gdy coś poszło nie tak?

Jeśli w połowie rozwiązywania stwierdzisz, że to nie jest dobry moment, albo nie rozumiesz, co się dzieje, wracasz do stanu sprzed scalania jednym poleceniem:

git merge --abort

Nic nie tracisz: repozytorium wraca do stanu sprzed git merge. Dla rebase odpowiednikiem jest git rebase --abort. Po git pull (który domyślnie scala zmiany) też użyjesz git merge --abort.

Przerwanie jest w porządku. Gorzej jest „na siłę” skończyć scalanie, którego nie rozumiesz, i wypchnąć wynik do zespołu.

Czym różni się konflikt przy merge, rebase i pull?

Mechanizm jest ten sam, ale zmienia się to, co robisz po rozwiązaniu, i znaczenie ours oraz theirs.

git mergegit rebasegit pull
Co robiłączy dwie gałęzie jednym commitem scalającymprzenosi Twoje commity na koniec innej gałęzipobiera zmiany (fetch) i scala je (merge)
Po rozwiązaniugit add + git commit (lub git merge --continue)git add + git rebase --continuejak merge
Wycofaniegit merge --abortgit rebase --abortgit merge --abort
Konfliktyraz, w całościmożliwe osobno przy każdym commiciejak merge
Znaczenie oursgałąź, na której jesteśgałąź, na którą przenosisz commityjak merge
Znaczenie theirsgałąź, którą scalaszTwoje własne commityjak merge

Wiersze z ours i theirs zaskakują najbardziej. Sprawdziliśmy to na tym samym przykładzie: gdy Piotr robi git rebase anna, to w trakcie konfliktu „nasza” wersja (ours) to kod Anny, a „ich” (theirs) to commit Piotra, czyli odwrotnie, niż podpowiada intuicja. Dlatego polecenie git checkout --ours -- cart.js w trakcie rebase przywróci wersję z gałęzi, na którą rebasujesz, a nie Twoją. Ten sam mechanizm stoi za częstym pytaniem na Stack Overflow, dlaczego „moje” i „ich” zmiany są zamienione.

Jakie są rodzaje łączenia gałęzi i kiedy dają konflikt?

Nie każde łączenie gałęzi daje ten sam wynik w historii, a konflikt może pojawić się tylko tam, gdzie Git musi coś faktycznie scalić.

RodzajJak działaWynik w historiiKonflikt?
Fast-forwardmain nie zmieniło się od odejścia gałęzi, więc Git tylko przesuwa wskaźnik do przoduliniowa historia, brak commita scalającegonie może wystąpić
Merge commit (git merge --no-ff oraz zwykły merge, gdy main poszło do przodu)Git łączy dwie linie historii i zapisuje commit scalającyhistoria z „rozgałęzieniem”tak, raz, w całości
Squash (git merge --squash)wszystkie commity gałęzi trafiają do main jako jeden nowy commitjeden commit zamiast kilku, bez commita scalającegotak, raz; squash nie tworzy konfliktów, których nie byłoby przy zwykłym merge
Rebasecommity gałęzi są odtwarzane jeden po drugim na końcu mainliniowa historia, commity z nowymi identyfikatoramitak, i może wystąpić osobno przy każdym commicie

Na GitHubie te same podejścia widzisz jako opcje przy scalaniu pull requesta: Create a merge commit, Squash and merge i Rebase and merge. Którą wybrać, ustala zespół, bo od tego zależy wygląd historii.

Praktyczny wniosek: fast-forward działa tylko wtedy, gdy nikt inny nie zmienił main w międzyczasie. Im dłużej żyje gałąź, tym rzadziej tak jest, tym częściej Git musi naprawdę scalać i tym częściej trafia na konflikt.

Co zrobić z plikami, których nie da się scalić ręcznie?

  • package-lock.json i podobne pliki blokady. Nie rozwiązuj ich ręcznie, linia po linii. Najpierw rozwiąż konflikt w package.json, a potem wygeneruj plik blokady od nowa (npm install) i dodaj wynik do commita.
  • Pliki binarne (obrazki, PDF-y). Git nie ma jak połączyć dwóch wersji. Wybierasz jedną: git checkout --ours -- plik albo git checkout --theirs -- plik, a potem git add plik.
  • Pliki generowane przez narzędzia (np. katalog dist/). Zwykle nie powinny być w repozytorium. Dodaj je do .gitignore, a konfliktów w nich w ogóle nie będzie.

Czy po rozwiązaniu konfliktu wszystko na pewno działa?

Nie, i to jest najczęściej pomijany krok. Brak konfliktu w Gicie znaczy tylko, że nie zmieniono tych samych linii. Nie znaczy, że kod razem działa.

Sprawdziliśmy to na małym przykładzie. Plik price.js eksportował funkcję calcPrice. Anna zmieniła jej nazwę na calculatePrice i poprawiła wywołanie w a.js. W tym samym czasie Piotr dodał nowy plik b.js, który importował starą nazwę calcPrice. Wynik scalania:

git merge piotr
 1 file changed, 2 insertions(+)
 create mode 100644 b.js

Bez żadnego ostrzeżenia. Dopiero uruchomienie kodu pokazuje, co się stało:

SyntaxError: The requested module './price.js' does not provide an export named 'calcPrice'

To tzw. konflikt semantyczny: zmiany nie nachodzą na siebie w tekście, ale kłócą się znaczeniem. Dlatego po każdym scaleniu, z konfliktem lub bez, warto:

  • uruchomić testy (albo choć aplikację),
  • sprawdzić, czy przechodzi automatyczna weryfikacja na GitHubie (CI),
  • poprosić drugą osobę o spojrzenie na wynik scalenia, zwłaszcza po trudnym konflikcie. Po to właśnie istnieje code review.

Jakie błędy najczęściej popełniają początkujący przy konfliktach?

BłądSkutekJak tego uniknąć
Zostawiony znacznik <<<<<<< w plikuaplikacja się nie buduje, a w repozytorium ląduje „zepsuty” commitprzed git add wyszukaj w projekcie ciąg <<<<<<<
Wybranie „swojej” wersji w całości, bo szybciejkasujesz cudzą pracę, a autor dowiaduje się po kilku dniachprzeczytaj obie wersje i, gdy nie wiesz, zapytaj autora
Dokończenie scalania bez uruchomienia kodukonflikt semantyczny przechodzi niezauważonyuruchom testy lub aplikację przed git commit
git push --force „żeby się pozbyć problemu”nadpisujesz zmiany innych osób na zdalnym repozytoriumprzy konflikcie nie używaj --force; najpierw pobierz i scal zmiany
Ignorowanie komunikatu i panikaprzypadkowe git reset --hard kasuje niezapisaną pracęzacznij od git status i, jeśli trzeba, git merge --abort

Jaki błąd mentor widzi przy konfliktach najczęściej?

W projektach uczniów rzadko problemem jest składnia Gita. Częściej chodzi o decyzję: osoba zostawia swoją wersję, bo to ona ją napisała, i nie sprawdza, co zmieniła druga strona. Scalanie przechodzi, a po kilku dniach okazuje się, że zniknęła cudza poprawka. Dlatego przy konflikcie warto przeczytać obie wersje i dopiero wtedy wybierać.

Jak sprawić, żeby konfliktów było mniej?

Najkrócej: pracuj na małych zmianach, często aktualizuj swoją gałąź względem main i ustal z zespołem, kto zajmuje się którym obszarem kodu. Wspólne formatowanie (Prettier, .editorconfig) usuwa całą klasę konfliktów o wcięcia. Szczegóły, na tle tego, jak w ogóle wygląda dzień pracy zespołu nad aplikacją, znajdziesz w osobnym tekście o pracy zespołowej w Git: gałęziach i pull requestach.

Jeśli dopiero uczysz się podstaw (klonowanie, fork i pull request), konflikt będzie jednym z pierwszych momentów, w którym praca z Gitem przestaje być teorią. W kursie JavaScript zadania zgłaszasz jako pull requesty, a mentor robi code review Twojego kodu, więc z Gitem ćwiczysz na własnych zmianach, a nie tylko na przykładach z poradnika.

Konsultacja i warsztaty devmentor.pl

Masz pytania?

Partnerzy

Firmy, z którymi współpracujemy

To partnerzy odpowiedzialni za staże po naszych kursach. Organizują je dla najlepszych absolwentów.

Zobacz wszystkich partnerów →
  • VR Medical
  • Sophona
  • Robot Force