-
1. Začetek
- 1.1 O nadzoru različic
- 1.2 Kratka zgodovina Gita
- 1.3 Kaj je Git?
- 1.4 Ukazna vrstica
- 1.5 Namestitev Gita
- 1.6 Prva nastavitev Gita
- 1.7 Pridobivanje pomoči
- 1.8 Povzetek
-
2. Osnove Git
- 2.1 Pridobivanje repozitorija Git
- 2.2 Snemanje sprememb v repozitorij
- 2.3 Pregled zgodovine potrditev
- 2.4 Razveljavljanje stvari
- 2.5 Delo z daljavami
- 2.6 Označevanje
- 2.7 Aliasi Git
- 2.8 Povzetek
-
3. Veje Git
- 3.1 Veje na kratko
- 3.2 Osnove vej in združevanja
- 3.3 Upravljanje vej
- 3.4 Poteki dela z vejami
- 3.5 Oddaljene veje
- 3.6 Ponovno baziranje
- 3.7 Povzetek
-
4. Git na strežniku
- 4.1 Protokoli
- 4.2 Pridobitev Gita na strežniku
- 4.3 Generiranje vaših javnih ključev SSH
- 4.4 Nastavitev strežnika
- 4.5 Prikriti proces Git
- 4.6 Pametni HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 Možnosti gostovanja pri tretjih ponudnikih
- 4.10 Povzetek
-
5. Porazdeljeni Git
- 5.1 Porazdeljeni poteki dela
- 5.2 Prispevek k projektu
- 5.3 Vzdrževanje projekta
- 5.4 Povzetek
-
6. GitHub
-
7. Orodja Git
- 7.1 Izbira revizije
- 7.2 Interaktivno pripravljanje
- 7.3 Shranjevanje na varno (angl. stashing) in čiščenje
- 7.4 Podpisovanje vašega dela
- 7.5 Iskanje
- 7.6 Prepisovanje zgodovine
- 7.7 Demistifikacija ponastavitve
- 7.8 Napredno združevanje
- 7.9 Rerere
- 7.10 Razhroščevanje z Gitom
- 7.11 Podmoduli
- 7.12 Povezovanje v pakete
- 7.13 Zamenjava
- 7.14 Shramba poverilnic
- 7.15 Povzetek
-
8. Prilagoditev Gita
- 8.1 Konfiguracija Git
- 8.2 Atributi Git
- 8.3 Kljuke Git
- 8.4 Primer pravilnika, ki ga uveljavlja Git
- 8.5 Povzetek
-
9. Git in ostali sistemi
- 9.1 Git kot odjemalec
- 9.2 Migracija na Git
- 9.3 Povzetek
-
10. Notranjost Gita
- 10.1 Napeljava in keramika
- 10.2 Objekti Git
- 10.3 Reference Git
- 10.4 Packfiles (datoteke zmanjšanih podatkov)
- 10.5 Refspec
- 10.6 Protokoli prenosa
- 10.7 Vzdrževanje in obnovitev podatkov
- 10.8 Spremenljivke okolja
- 10.9 Povzetek
-
A1. Dodatek A: Git v drugih okoljih
- A1.1 Grafični vmesniki
- A1.2 Git v Visual Studio
- A1.3 Git v Visual Studio Code
- A1.4 Git v IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine
- A1.5 Git v Sublime Text
- A1.6 Git v Bashu
- A1.7 Git v Zsh
- A1.8 Git v Powershellu
- A1.9 Povzetek
-
A2. Dodatek B: Vdelava Gita v vašo aplikacijo
- A2.1 Git v ukazni vrstici
- A2.2 Libgit2
- A2.3 JGit
- A2.4 go-git
- A2.5 Dulwich
-
A3. Dodatek C: Ukazi Git
- A3.1 Nastavitev in konfiguracija
- A3.2 Pridobivanje in ustvarjanje projektov
- A3.3 Osnove posnetkov
- A3.4 Veje in združevanje
- A3.5 Deljenje in posodabljanje projektov
- A3.6 Pregled in primerjava
- A3.7 Razhroščevanje
- A3.8 Popravljanje
- A3.9 E-pošta
- A3.10 Zunanji sistemi
- A3.11 Administracija
- A3.12 Orodja za sisteme napeljave
7.8 Orodja Git - Napredno združevanje
Napredno združevanje
Združevanje v Gitu je običajno precej enostavno. Ker Git omogoča večkratno združevanje druge veje, imate lahko zelo dolgotrajno vejo, vendar jo lahko vzdržujete tako, da pogosto rešujete majhne konflikte, namesto da vas na koncu preseneti ogromen konflikt.
Vendar včasih se pojavijo zapleteni konflikti. V primerjavi z nekaterimi drugimi sistemi za upravljanje različic, Git ne poskuša biti preveč pameten pri reševanju konfliktov združevanja. Gitova filozofija je, da je pameten pri določanju, kdaj je združevanje nedvoumno, vendar če obstaja konflikt, ga ne poskuša avtomatsko rešiti. Zato se lahko srečate s težavami, če predolgo čakate na združevanje dveh hitro razhajajočih se vej.
V tem razdelku bomo pregledali nekatere od teh težav in orodja, ki jih Git ponuja za pomoč pri reševanju teh bolj zapletenih situacij. Pokrili bomo tudi nekatere različne, nestandardne vrste združevanja ter videli, kako se lahko umaknemo iz opravljenih združevanj.
Konflikti združevanja
Medtem ko smo v razdelku Konflikti osnovnega združevanja predstavili nekaj osnov reševanja konfliktov med združevanjem, Git ponuja nekaj orodij za pomoč pri reševanju bolj zapletenih konfliktov.
Preden opravite združevanje, ki bi lahko povzročilo konflikte, poskusite najprej poskrbeti, da je delovni imenik čist. Če imate delo v teku, ga shranite v začasno vejo, ali pa ga dajte v shrambo na varno (angl. stash). Tako lahko razveljavite karkoli, kar tukaj poskušate. Če imate v delovnem imeniku neshranjene spremembe, ko poskusite združevati, vam lahko nekaj teh nasvetov pomaga pri ohranjanju tega dela.
Pojdimo skozi zelo preprost primer.
Imamo zelo preprosto datoteko Ruby, ki izpiše hello world
.
#! /usr/bin/env ruby
def hello
puts 'hello world'
end
hello()
V svojem repozitoriju ustvarimo novo vejo, imenovano whitespace
in nadaljujemo s spreminjanjem vseh končnic vrstic Unix v končnice vrstic DOS, torej dejansko spremenimo vsako vrstico datoteke, vendar le s praznimi znaki.
Nato spremenimo vrstico »hello world« v »hello mundo«.
$ git checkout -b whitespace
Switched to a new branch 'whitespace'
$ unix2dos hello.rb
unix2dos: converting file hello.rb to DOS format ...
$ git commit -am 'Convert hello.rb to DOS'
[whitespace 3270f76] Convert hello.rb to DOS
1 file changed, 7 insertions(+), 7 deletions(-)
$ vim hello.rb
$ git diff -b
diff --git a/hello.rb b/hello.rb
index ac51efd..e85207e 100755
--- a/hello.rb
+++ b/hello.rb
@@ -1,7 +1,7 @@
#! /usr/bin/env ruby
def hello
- puts 'hello world'
+ puts 'hello mundo'^M
end
hello()
$ git commit -am 'Use Spanish instead of English'
[whitespace 6d338d2] Use Spanish instead of English
1 file changed, 1 insertion(+), 1 deletion(-)
Sedaj preklopimo nazaj na našo vejo master
in dodamo nekaj dokumentacije za funkcijo.
$ git checkout master
Switched to branch 'master'
$ vim hello.rb
$ git diff
diff --git a/hello.rb b/hello.rb
index ac51efd..36c06c8 100755
--- a/hello.rb
+++ b/hello.rb
@@ -1,5 +1,6 @@
#! /usr/bin/env ruby
+# prints out a greeting
def hello
puts 'hello world'
end
$ git commit -am 'Add comment documenting the function'
[master bec6336] Add comment documenting the function
1 file changed, 1 insertion(+)
Sedaj poskusimo združiti v našo vejo whitespace
in dobimo konflikte zaradi sprememb praznih znakov.
$ git merge whitespace
Auto-merging hello.rb
CONFLICT (content): Merge conflict in hello.rb
Automatic merge failed; fix conflicts and then commit the result.
Prekinitev združevanja
Zdaj imamo nekaj možnosti.
Najprej se pogovorimo, kako iz te situacije priti ven.
Če niste pričakovali konfliktov in se z njimi ne želite spopasti, lahko preprosto razveljavite združevanje z git merge --abort
.
$ git status -sb
## master
UU hello.rb
$ git merge --abort
$ git status -sb
## master
Možnost git merge --abort
poskuša vrniti vaše stanje na tisto pred zagonom združevanja.
Edini primeri, ko morda ne bo mogla tega storiti popolnoma, so, če ste imeli ob zagonu neshranjene in nepotrjene spremembe v delovnem imeniku, sicer bi moralo delovati v redu.
Če želite iz nekega razloga preprosto začeti znova, lahko zaženete tudi git reset --hard HEAD
in vaš repozitorij se bo vrnil v zadnje potrjeno stanje.
Ne pozabite, da bodo izgubljene vse nepotrjene spremembe, zato preverite, da ne želite ohraniti nobenih sprememb.
Ignoriranje praznih znakov
V tem konkretnem primeru so konflikti povezani s praznimi znaki. To vemo, ker je primer preprost, a je tudi v resničnih primerih precej enostavno ugotoviti, ko gledamo konflikt, saj je na eni strani odstranjena vsaka vrstica in na drugi strani spet dodana. Privzeto Git vidi vse te vrstice kot spremembe, zato datoteke ne more združiti.
Privzeta strategija združevanja lahko sprejme tudi argumente, nekaj med njimi pa se jih nanaša na ustrezno ignoriranje sprememb praznih znakov.
Če ugotovite, da imate v združevanju veliko težav s praznimi znaki, ga lahko preprosto prekinete in ga ponovno zaženete, tokrat z uporabo -Xignore-all-space
ali -Xignore-space-change
.
Prva možnost povsem ignorira prazne znake pri primerjanju vrstic, druga pa obravnava zaporedja enega ali več praznih znakov kot enakovredna.
$ git merge -Xignore-space-change whitespace
Auto-merging hello.rb
Merge made by the 'recursive' strategy.
hello.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
Ker v tem primeru dejanske spremembe datoteke niso konfliktne, ko enkrat prezremo spremembe praznih znakov, se vse združi brez težav.
To je rešitelj življenj, če imate na svoji ekipi nekoga, ki rad občasno preoblikuje vse iz presledkov v tabulatorje ali obratno.
Ročno ponovno združevanje datotek
Čeprav se Git dobro spopada s predobdelavo praznih znakov, obstajajo drugi tipi sprememb, ki jih morda Git ne more samodejno obdelati, vendar so pa skriptni popravki. Kot primer si predstavljamo, da Git ni mogel obdelati spremembe praznih znakov in jo moramo opraviti ročno.
Tisto, kar resnično potrebujemo, je, da datoteko, ki jo želimo združiti, poženemo skozi program dos2unix
, preden poskusimo dejansko združitev datoteke.
Kako bi to storili?
Najprej se znajdemo v stanju konflikta združevanja. Nato želimo dobiti kopije svoje različice datoteke, njihove različice (iz veje, ki jo združujemo) in skupne različice (od koder sta se obe strani odcepili). Nato želimo popraviti bodisi njihovo stran bodisi svojo stran in ponovno poskusimo združiti samo to eno datoteko.
Dobivanje treh različic datoteke je dejansko precej enostavno.
Git vse te različice shrani v indeksu pod »stopnjami«, kjer ima vsaka številko povezano z njo.
Stopnja 1 je skupni prednik, stopnja 2 je vaša različica in stopnja 3 je iz MERGE_HEAD
, različica, ki jo združujete (»theirs«).
Kopijo vsake od teh različic konfliktne datoteke lahko izvlečete s pomočjo ukaza git show
in posebne sintakse.
$ git show :1:hello.rb > hello.common.rb
$ git show :2:hello.rb > hello.ours.rb
$ git show :3:hello.rb > hello.theirs.rb
Če želite biti malo bolj hard core, lahko uporabite ukaz ls-files -u
, da dobite dejanske vrednosti SHA-1 blobov Git za vsako od teh datotek.
$ git ls-files -u
100755 ac51efdc3df4f4fd328d1a02ad05331d8e2c9111 1 hello.rb
100755 36c06c8752c78d2aff89571132f3bf7841a7b5c3 2 hello.rb
100755 e85207e04dfdd5eb0a1e9febbc67fd837c44a1cd 3 hello.rb
:1:hello.rb
je samo bližnjica za iskanje tega bloba SHA-1.
Sedaj, ko imamo vsebine vseh treh stopenj v svojem delovnem imeniku, lahko njihovo težavo praznih znakov ročno popravimo in poskusimo ponovno združiti datoteko z manj znanim ukazom git merge-file
, ki počne ravno to.
$ dos2unix hello.theirs.rb
dos2unix: converting file hello.theirs.rb to Unix format ...
$ git merge-file -p \
hello.ours.rb hello.common.rb hello.theirs.rb > hello.rb
$ git diff -b
diff --cc hello.rb
index 36c06c8,e85207e..0000000
--- a/hello.rb
+++ b/hello.rb
@@@ -1,8 -1,7 +1,8 @@@
#! /usr/bin/env ruby
+# prints out a greeting
def hello
- puts 'hello world'
+ puts 'hello mundo'
end
hello()
V tem trenutku smo lepo združili datoteko.
Pravzaprav to deluje bolje kot možnost ignore-space-change
, saj dejansko popravi spremembe praznih znakov pred združitvijo, namesto da jih preprosto ignorira.
Pri združitvi z možnostjo ignore-space-change
smo dejansko dobili nekaj vrstic s koncem vrstice DOS, kar je povzročilo zmedo.
Če želite pred dokončanjem te potrditve dobiti idejo o tem, kaj se je dejansko spremenilo med enim ali drugim delom, lahko uporabite ukaz git diff
, da primerjate, kaj je v vašem delovnem direktoriju, ki ga želite potrditi kot rezultat združitve na katerokoli od teh stopenj.
Pojdi skozi vse.
Da primerjate svoj rezultat s tistim, kar ste imeli v svoji veji pred združitvijo, z drugimi besedami, da vidite, kaj je združitev uvedla, lahko zaženete git diff --ours
:
$ git diff --ours
* Unmerged path hello.rb
diff --git a/hello.rb b/hello.rb
index 36c06c8..44d0a25 100755
--- a/hello.rb
+++ b/hello.rb
@@ -2,7 +2,7 @@
# prints out a greeting
def hello
- puts 'hello world'
+ puts 'hello mundo'
end
hello()
Tukaj lahko jasno vidimo, da se je v naši veji spremenila samo ta ena vrstica in to vnesemo v to datoteko z združevanjem.
Če želimo videti, kako se je rezultat združevanja razlikoval od tistega, kar je bilo na njihovi strani, lahko zaženemo git diff --theirs
.
V tem in naslednjem primeru moramo uporabiti -b
, da odstranimo prazne znake, ker primerjamo s tem, kar je v Gitu in ne z našo očiščeno datoteko hello.theirs.rb
.
$ git diff --theirs -b
* Unmerged path hello.rb
diff --git a/hello.rb b/hello.rb
index e85207e..44d0a25 100755
--- a/hello.rb
+++ b/hello.rb
@@ -1,5 +1,6 @@
#! /usr/bin/env ruby
+# prints out a greeting
def hello
puts 'hello mundo'
end
Na koncu lahko z git diff --base
vidite, kako se je datoteka spremenila iz obeh strani.
$ git diff --base -b
* Unmerged path hello.rb
diff --git a/hello.rb b/hello.rb
index ac51efd..44d0a25 100755
--- a/hello.rb
+++ b/hello.rb
@@ -1,7 +1,8 @@
#! /usr/bin/env ruby
+# prints out a greeting
def hello
- puts 'hello world'
+ puts 'hello mundo'
end
hello()
V tem trenutku lahko uporabimo ukaz git clean
, da počistimo dodatne datoteke, ki smo jih ustvarili pri ročnem združevanju in jih ne potrebujemo več.
$ git clean -f
Removing hello.common.rb
Removing hello.ours.rb
Removing hello.theirs.rb
Preverjanje konfliktov
Morda iz nekega razloga nismo zadovoljni z rešitvijo v tem trenutku, ali pa je morda ročno urejanje ene ali obeh strani še vedno slabo delovalo in potrebujemo več konteksta.
Naj malo spremenimo primer. V tem primeru imamo dve dolgotrajni veji, kjer ima vsaka od njiju nekaj potrditev, vendar ob združevanju ustvarita legitimni konflikt vsebine.
$ git log --graph --oneline --decorate --all
* f1270f7 (HEAD, master) Update README
* 9af9d3b Create README
* 694971d Update phrase to 'hola world'
| * e3eb223 (mundo) Add more tests
| * 7cff591 Create initial testing script
| * c3ffff1 Change text to 'hello mundo'
|/
* b7dcc89 Initial hello world code
Sedaj imamo tri unikatne potrditve, ki se nahajajo samo v veji master
ter tri ostale, ki se nahajajo v veji mundo
.
Če poskusimo združiti vejo mundo
, dobimo konflikt.
$ git merge mundo
Auto-merging hello.rb
CONFLICT (content): Merge conflict in hello.rb
Automatic merge failed; fix conflicts and then commit the result.
Videti bi želeli, za kateri konflikt združevanja gre. Če odpremo datoteko, bomo videli nekaj takega:
#! /usr/bin/env ruby
def hello
<<<<<<< HEAD
puts 'hola world'
=======
puts 'hello mundo'
>>>>>>> mundo
end
hello()
Obe strani združevanja sta dodali vsebino v to datoteko, vendar nekaj potrditev je spremenilo datoteko na istem mestu, kar je povzročilo ta konflikt.
Raziščimo nekaj orodij, ki so vam na voljo, da ugotovite, kako je prišlo do tega konflikta. Morda ni očitno, kako točno bi ta konflikt morali rešiti. Potrebujete več konteksta.
Eno izmed uporabnih orodij je git checkout
z možnostjo --conflict
.
To bo ponovno izvleklo datoteko in zamenjalo oznake konfliktov med združevanjem.
To je lahko koristno, če želite ponastaviti oznake in poskusiti znova rešiti konflikte.
Možnost --conflict
lahko podate diff3
ali merge
(kar je privzeto).
Če jo podate diff3
, bo Git uporabil nekoliko drugačno različico oznak konfliktov, ki vam ne bodo dale samo »naših« in »njihovih« različic, ampak tudi »osnovno« različico, ki vam bo dala več konteksta.
$ git checkout --conflict=diff3 hello.rb
Ko enkrat to poženemo, bo datoteka videti takole:
#! /usr/bin/env ruby
def hello
<<<<<<< ours
puts 'hola world'
||||||| base
puts 'hello world'
=======
puts 'hello mundo'
>>>>>>> theirs
end
hello()
Če vam je ta oblika ustrezna, jo lahko nastavite kot privzeto za prihodnje konflikte združevanja z nastavitvijo merge.conflictstyle
pri diff3
.
$ git config --global merge.conflictstyle diff3
Ukaz git checkout
lahko uporabimo tudi z možnostima --ours
in --theirs
, kar je zelo hiter način izbire samo ene strani brez združevanja.
To je lahko posebej uporabno za konflikte binarnih datotek, kjer lahko preprosto izberete eno stran, ali pa za združevanje določenih datotek iz druge veje — izvedete lahko združevanje in nato preprosto izvlečete določene datoteke z ene strani ali druge, preden izvedete potrditev.
Dnevnik združevanja
Drugo uporabno orodje pri reševanju konfliktov združevanja je git log
.
To vam lahko pomaga dobiti kontekst o tem, kaj je lahko prispevalo h konfliktom.
Včasih lahko zelo pomaga pregledati nekaj zgodovine, da se spomnite, zakaj sta se dve vrstici razvoja dotaknili istega dela kode.
Če želimo dobiti popoln seznam vseh edinstvenih potrditev, ki so bile vključene v katerokoli vejo, ki sodeluje pri tem združevanju, lahko uporabimo sintakso »trojne pike«, ki smo se je naučili v Trojna pika.
$ git log --oneline --left-right HEAD...MERGE_HEAD
< f1270f7 Update README
< 9af9d3b Create README
< 694971d Update phrase to 'hola world'
> e3eb223 Add more tests
> 7cff591 Create initial testing script
> c3ffff1 Change text to 'hello mundo'
To je dober seznam šestih vključenih skupnih potrditev, kot tudi na kateri vrsti razvoja je bila vsaka od teh potrditev.
To lahko še bolj poenostavimo, da dobimo natančnejši kontekst.
Če dodamo ukazu git log
možnost --merge
, bo prikazal samo tiste potrditve na vsaki strani združevanja, ki se dotikajo datoteke, ki je trenutno v konfliktu.
$ git log --oneline --left-right --merge
< 694971d Update phrase to 'hola world'
> c3ffff1 Change text to 'hello mundo'
Če to zaženete z možnostjo -p
, dobite samo razlike v datoteki, ki so povzročile konflikt.
To lahko zelo pomaga, saj vam hitro zagotovi kontekst, ki ga potrebujete, da razumete, zakaj je nekaj v konfliktu in kako ga bolj inteligentno rešiti.
Kombinirana oblika razlike
Ker Git osnuje vsako uspešno združitev, ko se izvaja ukaz git diff
v stanju konflikta združevanja, se prikaže samo tisto, kar je trenutno še vedno v konfliktu.
To lahko pomaga pri ogledu tega, kar morate še rešiti.
Ko zaženete git diff
neposredno po konfliktnem združevanju, vam bo dala informacije v dokaj edinstveni obliki izpisa diff.
$ git diff
diff --cc hello.rb
index 0399cd5,59727f0..0000000
--- a/hello.rb
+++ b/hello.rb
@@@ -1,7 -1,7 +1,11 @@@
#! /usr/bin/env ruby
def hello
++<<<<<<< HEAD
+ puts 'hola world'
++=======
+ puts 'hello mundo'
++>>>>>>> mundo
end
hello()
Format imenovan »Combined Diff« vam da dva stolpca podatkov poleg vsake vrstice. Prvi stolpec vam pokaže, ali je ta vrstica drugačna (dodana ali odstranjena) med vejo »ours« in datoteko v vašem delovnem imeniku, drugi stolpec pa naredi enako med vejo »theirs« in kopijo vašega delovnega imenika.
Tako lahko v tem primeru vidite, da sta vrstici <<<<<<<
in >>>>>>>
v delovni kopiji, vendar nista bili na nobeni strani združitve.
To ima smisel, saj jih je orodje za združevanje tam postavilo za naš kontekst, pričakuje pa se, da jih bomo odstranili.
Če rešimo konflikt in znova zaženemo git diff
, bomo videli isto stvar, vendar je to nekoliko bolj uporabno.
$ vim hello.rb
$ git diff
diff --cc hello.rb
index 0399cd5,59727f0..0000000
--- a/hello.rb
+++ b/hello.rb
@@@ -1,7 -1,7 +1,7 @@@
#! /usr/bin/env ruby
def hello
- puts 'hola world'
- puts 'hello mundo'
++ puts 'hola mundo'
end
hello()
To bi nam pokazalo, da je »hola world« obstajal v naši veji vendar ne v delovni kopiji, »hello mundo« je obstajal v njihovi veji vendar ne v delovni kopiji in »hola mundo« ni obstajal v nobeni veji, vendar je sedaj v delovni kopiji. To lahko pomaga pri pregledu pred potrditvijo rešitve.
To lahko dobite tudi iz git log
za vsako združitev, da vidite, kako je bila neka težava v resnici rešena.
Git bo izpisal to obliko, če na potrditvi združitve zaženete git show
, ali pa če dodate možnost --cc
h git log -p
(ki privzeto prikazuje popravke samo za potrditve nezdružitev).
$ git log --cc -p -1
commit 14f41939956d80b9e17bb8721354c33f8d5b5a79
Merge: f1270f7 e3eb223
Author: Scott Chacon <schacon@gmail.com>
Date: Fri Sep 19 18:14:49 2014 +0200
Merge branch 'mundo'
Conflicts:
hello.rb
diff --cc hello.rb
index 0399cd5,59727f0..e1d0799
--- a/hello.rb
+++ b/hello.rb
@@@ -1,7 -1,7 +1,7 @@@
#! /usr/bin/env ruby
def hello
- puts 'hola world'
- puts 'hello mundo'
++ puts 'hola mundo'
end
hello()
Razveljavitev združitev
Ko sedaj znate ustvariti potrditev združitve, jih boste verjetno naredili nekaj po pomoti. Ena izmed dobrih stvari pri delu z Gitom je, da je v redu narediti napake, saj jih je mogoče (in v mnogih primerih enostavno) popraviti.
Potrditve združitev niso nič drugačne.
Recimo, da ste začeli delati na tematski veji, jo po nesreči združili v master
in zdaj je vaša zgodovina potrditev videti takole:
Na voljo sta dva načina za pristop k temu problemu, odvisno od tega, kaj je vaš željeni izid.
Popravek referenc
Če je neželena potrditev združitve prisotna samo v vašem lokalnem repozitoriju, je najlažja in najboljša rešitev premik vej tako, da kažejo, kamor jih želite.
V večini primerov bo sledenje nepravilnemu ukazu git merge
z git reset --hard HEAD~
ponastavilo kazalnike vej, tako da bodo videti takole:
git reset --hard HEAD~
reset
smo pokrili v razdelku Demistifikacija ponastavitve, zato ne bi smelo biti pretežko razumeti, kaj se tu dogaja.
Tukaj je hitra osvežitev: reset --hard
običajno opravi tri korake:
-
Premakne kazalec HEAD veje. V tem primeru želimo premakniti
master
tja, kjer je bila pred potrditvijo združitve (C6
). -
Naredi, da je indeks videti kot HEAD.
-
Naredi, da je delovni imenik videti kot indeks.
Slaba stran tega pristopa je, da se prepisuje zgodovina, kar lahko predstavlja težave z deljenim repozitorijem.
Preverite Nevarnosti ponovnega baziranja za več o tem, kaj se lahko zgodi; na kratko, če imajo druge osebe potrditve, ki jih prepisujete, se je treba izogibati uporabi reset
.
Ta pristop prav tako ne bo deloval, če so bile ustvarjene druge potrditve od časa združitve; premikanje referenc bi dejansko izgubilo te spremembe.
Preklicane potrditve
Če vam premikanje kazalcev vej ne bo delovalo, vam Git omogoča možnost ustvarjanja nove potrditve, ki razveljavi vse spremembe obstoječe. Git to operacijo imenuje »revert« in v tem posebnem scenariju bi to sprožili takole:
$ git revert -m 1 HEAD
[master b1d8379] Revert "Merge branch 'topic'"
Zastavica -m 1
kaže na to, da je nadrejeni »mainline« in bi se moral obdržati.
Ko kličete združitev v HEAD
(git merge topic
), ima nova potrditev dve nadrejeni: prva je HEAD
(C6
), druga pa vrh veje, ki se združuje (C4
).
V tem primeru želimo razveljaviti vse spremembe, ki jih je uvedla združitev druge nadrejene (C4
), hkrati pa ohraniti vsebino nadrejene #1 (C6
).
Zgodovina s preklicano potrditvijo združitve je videti tako:
git revert -m 1
Nova potrditev ^M
ima enake vsebine kot C6
, zato je od tu dalje, kot da se združitev ni nikoli zgodila, razen, da so zdaj nezdružene potrditve še vedno v zgodovini HEAD
.
Git bo zmeden, če boste poskusili znova združiti topic
v master
:
$ git merge topic
Already up-to-date.
V topic
ni ničesar, kar ne bi bilo že dosegljivo iz master
.
Kar je še huje, če dodate delo v topic
in znova združite, bo Git prinesel samo spremembe od razveljavitve združitve:
Najboljši način za rešitev tega problema je razveljavitev prvotne združitve, saj želite zdaj uvoziti spremembe, ki so bile razveljavljene, in nato ustvariti novo potrditev združitve:
$ git revert ^M
[master 09f0126] Revert "Revert "Merge branch 'topic'""
$ git merge topic
V tem primeru sta ^M
in ^M
preklicana.
^^M
se učinkovito združi v spremembe iz C3
in C4
ter C8
se združi v spremembe iz C7
, tako da je sedaj veja topic
polno združena.
Druge vrste združitev
Do sedaj smo pogledali običajno združitev dveh vej, kar se ponavadi izvede s t. i. »rekurzivno« strategijo združevanja. Vendar pa obstajajo še drugi načini združevanja vej skupaj. Poglejmo na hitro nekaj od njih.
Naša ali njihova želja
Najprej imamo na voljo še eno uporabno funkcionalnost normalnega »rekurzivnega« načina združevanja vej.
Videli smo že možnosti ignore-all-space
in ignore-space-change
, ki se podajata s parametrom -X
, lahko pa Gitu tudi povemo, naj v primeru konflikta raje izbere eno ali drugo stran.
Privzeto Git, ko naleti na konflikt med dvema vejama, ki ju poskuša združiti, v kodo doda oznake konflikta ter datoteko označi kot konfliktno, da lahko uporabnik konflikt reši ročno.
Če bi raje, da Git izbere določeno stran in ignorira drugo stran, namesto da bi ročno reševali konflikt, lahko ukazu merge
podate -Xours
ali -Xtheirs
.
Če Git to zazna, ne bo dodal oznak konflikta. Vse razlike, ki so združljive, jih bo združil. Vse razlike, ki se konfliktno prekrivajo, bo preprosto izbral celotno stran, ki ste jo določili, vključno z binarnimi datotekami.
Če se vrnemo na primer »hello world«, ki smo ga uporabljali prej, lahko vidimo, da združitev naše veje povzroči konflikt.
$ git merge mundo
Auto-merging hello.rb
CONFLICT (content): Merge conflict in hello.rb
Resolved 'hello.rb' using previous resolution.
Automatic merge failed; fix conflicts and then commit the result.
Vendar, če ga zaženemo z -Xours
ali -Xtheirs
, se to ne zgodi.
$ git merge -Xours mundo
Auto-merging hello.rb
Merge made by the 'recursive' strategy.
hello.rb | 2 +-
test.sh | 2 ++
2 files changed, 3 insertions(+), 1 deletion(-)
create mode 100644 test.sh
V tem primeru namesto tega, da bi dobili oznake za konflikt v datoteki s »hello mundo« na eni strani in »hola world« na drugi, bo preprosto izbral »hola world«. Vendar pa so vse druge nekonfliktne spremembe na tisti veji uspešno združene.
Ta možnost se lahko poda tudi ukazu git merge-file
, ki smo ga videli prej, tako da za posamezne združitve datotek zaženete nekaj podobnega kot git merge-file --ours
.
Če želite narediti nekaj takega, vendar ne želite, da Git poskuša združiti sprememb iz druge strani, obstaja še bolj stroga možnost, kar je združitvena strategija »ours«. To je drugačno od možnosti rekurzivnega združevanja »ours«.
To bo v bistvu naredilo ponarejeno združevanje. Posnelo bo novo potrditev združitve z obema vejama kot nadrejeno, vendar ne bo niti pogledalo veje, ki jo združujete. Za rezultat združitve bo preprosto posnelo natančno kodo na vaši trenutni veji.
$ git merge -s ours mundo
Merge made by the 'ours' strategy.
$ git diff HEAD HEAD~
$
Vidite, da ni razlike med vejo, na kateri smo bili, in rezultatom združevanja.
To je lahko pogosto uporabno, da preprosto prevarate Git, da misli, da je veja že združena, ko pozneje izvajate združevanje.
Na primer, recimo, da ste razvejali vejo release
in na njej opravili nekaj dela, ki ga boste nekega dne želeli združiti nazaj v svojo vejo master
.
V tem času je iz veje master
potrebno prenesti nazaj popravek napake na vejo release
.
Lahko združite vejo z odpravo napake v vejo release
in enako vejo tudi združite z merge -s ours
v vašo vejo master
(čeprav je popravek že tam), tako da pozneje, ko spet združite vejo release
, ni konfliktov zaradi odprave napake.
Združevanje poddreves (angl. subtree merge)
Ideja združevanja poddreves je, da imate dva projekta in eden izmed projektov je preslikan v poddirektorij drugega. Ko določite združitev poddreves, je Git pogostokrat dovolj pameten, da ugotovi, da je eno poddrevo drugega in ju ustrezno združi.
Šli bomo skozi primer dodajanja ločenega projekta v obstoječi projekt in nato združili kodo drugega v poddirektorij prvega.
Najprej bomo v svoj projekt dodali aplikacijo Rack. Dodali bomo projekt Rack kot oddaljeno referenco v svojem lastnem projektu in ga nato izvlekli v svojo lastno vejo:
$ git remote add rack_remote https://github.com/rack/rack
$ git fetch rack_remote --no-tags
warning: no common commits
remote: Counting objects: 3184, done.
remote: Compressing objects: 100% (1465/1465), done.
remote: Total 3184 (delta 1952), reused 2770 (delta 1675)
Receiving objects: 100% (3184/3184), 677.42 KiB | 4 KiB/s, done.
Resolving deltas: 100% (1952/1952), done.
From https://github.com/rack/rack
* [new branch] build -> rack_remote/build
* [new branch] master -> rack_remote/master
* [new branch] rack-0.4 -> rack_remote/rack-0.4
* [new branch] rack-0.9 -> rack_remote/rack-0.9
$ git checkout -b rack_branch rack_remote/master
Branch rack_branch set up to track remote branch refs/remotes/rack_remote/master.
Switched to a new branch "rack_branch"
Sedaj imamo vrh projekta Rack v naši veji rack_branch
in naš lastni projekt v veji master
.
Če izpišete enega in nato drugega, lahko vidite, da imata različna vrha projektov:
$ ls
AUTHORS KNOWN-ISSUES Rakefile contrib lib
COPYING README bin example test
$ git checkout master
Switched to branch "master"
$ ls
README
To je nekako čudna zasnova. Ni potrebno, da so vse veje v vašem repozitoriju dejansko veje istega projekta. Ni pogosto, saj je redkokdaj koristno, vendar je precej enostavno imeti veje, ki vsebujejo popolnoma različne zgodovine.
V tem primeru želimo povleči projekt Rack v naš projekt master
kot poddirektorij.
To lahko naredimo v Gitu z git read-tree
.
Več o read-tree
in njegovih prijateljih se boste naučili v Notranjost Gita, vendar za sedaj vedite, da prebere vrh drevesa ene veje v vaše trenutno področje priprave in delovni direktorij.
Smo ravno preklopili nazaj na vašo vejo master
in povlečemo vejo rack_branch
v poddirektorij rack
naše veje master
našega glavnega projekta:
$ git read-tree --prefix=rack/ -u rack_branch
Ko naredimo potrditev, je videti, kot da imamo vse datoteke Rack pod tem poddirektorijem — kakor da bi jih kopirali iz stisnjega arhiva tar (angl. tarball). Kar postane zanimivo, je, da lahko precej enostavno združimo spremembe iz ene veje v drugo. Torej, če se projekt Rack posodobi, lahko povlečemo zgornje spremembe s preklopom na tisto vejo in povlekom:
$ git checkout rack_branch
$ git pull
Nato lahko združimo te spremembe nazaj v našo vejo master
.
Da povlečemo spremembe in vnaprej napolnimo sporočilo potrditve, uporabimo možnost --squash
kot tudi rekurzivno združevanje strategije možnosti -Xsubtree
.
Rekurzivna strategija je tu privzeta, vendar jo dodajamo zaradi večje jasnosti.
$ git checkout master
$ git merge --squash -s recursive -Xsubtree=rack rack_branch
Squash commit -- not updating HEAD
Automatic merge went well; stopped before committing as requested
Vse spremembe iz projekta Rack so združene in pripravljene za lokalno potrditev.
Lahko naredite tudi nasprotno — naredite spremembe v poddirektoriju rack
vaše veje master
in jih nato kasneje združite v vašo vejo rack_branch
, da jih pošljete vzdrževalcem ali potisnete navzgor.
To nam da način, da imamo potek dela, ki je nekako podoben tistemu s podmoduli, le brez uporabe podmudolov (kar bomo pokrili v Podmoduli). Obdržimo lahko veje z ostalimi povezanimi projekti v svojem repozitoriju in jih občasno poddrevesno združimo v svoj projekt. Na neki način je dobro, na primer, da je vsa koda potrjena na enem mestu. Vendar ima ostale slabosti v tem, da je malo bolj kompleksno in hitro se naredi napake pri ponovnem integriranju sprememb, ali pa se po nesreči potisne veja v nepovezani repozitorij.
Druga nekoliko čudna stvar je, da za dobiti razliko med tem, kar imate v vašem poddirektoriju rack
in kodi v vaši veji rack_branch
— da vidite, če jih morate združiti — ne morete uporabiti običajnega ukaza diff
.
Namesto tega morate pognati git diff-tree
z vejo, ki jo želite primerjati:
$ git diff-tree -p rack_branch
Da primerjate, kaj je v vašem poddirektoriju rack
s tem, kakšna je bila veja master
na strežniku nazadnje, ko ste prenašali, lahko poženete:
$ git diff-tree -p rack_remote/master