Git
Chapters ▾ 2nd Edition

3.2 Grananje u Gitu - Osnove grananja i spajanja

Osnove grananja i spajanja

Hajde da prođemo kroz jednostavan primer grananja i spajanja sa tokom rada kakav se često javlja u realnom svetu. Pratićete ove instrukcije:

  1. Radite na veb-sajtu.

  2. Napravite granu za novu priču na kojoj radite.

  3. Radite nešto na toj grani.

U ovom trenutku, dobijate poziv da postoji kritičan problem koji morate da rešite istog trenutka. Uradićete sledeće:

  1. Prebacite se na granu za produkciju.

  2. Napravite novu granu na kojoj ćete dodati kôd koji rešava problem.

  3. Kada ga testirate, spojite granu sa rešenjem problema, i gurnete na server.

  4. Vratite se nazad na priču na kojoj ste radili.

Osnove grananja

Prvo, pretpostavimo da radite na projektu na kome već imate nekoliko komitova.

Jednostavna istorija komitova.
Figure 18. Jednostavna istorija komitova.

Odlučili ste da ćete raditi na problemu #53 koji se nalazi na kom god sistemu za praćenje problema koristi vaša kompanija. Da biste napravili granu i skočili na nju istovremeno, možete da pokrenete komandu git checkout sa opcijom -b:

$ git checkout -b iss53
Switched to a new branch "iss53"

Ovo je skraćenica za:

$ git branch iss53
$ git checkout iss53
Kreiranje novog pokazivača na granu.
Figure 19. Kreiranje novog pokazivača na granu.

Radite neke stvari na svom veb-sajtu i obavite neke komitove. Dok radite to, grana iss53 se kreće napred, jer je ona čekautovana (odnosno, HEAD pokazuje na nju).

$ vim index.html
$ git commit -a -m 'added a new footer [issue 53]'
Grana `iss53` se pomerila napred u skladu s poslom koji ste obavili.
Figure 20. Grana iss53 se pomerila napred u skladu s poslom koji ste obavili.

Sada dobijate poziv da postoji problem sa vebsajtom, i morate odmah da ga popravite. Sa Gitom, ne morate da rešavate problem zajedno sa iss53 promenama koje ste napravili, i ne morate da ulažete mnogo truda u to da povratite te promene pre nego što možete da krenete da radite na rešavanju novonastalog problema. Sve što treba da uradite jeste da se prebacite na master granu.

Ipak, pre nego što to uradite, obratite pažnju na to da vam, ako vaš radni direktorijum ima nekomitovane promene koje su u konfliktu sa granom koju čekautujete, Git neće dozvoliti da promenite granu. Najbolje da radno stanje bude čisto kada pravite skok između grana. Postoje načini da se ovo zaobiđe (naime, skrivanje i komitovanje ispravki) koje ćemo obraditi kasnije, u [git_stashing]. Zasad, pretpostavimo da ste komitovali sve promene, tako da možete da se vratite na master granu:

$ git checkout master
Switched to branch 'master'

U ovom trenutku, radni direktorijum vašeg projketa izgleda isto onako kako je izgledao pre nego što se počeli da radite na problemu #53, i možete da se koncentrišete na hitni slučaj. Ovo je bitna stvar koju treba zapamtiti: kada menjate grane, Git resetuje radni direktorijum da izgleda onako kako je izgledao kada ste poslednji put komitovali na toj grani. Dodaje, briše i modifikuje fajlove automatski da bi se postarao da vaša radna kopija izgleda tačno onako kako je izgledala na grani kada ste uradili poslednji komit.

Sledeće, treba da rešite hitan problem. Napravićemo hotfix granu na kojoj ćemo raditi dok to ne bude sređeno.

$ git checkout -b hotfix
Switched to a new branch 'hotfix'
$ vim index.html
$ git commit -a -m 'fixed the broken email address'
[hotfix 1fb7853] fixed the broken email address
 1 file changed, 2 insertions(+)
`hotfix` grana bazirana na `master`-u.
Figure 21. hotfix grana bazirana na master-u.

Možete da testirate ono što ste uradili, da budete sigurni da je problem rešen, i da spojite to nazad sa granom master. Ovo možete da uradite pomoću komande git merge.

$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast-forward
 index.html | 2 ++
 1 file changed, 2 insertions(+)

Primetićete izraz Fast-forward (motanje unapred) koji se pojavio na izlazu prilikom spajanja. Pošto se komit C4 na koji pokazuje grana hotfix u koju ste se spojili nalazio direktno ispred od komita C2 na kom ste bili, Git jednostavno pomera pokazivač napred. Da parafraziramo, kada probate da spojite jedan komit sa komitom do kog se može stići prateći istoriju prvog komita, Git pojednostavljuje stvari tako što samo pomeri pokazivač napred jer nema divergencije sa kojom bi se spajao — zato se ovo zove motanje unapred.

Promena se sada nalazi u snimku komita na koji pokazuje master grana.

`master` se premotao unapred do `hotfix`.
Figure 22. master se premotao unapred do hotfix

Pošto je super-važan problem rešen, vreme je da se vratite na ono što ste radili pre nego što ste bili prekinuti. Ipak, prvo ćete obrisati hotfix granu, jer vam više nije potrebna — grana master pokazuje na isto mesto. Možete je obrisati pomoću opcije -d uz komandu git branch:

$ git branch -d hotfix
Deleted branch hotfix (3a0874c).

Sada se vraćate na tiket #53 i nastavljate sa radom

$ git checkout iss53
Switched to branch "iss53"
$ vim index.html
$ git commit -a -m 'finished the new footer [issue 53]'
[iss53 ad82d7a] finished the new footer [issue 53]
1 file changed, 1 insertion(+)
Rad se nastavlja na `iss53`.
Figure 23. Rad se nastavlja na iss53

Ovde treba napomenuti da posao koji ste odradili u grani hotfix nije sadržan u fajlovima na grani iss53. Ako vam je on potreban, možete da spojite granu master u granu iss53 pokretanjem komande git merge master, ili možete da sačekate sa integrisanjem tih promena dok kasnije ne odlučite da povučete granu iss53 nazad u master.

Osnove spajanja

Pretpostavimo da ste odlučili da je rad na problemu #53 gotov i da je kod spreman da se spoji sa master granom. Da biste uradili to, treba da pripojite granu iss53 grani master, kao što ste ranije spojili hotfix. Sve što treba da uradite jeste da čekautujete granu u koju želite da se spojite i da onda pokrenete git merge komandu:

$ git checkout master
Switched to branch 'master'
$ git merge iss53
Merge made by the 'recursive' strategy.
index.html |    1 +
1 file changed, 1 insertion(+)

Ovo izgleda malo drugačije od spajanja hotfix od ranije. U ovom slučaju, istorija je divergirala na nekoj ranijoj tački. Pošto komit na grani na kojoj se nalazite nije direktan predak grane u koju se spajate, Git mora uradi neki posao. U ovom slučaju, Git radi jednostavan trostruki spoj, koristeći dva snimka na koje pokazuju vrhovi grana i njihovog zajedničkog pretka.

Tri snimka koji se koriste u tipičnom spoju.
Figure 24. Tri snimka koji se koriste u tipičnom spoju

Umesto da samo pomeri pokazivač na granu napred, Git pravi novi snimak koji je rezultat ovog trostrukog spoja i automatski pravi novi komit koji pokazuje na njega. Ovo se naziva spojni komit (merge commit), i poseban je jedino u tom smislu što ima više od jednog roditelja.

Spojni komit.
Figure 25. Spojni komit

Vredi napomenuti da je Git taj koji odlučuje koji zajednički predak je najbolje iskoristiti kao bazu za spoj; ovo je drugačije od starijih alata kao što su CVS i Subversion (pre verzije 1.5), gde programer koji radi spoj mora sam da odgonetne koji čvor je najbolja baza. Ovo čini spajanje mnogo jednostavnijim u odnosu na ostale sisteme.

Sada kada je ceo rad spojen, više nema potrebe za iss53 granom. Možete da zatvorite tiket u vašem sistemu za praćenje i da obrišete granu:

$ git branch -d iss53

Osnovni konflikti pri spoju

S vremena na vreme, ovaj proces neće teći ovako glatko. Ako ste promenili isti deo istog fajla u dvema različitim granama koje spajate u jednu, Git neće moći da ih spoji kako valja. Ako je fiks za tiket #53 modifikovao isti deo kao i hotfix, dobićete konflikt pri spoju koji izgleda ovako nekako:

$ git merge iss53
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.

Git nije automatski napravio novi spojni komit. Pauzirao je proces dok vi ne rešite konflikt. Ako želite da vidite koji fajlovi nisu spojeni bilo kada nakon konflikta pri spoju, treba da pokrenete git status:

$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")

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

    both modified:      index.html

no changes added to commit (use "git add" and/or "git commit -a")

Sve što ima konflikt pri spoju a nije rešeno izlistano je pod nespojeno (unmerged). Git dodaje standardne markere za rešavanje konflikta u fajlove koji imaju konflikt, da biste mogli da ih otvorite i ručno rešite konflikte. Fajl sadrži sekciju koja izgleda nekako ovako:

<<<<<<< HEAD:index.html
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
 please contact us at support@github.com
</div>
>>>>>>> iss53:index.html

Ovo znači da je verzija u HEAD (vaša master grana, jer to ste čekautovali kada ste pokrenuli komandu merge) na vrhu tog bloka (sve iznad =======), dok je verzija iz grane iss53 prikazana u donjem delu. Da biste rešili konflikt, morate ili da izaberete jednu stranu ili drugu ili da ručno spojite sadržinu fajla. Na primer, ovaj konflikt se može rešiti tako što ćete ceo gornji blok zameniti ovime:

<div id="footer">
please contact us at email.support@github.com
</div>

Ovo rešenje ima pomalo iz obe sekcije, a linije sa ======= i >>>>>>> su potpuno obrisane. Nakon što rešite svaku od ovakvih sekcija u svakom fajlu sa konfliktom, pokrenite git add na svaki fajl da biste ga označili kao razrešen. Stejdžovanje fajla obeležava konflikt kao razrešen u Gitu.

Ako želite da koristite grafički alat da rešite ovakve probleme, možete da pokrenete komandu git mergetool, koja pokreće odgovarajući vizuelni alat koji vas vodi kroz konflikte:

$ git mergetool

This message is displayed because 'merge.tool' is not configured.
See 'git mergetool --tool-help' or 'git help config' for more details.
'git mergetool' will now attempt to use one of the following tools:
opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge p4merge araxis bc3 codecompare vimdiff emerge
Merging:
index.html

Normal merge conflict for 'index.html':
  {local}: modified file
  {remote}: modified file
Hit return to start merge resolution tool (opendiff):

Ako hoćete da koristite neki drugi alat za spajanje koji nije podrazumevani (Git je izabrao opendiff u ovom slučaju jer je komanda pokrenuta na Meku), videćete sve podržane alate izlistane na vrhu posle "one of the following tools". Samo ukucajte ime alata koji biste radije koristili.

Note

Ako su vam potrebni napredniji alati da biste rešili nezgodnije konflikte pri spoju, pogledaćemo neke od njih u Advanced Merging.

Kada izađete iz alata za rešavanje konflikta pri spoju, Git će vas pitati da li je spoj bio uspešan. Ako kažete skripti da jeste, fajl će biti stejdžovan i označen kao razrešen. Možete da pokrenete git status da biste se uverili da su svi konflikti razrešeni:

$ git status
On branch master
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:

    modified:   index.html

Ako ste zadovoljni time, i ako ste potvrdili da je sve što je imalo konflikte sada na stejdžu, možete da ukucate git commit da finalizirate spojni komit. Podrazumevana komit poruka izgleda nekako ovako:

Merge branch 'iss53'

Conflicts:
    index.html
#
# It looks like you may be committing a merge.
# If this is not correct, please remove the file
#	.git/MERGE_HEAD
# and try again.


# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# All conflicts fixed but you are still merging.
#
# Changes to be committed:
#	modified:   index.html
#

Možete da modifikujete tu poruku sa detaljima o tome kako ste razrešili spoj ako mislite da će to biti korisno drugima koji budu gledali ovaj spoj u budućnosti — zašto ste uradili to što ste uradili, ako nije očigledno.