U prošlom postu u Git serijalu spomenuli smo mogućnost konflikata prilikom rebaziranja ili spajanja grana te dali par sugestija kako ih izbjeći. No kod kompliciranijih projekata i workflowova konflikti su ponekad neizbježni, stoga je korisno znati kako riješiti konflikt kad do njega dođe.
Rješavanje konflikata
Ukoliko sami radite na projektu, konflikti vam se vjerojatno neće događati (osim ukoliko ne pazite). No, ukoliko radite na projektu sa više ljudi, može vam se zadesiti da se nađete u situaciji gdje ćete morati riješiti konflikt.
Recimo da je naš helloworld projekt dobio na važnosti i sada na njemu rade dva developera, Mirko i Slavko. Mirko i Slavko u stvarnom životu radit će na svojim granama u svojim klonovima repozitorija, ali da si ovdje pojednostavnimo priču, svaki će imati po jednu granu u našem zajedničkom repozitoriju.
Kreirajmo obje grane, bez prebacivanja u njih:
> git checkout master
Switched to branch 'master'
> git branch mirko
> git branch slavko
> git branch -l
helloworld-0
* master
mirko
slavko
Dolazi Mirko, vidi problem u načinu kako je definirana funkcija main i popravlja ga u svojoj grani:
> git checkout mirko
Switched to branch 'mirko'
[...promjene hello.c...]
> git diff
diff --git a/hello.c b/hello.c
index 0bb4941..8b08068 100644
--- a/hello.c
+++ b/hello.c
@@ -1,6 +1,7 @@
#include <stdio.h>
-int main(void)
+int
+main(int argc, char *argv[])
{
printf("Hello world\n");
return 0;
> git commit -a -m "popravio main prototip"
[mirko 4e5f904] popravio main prototip
1 files changed, 2 insertions(+), 1 deletions(-)
U međuvremenu, dolazi Slavko, vidi isti problem, ne zna da je Mirko radio na istoj stvari pa ga rješava kod sebe:
> git checkout slavko
Switched to branch 'slavko'
[...promjene hello.c...]
> git diff
diff --git a/hello.c b/hello.c
index 0bb4941..38586b0 100644
--- a/hello.c
+++ b/hello.c
@@ -1,6 +1,6 @@
#include <stdio.h>
-int main(void)
+int main(int argc, char **argv)
{
printf("Hello world\n");
return 0;
> git commit -a -m "prepravio definiciju funkcije main"
[slavko 4bb59be] prepravio definiciju funkcije main
1 files changed, 1 insertions(+), 1 deletions(-)
Sve je dobro dok su promjene u zasebnim granama. No u jednom trenu ćemo željeti sve Mirkove i Slavkove promjene spojiti u master. Prva promjena će proći dobro, no na drugoj će nam se dogoditi konflikt.
> git checkout master
Switched to branch 'master'
> git merge mirko
git merge mirko
Updating 302ef80..4e5f904
Fast-forward
hello.c | 3 ++-
1 files changed, 2 insertions(+), 1 deletions(-)
> git merge slavko
git merge slavko
Auto-merging hello.c
CONFLICT (content): Merge conflict in hello.c
Automatic merge failed; fix conflicts and then commit the result.
Uh, huh… obojica su mijenjala isto mjesto u istoj datoteci, i git ne zna kako riješiti problem. Zbog toga je merge pauziran i dana nam je prilika da riješimo konflikt. Pogledajmo što se dogodilo:
git diff
diff --cc hello.c
index 8b08068,38586b0..0000000
--- a/hello.c
+++ b/hello.c
@@@ -1,7 -1,6 +1,11 @@@
#include <stdio.h>
++<<<<<<< HEAD
+int
+main(int argc, char *argv[])
++=======
+ int main(int argc, char **argv)
++>>>>>>> slavko
{
printf("Hello world\n");
return 0;
> cat hello.c
#include <stdio.h>
<<<<<<< HEAD
int
main(int argc, char *argv[])
=======
int main(int argc, char **argv)
>>>>>>> slavko
{
printf("Hello world\n");
return 0;
}
Diff prikazuje obje nekompatibilne promjene. Prva je iz trenutne grane (odnosno to je ona Mirkova promjena koju smo uspješno preuzeli spajanjem Mirkove grane), dok je druga iz grane slavko. Rješimo konflikt modificiranjem tog dijela datoteke da izgleda onako kako mi želimo:
[...promjene hello.c...]
cat hello.c
#include <stdio.h>
int
main(int argc, char **argv)
{
printf("Hello world\n");
return 0;
}
> git diff
diff --cc hello.c
index 8b08068,38586b0..0000000
--- a/hello.c
+++ b/hello.c
@@@ -1,7 -1,6 +1,7 @@@
#include <<tdio.h>
-int main(int argc, char **argv)
+int
- main(int argc, char *argv[])
++main(int argc, char **argv)
{
printf("Hello world\n");
return 0;
> git add hello.c
> git commit
[master fd96bab] Merge branch 'slavko'
Diff prikaz u slučaju konflikata je pomalo zbunjujuć, no snalaženje u samoj datoteci koju treba popraviti je relativno jednostavno - konflikt je između <<<<<<< i >>>>>>>, a ======= razdvaja konfliktne promjene.
Konflikt prilikom rebaziranja
Kao što smo već prije vidjeli, obično želimo grane održavati tako da u svakom trenu njihove promjene budu primjenjive na master (odnosno na baznu granu gdje ih na kraju želimo spojiti), a jedan od razloga je upravo izbjegavanje konflikata kod spajanja.
Jedan od razloga zbog kojih je konflikt kod spajanja nezgodan je i činjenica da problem praktički "prebacujemo" onom tko radi merge, a kod većih projekata to je obično osoba koja održava projekt (tj. glavni developer). Ukoliko od nje tražite da granu sa vašim promjenama spaja sa masterom, a to izaziva konflikte kod mergea, zapravo ste prilično nepristojni.
Zato je bolje prije nego zatražite (ili krenete raditi) spajanje rebazirati svoju granu tako da samo spajanje bude čisto. To znači da ćete eventualne konflikte morati sami rješiti prilikom rebaziranja.
Vratimo se malo u prošlost i zaboravimo da smo radili merge Mirkove i Slavkove grane:
> git reset --hard 302ef80039bd55fbbc680d84e37e5ebcf7f63a5c
HEAD is now at 302ef80 dodan README
Mirko odluči spojiti svoju granu s masterom. Njegovu granu nije potrebno rebazirati jer se nastavlja na master (tj master nema nikakvih novih promjena):
> git merge mirko
Updating 302ef80..4e5f904
Fast-forward
hello.c | 3 ++-
1 files changed, 2 insertions(+), 1 deletions(-)
Slavko odluči spojiti svoju granu. Njegovu granu je potrebno rebazirati jer
master ima novih comittova:
> git checkout slavko
Switched to branch 'slavko'
> git rebase master
First, rewinding head to replay your work on top of it...
Applying: prepravio definiciju funkcije main
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
Auto-merging hello.c
CONFLICT (content): Merge conflict in hello.c
Failed to merge in the changes.
Patch failed at 0001 prepravio definiciju funkcije main
When you have resolved this problem run "git rebase --continue".
If you would prefer to skip this patch, instead run "git rebase --skip".
To restore the original branch and stop rebasing run "git rebase --abort".
Ovo izgleda mnogo strašnije nego poruka kod mergea! Ali problem je isti, u što se možemo uvjeriti pomoću git diff (s tim da će umjesto imena naše grane biti naziv commita u našoj grani s kojim ima problema). Nakon što napravimo ispravke u datoteci, trebamo nastaviti proces rebaziranja:
[...promjene hello.c...]
> git add hello.c
> git rebase --continue
Applying: prepravio definiciju funkcije main
Ovdje nismo ručno radili git commit, nego smo nakon popravka nastavili proces rebaziranja. Kako rebaziranje ide commit po commit, ukoliko ima još konflikata u narednim commitovima, rebase bi se opet zaustavio i zatražio da popravimo stvari.
Ukoliko zaključimo da je commit nepotreban (npr. zato što je u drugoj grani taj problem već riješen), možemo ga odbaciti. Ako se radi o ozbiljnom problemu za kojeg ne znamo kako ga riješiti, možemo prekinuti rebase, a grana će nam se vratiti u prvobitno stanje. U tom slučaju ćemo vjerojatno morati rješavati merge konflikte prilikom spajanja i napraviti merge a ne rebase.