Git
Chapters ▾ 2nd Edition

7.8 Mga Git na Kasangkapan - Advanced na Pag-merge

Advanced na Pag-merge

Ang pag-merge sa Git ay karaniwang medyo madali. Dahil ang Git ay nagagawa nito ipadali ang pag-merge sa ibang branch ng maraming beses, ito ay nangangahulugan na maaari ka na merong isang matagal nang buhay na branch ngunit maaari mong panatilihin ang pinakabago habang ikaw ay gumagamit, paglutas ng maliit na mga kasalungat ng madalas, sa halip na mabigla ng isang napakalaking salungat sa katapusan ng serye.

Gayunpaman, minsan nakakalito ang mga salungatan na magaganap. Di tulad ng ibang mga bersyon na kontrol na mga sistema, ang Git ay hindi susubok upang maging sobrang matalino tungkol sa pagsama ng pagsasalungatan na resolusyon. Ang pilisopiya ng Git ay dapat maging talino tungkol sa pagtukoy kapag ang isang merge na resolusyon ay hindi malabo, ngunit kung may isang salungat, hindi ito sinubukan na maging matalino tungkol sa awtomatikong palutas nito. Samakatuwid, kung ikaw ay naghintay nang sobrang tagal upang i-merge ang dalawang branch na mabilis na nagbabagu-bago, maaari kang magpatakbo ng ilang mga isyu.

Sa seksyon na ito, kami ay magpapatuloy kung ano ang mga maaaring isyu at anong mga kasangkapan na nagbibigay tulong upang panghawakan ang mga ito na mas madayang mga sitwasyon. At saka tatalakayin din natin ang iba’t iba, na hindi pamantayan na mga uri ng mga merge na maaari mong gawin, pati na rin makita kung paano umatras sa mga merge na iyong nagawa.

Pag-merge sa mga Kasalungat

Habang nasakop natin ang maraming mga batayan sa paglulutas ng merge na mga salungat sa Mga Pangunahing Salungatan sa Pag-Merge, para sa higit na kumplikado na mga salungat, Ang Git ay nagbibigay na ilang mga kasangkapan para tulungan ka na tingnan kung ano ang nangyayari at kung paano mas mahusay na makitungo sa mga salungat.

Una sa lahat, kung sa lahat ay posible, subukan na siguraduhin na ang iyong tinatrabahong direktoryo ay malinis bago magsagawa ng merge na maaaring magkakaroon ng mga salungat. Kung merong kang trabaho na nasa proseso, alinmang i-commit ito sa isang pansamantalang branch o itago ito. Ginagawa ito nito para maka-undo ka ng anumang bagay na sinubukan mo dito. Kung meron kang hindi na-save na mga pagbabago sa iyong tintrabahong direktoryo kapag sinubukan mong i-merge, ilan sa mga tip na ito ay tumutulong sayo na mawala ang trabahong iyon.

Tingnan nating mabuti ang pinakasimpleng halimbawa. Mayroon tayong isang sobrang simple na Ruby file na nagpri-print ng hello world.

#! /usr/bin/env ruby

def hello
  puts 'hello world'
end

hello()

Sa repositoryo natin, tayo ay lilikha ng isang bagong branch na tinatawag na whitespace at magpatuloy upang baguhin ang lahat na nagtatapos sa linyang Unix hanggang sa nagtatapos sa linyang DOS, na mahalagang nagbabago bawat linya sa file, pero basta may whitespace. Then we change the line “hello world” to “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 'converted hello.rb to DOS'
[whitespace 3270f76] converted 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 'hello mundo change'
[whitespace 6d338d2] hello mundo change
 1 file changed, 1 insertion(+), 1 deletion(-)

Ngayon tayo at lilipat pabalik sa ating master na branch at magdagdag ng maraming dokumentasyon para sa function.

$ 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 'document the function'
[master bec6336] document the function
 1 file changed, 1 insertion(+)

Ngayon ay sinusubukan aming na i-merge sa ating whitespace na branch at makakuha tayo ng mga salungat dahil sa whitespace na mga pagbabago.

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

Pag-abort ng Merge

Tayo ngayon ay maroon nang ilang mga opsyon. Una, talakayin natin kung papaano makalabas sa sitwasyon na ito. Kung sasabihin mo na ikaw ay hindi umaasa ng mga salungat at hindi gusto ang medyong pakikitungo sa sitwasyon, maaari kang hindi magpatuloy sa pag-merge ng git merge --abort.

$ git status -sb
## master
UU hello.rb

$ git merge --abort

$ git status -sb
## master

Ang git merge --abort na opsyon na susubukang ibalik muli sa estado bago magpatakbo ng merge. Ang tanging mga kaso na kung saan ay hindi perpektong makagawa kung ito ay hindi naitago, ang mga hindi nai-commit na mga pagbabago sa iyong tinatrabahong direktoryo kapag pinatakbo mo ito, kung hindi man ay dapat itong gumana ng maayos.

Para sa kadahilanan na gusto mo lang magsimulang muli, maaari ka ding magpatakbo ng git reset --hard HEAD, at ang iyong repositoryo ay babalik sa huling na-commit na estado. Tandaan na anumang hindi nai-commit na trabaho ay mawawala, kaya siguraduhin mo ang anumang pagbabago.

Hindi pinansin na Whitespace

Sa ganitong partikular na kaso, ang mga salungat ay may kaugnay sa whitespace. Alam natin ito dahil ang kaso ay simple, ngunit ito ay medyo madali rin na sabihin sa totoong mga kaso kapag tiningnan ang salungat dahil sa bawat linya ay inalis sa isang banda at dinagdag uli sa iba. Bilang default, ang Git ay nakakakita ng lahat na ito na mga linya bilang nabago, kaya ito ay hindi ma-merge ang mga file.

Ang default na diskarte ng pag-merge ay bagaman maaaring tumanggap ng maraming mga argumento, at ilang sa kanila ay tungkol sa maayos na pagbalewala ng mga pagbabago sa whitespace. Kung ikaw ay makakita na merong kang maraming mga isyu sa whitespace sa isang merge, maaari mong itigil ito at ulitin uli, sa ngayon may -Xignore-all-space o -Xignore-space-change. Ang unang opsyon ay nagbabalewala sa whitespace na ganap kung nagkukumpara ng mga linya, ang pangalawa ay nagtuturing sa sunod-sunod sa isa o higit na whitespace na mga karaker bilang katumbas.

$ 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(-)

Dahil sa kasong ito, ang aktwal na pagbabago ng file ay hindi nagkakasalungat, na kapag babalewalain ang whitespace na mga pagbabago, ang lahat ay magme-merge na okey lang.

Ito ay isang nagliligtas ng buhay kung ikaw ay may isang tao sa iyong koponan na gusto na paminsan-minsan na mag-reformat ng lahat ng bagay mula sa mga space hanggang sa mga tab o baliktad.

Manual File Re-merging

Kahit na humahawak ang Git sa whitespace pre-processing ng mabuti, may iba pang mga uri ng mga pagbabago na marahil hindi mahawakan ng awtomatiko, ngunit ay naka scriptable na mga pag-aayos. Bilang isang halimbawa, magpanggap tayo na ang Git ay hindi makahawak ng binago na whitespace at kailangan nating gawin ito sa pamamagitan ng ating mga kamay.

Ano talaga ang kailangan nating gawin ay ang patakbuhin ang file na sinusubukan nating i-merge sa pamamagitan ng isang dos2unix na programa bago sinusubukan ang aktwal file na nai-merge. Kaya paano natin ito gawin?

Una, kailangan natin pumunta sa merge na salungat na estado. Pagkatapos ay gusto nating kunin ang mga kopya sa aking bersyon sa file, ang kanilang bersyon(mula sa branch kami ay nagme-merge) at ang karaniwang bersyon (mula sa kung saang magkabilang panig na na-branch off). Pagkatapos gusto nating ayusin ng maaga sa kanilang panig o sa aming panig at subukang muli ang pag-merge muli para sa solong file lamang.

Pagkuha ng tatlong mga bersyon ng file ay talagang medyo madali. Ang Git ay nag-iipon ng lahat ng mga bersyon na ito sa index sa ilalim ng “stages” na ang bawat isa ay may mga numero na nag-uugnay sa kanila. Ang Stage 1 ay isang pangkaraniwang ninuno, ang stage 2 ay iyong bersyon at stage 3 ay mula sa MERGE_HEAD, ang bersyon na iyong i-merge sa (“theirs”).

Maaari mong kunin ang isang kopya sa bawat bersyon na ito sa sumasalungat na file na may git show na utos at isang espesyal na syntax.

$ git show :1:hello.rb > hello.common.rb
$ git show :2:hello.rb > hello.ours.rb
$ git show :3:hello.rb > hello.theirs.rb

Kung gusto mong makakuha ng isang maliit na mas hard core, maaari mo ding gamitn ang ls-files -u na plumbing na utos command para makuha ang aktwal na SHA-1s sa Git blobs para sa bawat mga file na ito.

$ git ls-files -u
100755 ac51efdc3df4f4fd328d1a02ad05331d8e2c9111 1	hello.rb
100755 36c06c8752c78d2aff89571132f3bf7841a7b5c3 2	hello.rb
100755 e85207e04dfdd5eb0a1e9febbc67fd837c44a1cd 3	hello.rb

Ang :1:hello.rb ay isa lamang shorthand para sa pagtingin na sa blob SHA-1.

Ngayon na mayroon kaming laman sa lahat ng tatlong mga stage sa aming tinatrabahong direktoryo, maaari nating mano-mano na ayusin ang kanilang aayusin na whitespace na isyu at i-merge uli ang file na may hindi gaaanong kilala git merge-file na utos na ginagawa lamang iyon.

$ 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()

Mula rito kami ay mabuting naka-merge sa file. Sa katunayan, talagang gumagana itong mas mahusay kaysa sa ignore-space-change na opsyon dahil ito talaga ay nag-aayos sa pagbabago ng whitespace bago nai-merge sa halip na balewalain ang mga ito. Ang nasa ignore-space-change na merge, talagang natapos na kami na may ilang linya na may nagtatapos na DOS na linya, gumgawa ng mga bagay na magkahalo.

Kung gusto mong makakuha ng isang ideya bago ang pagwawakas ng commit na ito tungkol sa kung ano ang tunay na nabago sa pagitan ng isang panig o ang iba, maaari mong tanungin ang git diff para ikumpara kung ano ang nasa iyong tinatrabahong direktoryo na malapit mo nang i-commit bilang resulta sa pag-merge ng anumang mga stage na ito. Talakayin natin ang lahat na ito.

Para ikumpara ang iyong resulta sa kung ano ang nasa iyong branch bago ang merge, sa ibang salita, para tingnan kung ano ang naipakilala na nai-merge, maaari kang magpatakbo ng 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()

Kaya narito kami para madaling makita kung anong ang nangyari sa ating branch, na kung ano talaga ang ipinakilala sa file sa pag-merge nito, ay nagbabago sa solong linya.

Kung gusto nating tingnan ang resulta sa merge differed mula sa kung ano ang nasa kanilang panig, maaari kang magpatakbo ng git diff --theirs. Dito sa at ang sumusunod na halimbawa, kailangan nating gamitin ang -b upang alisin ang whitespace dahil ikukumpara natin ito kung ano ang nasa Git, hindi ang ating nalinis na hello.theirs.rb na file.

$ 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

Sa wakas, makikita mo kung paano ang file nagbago mula magkabilang panig sa git diff --base.

$ 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()

Mula rito pwede nating gamitin ang git clean na utos upang malinis ang dagdag na mga file na ginawa natin upang gawin ang manu-manong pag-merge ngunit hindi na kinakailangan.

$ git clean -f
Removing hello.common.rb
Removing hello.ours.rb
Removing hello.theirs.rb

Sinusuring ang mga Salungat

Marahil hindi tayo masaya sa resolusyon sa puntong ito para sa ilang kadahilanan, o maaaring mano-manong pag-edit sa isa o kapwa panig na hindi pa rin gumagana ng maayos at kakailanganin ng mas maraming konteksto.

Tayo ay magbago ng halimbawa ng kaunti. Para sa halimbawa na ito, mayroon tayong dalawang matagal na mga branch na ang bawat isa ay may ilang mga commit sa kanila ngunit lumikha ng isang lehitimong nilalaman na sumalungat kapag nai-merge.

$ git log --graph --oneline --decorate --all
* f1270f7 (HEAD, master) update README
* 9af9d3b add a README
* 694971d update phrase to hola world
| * e3eb223 (mundo) add more tests
| * 7cff591 add testing script
| * c3ffff1 changed text to hello mundo
|/
* b7dcc89 initial hello world code

Kapag meron ka nang tatlong natatanging mga commit na nabuhay lamang sa master na branch at tatlong iba pa na nabuhay sa mundo na branch. Kung subukan natin i-merge sa mundo na branch, makakuha tayo ng pagsasalungat.

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

Gusto namin tingnan kung ano ang salungat sa merge. Kung buksan natin ang file, makikita natin ang ibang bagay na tulad nito:

#! /usr/bin/env ruby

def hello
<<<<<<< HEAD
  puts 'hola world'
=======
  puts 'hello mundo'
>>>>>>> mundo
end

hello()

Bawat panig sa merge ay nagdaragdag ng nilalaman sa file na ito, ngunit marami sa mga commit na nagbago sa file sa parehong lugar na sanhi sa pagkasalungat nito.

Talakayin natin ang karamihan sa mga kasangkapan na sa ngayon ay alam mo na iyong magagamit upang malaman kung papaano ang pagkasalungat ay dumating. Marahil ito ay hindi halata kung gaano ka eksakto na dapat mong ayusin ang salungat. Kailangan mo ng maraming konteksto.

Isang matulungin na kasangkapan ay ang git checkout kasama ang ‘--conflict’ na opsyon. Ito ay mag-checkout muli muli sa file at papalitan ang salungat na merge na mga marka. Maaari itong makakatulong kung gusto mong i-reset ang mga marker at subukan na malutas silang muli.

Maaari mong ipasa ang --conflict alinman diff3 o merge (kung saan ay ang default). Kung ipapasa mo ito sa diff3, ang Git ay gagamit ng isang bahagyang bersyon sa salungat na mga marka , di lamang nagbibigay sayo ng “ours” at “theirs” na mga bersyon, ngunit ang “base” din na bersyon na nasa linya upang magbigyan ka ng maraming konteksto.

$ git checkout --conflict=diff3 hello.rb

Sa sandaling patakbuhin natin yan, ang file ay kamukha nito sa halip:

#! /usr/bin/env ruby

def hello
<<<<<<< ours
  puts 'hola world'
||||||| base
  puts 'hello world'
=======
  puts 'hello mundo'
>>>>>>> theirs
end

hello()

Kung gusto mo ang format na ito, maaari kang magtakda nito bilang default para sa hinaharap na merge na mga salungat sa pamamagitan ng pagtatakda ng merge.conflictstyle at pagtatakda sa diff3.

$ git config --global merge.conflictstyle diff3

Ang git checkout na utos ay maaari ding kumuha ng --ours at --theirs na mga opsyon, na kung saan ay maaaring maging isang tunay na mabilis na paraan sa pagpili pa lamang alinman sa isang panig o sa iba na hindi naka merge sa mga bagay sa lahat.

Ito ay maaaring maging partikular na kapaki-pakinabang para sa mga salungat na mga binary file na kung saan maaari ka lamang pumili ng isang panig, o kung saan gusto lamang i-merge ang tiyak na mga file mula sa ibang branch - maaari mo ding gawin ang pag-merge at pagkatapos i-checkout tiyak na mga file mula sa isang panig o sa iba bago mag-commit.

Pag-merge ng Log

Ang kapaki-pakinabang na kasangkapan kapag naglutas ng merge na mga salungat ay ang git log. Ito ay nakakatulong sayo na kunin ang konteksto na sa kung ano ang maaaring maiiambag sa mga salungat. Pagrepaso nang kaunti sa kasaysayan para tandaan kung bakit ang dalawang mga linya sa development na hinahawakan natin sa parehong nakakatulong minsan.

Para makakuha ng buong listahan sa lahat ng natatanging mga commit na nakasali sa alinmang branch na sangkot sa merge na ito, maaari nating gamitin ang “triple dot” na syntax na natutunan natin sa Triple na Dot.

$ git log --oneline --left-right HEAD...MERGE_HEAD
< f1270f7 update README
< 9af9d3b add a README
< 694971d update phrase to hola world
> e3eb223 add more tests
> 7cff591 add testing script
> c3ffff1 changed text to hello mundo

Iyan ay magandang listahan sa anim na kabuuang mga commit na kasangkot, pati na rin kung aling linya sa development para sa bawat commit na nangyayari.

Maaari tayong higit pa na magpasimple upang bigyan ng mas tiyak na konteksto. Kung idagdag natin ang --merge na opsyon sa git log, ito ay nagpapakita lamang ng mga commit alinmang panig sa merge na nakahawak ng isang file na kasalukuyang may na salungat.

$ git log --oneline --left-right --merge
< 694971d update phrase to hola world
> c3ffff1 changed text to hello mundo

Kung nagpapatakbo ka gamit ang -p na opsyon na sa halip, ikaw ay makakuha lamang ng diffs sa file na nagtatapos ng pagkasalungat. Ito ay maaaring talagang kapaki-pakinabang sa mabilis na pagbibigay sayo ng konteksto na kailangan mo ng tulong na maunawaan kung bakit isang bagay ang mga kasalungat at kung papaano mas matalinong malutas ito.

Pinagsamang Diff na Format

Dahil ang mga Git ay nag-stage ng anumang mga resulta ng merge na matagumpay, kapag pinatakbo mo ang git diff habang nasa pagkasalungat na merge na estado, ikaw ay makakuha lamang ng kung ano ang kasalukuyang nasa pagkasalungat. Maaari itong matulungin upang makita kung ano ang hindi pa nalutas.

Kapag pinatakbo mo ang git diff direkta matapos ang isang merge na salungat, ito ay nagbibigay ng impormasyon na sa halip ay natatanging diff output na format.

$ 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()

Ang format ay tinatawag na “Combined Diff” at nagbibigay sa iyo ng dalawang mga column ng datos sa susunod sa bawat linya. Ang unang column ay nagpapakita sa iyo kung ang linya ay iba (nadagdag o inalis) sa pagitan ng “ours” na branch at ang file sa iyong tinatrabahong direktoryo at ang pangalawang column ay gagawa pareho sa pagitan ng “theirs” na branch at ang iyong tinatrabahong direktoryo na kopya.

Kaya sa halimbawang iyon ay makikita mo na ang <<<<<<< and >>>>>>> na mga linya ay nasa tinatrabahong kopya ngunit ay hindi alinman na panig sa merge. Ito ay may katuturan dahil sa ang merge na kasangkapan ay natigil nila na para doon sa susunod na conteksto natin, ngunit inaasahan namin na maalis sila.

Kung lutasin natin ang salungat at ipatakbo ang git diff muli, makikita natin ang parehong bagay, ngunit ito ay medyo mas magagamit.

$ 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()

Ito ay nagpapakita na ang “hola world” ay nasa iyong panig ngunit wala sa tinatrabahong kopya, na ang “hello mundo” ay nasa kanilang panig ngunit wala sa tinatrabahong kopya at sa wakas na “hola mundo” ay wala sa magkabilang panig ngunit ngayon ay nasa tinatrabahong kopya. Ito ay maaaring magagamit sa pagsusuri bago ang pag-commit sa resolusyon.

Maaari kang makakuha din nito mula sa git log para sa anumang merge upang makita kung paano nalutas pagkatapos sa katotohanan. Ang Git ay mag-a-output ng ganitong format kung magpatakbo ka ng git show sa isang merge commit, o kung ikaw ay magdagdag ng --cc na opsyon sa isang git log -p (na sa pamamagitan ng default lamang ay nagpapakita ng mga patch para sa non-merge na mga commit).

$ 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()

Pag-undo sa mga Merge

Ngayon na alam mo na kung papaano maglikha ng isang merge commit, malamang ikaw ay gagawa ng pagkakamali. Isa sa mga dakilang mga bagay tungkol sa tinatrabaho gamit ang Git ay ok lang na makagawa ng mga pagkakamali, dahil ito ay posible (at sa maraming mga kaso na madali) sa pag-ayos nila.

Merge commits are no different. Sabihin mo na ikaw ay nagsimula sa pagtrabaho sa isang topic na branch, aksidenteng na-merge mo ito sa master, at ngayon ang iyong commit na kasaysayan ay ganito na ang hitsura:

Aksidenteng merge commit.
Figure 138. Aksidenteng merge commit

Mayroong dalawang paraan upang lumapit sa ganitong problema, depende sa ano ang iyong ninanais na kinalalabasan.

Ayusin ang mga reperensiya

Kung ang hindi gustong merge commit ay umiiral na lamang sa iyong lokal na repositoryo, ang pinakamadali at pinakamahusay na solusyon ay ang ilipat ang mga branch upang sila ay nagtuturo kung saan gusto mo sila. Sa maraming mga kaso, kung ikaw ay sumusunod sa maling git merge na may git reset --hard HEAD~, ito ay nagre-reset sa mga punto ng branch para magmukha silang ganito:

Kasaysayan pagkatapos ang `git reset --hard HEAD~`.
Figure 139. Kasaysayan pagkatapos ang git reset --hard HEAD~

Tinatalakay namin ang reset na balik sa Ang Reset Demystified, kaya ito ay hindi napakahirap na malaman kung ano nangyayari dito. Narito ang isang mabilis na refresher: reset --hard kadalasang napupunta sa tatlong paraan:

  1. Igalaw ang branch HEAD na mga puntos sa. sa kasong ito, gusto naming ilipat ang master sa kung saan ito ay nating na merge commit (C6).

  2. Gawin ang index na kamukha ng HEAD.

  3. Gawin ang tinatrabahong direktoryo na kamukha ng index.

Ang kakulangan sa diskarteng ito ay ang kanyang sinusulatang muli na kasaysayan, na maaaring maging problema na may isang ibinabahaging direktoryo. Tingnan ang Ang mga Panganib ng Pag-rebase para sa marami na mga pwedeng mangyari; ang maikling bersyon ay kung ang ibang tao ay mayroong mga commit na iyong sinusulatang muli, dapat mong marahil iwasan ang reset. Ang pamamaraan na ito ay hindi rin gagana kung ang ibang mga anumang commit ay nalikha mula nang na-merge; paglilipat sa mga ref ay epektibong mawawala ang mga pagbabago na iyon.

Ibaliktad ang commit

Kung ang paglilipat ng branch na mga punto sa paligid ay hindi gagana para sa iyo, Ang Git ay nagbibigay sayo ng opsyon sa paggawa ng isang bagong commit na nagbabawal sa lahat ng mga pagbabago sa isang umiireal na. Ang Git ay tumatawag sa operasyon na ito ang “revert”, at sa partikular na sitwasyon na ito, gusto mong itong tumawag ng katulad nito:

$ git revert -m 1 HEAD
[master b1d8379] Revert "Merge branch 'topic'"

Ang -m 1 na flag ay nagpapahiwatig kung aling magulang ang “mainline” at dapat manatili. Kapag ikaw ay tumatawag ng merge sa HEAD (git merge topic), ang bagong commit ay mayroong dalawang magulang: ang pinaka una ay HEAD (C6), at ang pangalawa ay ang tip sa branch na pinagsama sa (C4). Sa kasong ito, gusto natin na pawalang-bisa ang lahat na pagbabago na ipinakilala sa pamamagitan ng pag-merge sa magulang #2 (C4), habang pinapanatili ang lahat ng mga nilalaman mula sa magulang #1 (C6).

Ang kasaysayan na may bumabalik na commit ay ganito ang hitsura nito:

Kasaysayan pagkatapos ng `git revert -m 1`.
Figure 140. Kasaysayang pagkatapos ng git revert -m 1

Ang bagong commit ^M ay may eksaktong parehong mga nilalaman bilang C6, kaya nagsisimula mula dito na ito ay parang na-merge na hindi nangyari, maliban na ang ngayon-hindi na merge na mga commit ay nandiyan pa rin sa kasaysayan ni HEAD. Ang Git ay malilito kung ikaw ay sumubok sa pag-merge sa topic sa master muli:

$ git merge topic
Already up-to-date.

Walang anumang sa topic na hindi pa naabot mula sa master. Kung ano ang mas masahol, kung ikaw ay magdagdag sa topic at i-merge muli, Ang Git ay magdadala ng mga pagbabago dahil sa reverted na merge:

Kasaysayan na may isang masamang merge.
Figure 141. Kasaysayan na may isang masamang merge

Ang pinakamahusay na paraan ay ang pag un-revert sa orihinal na merge, mula ngayon gusto mong dalhin ang mga pagbabago na iyong na-revert , pagkatapos lumikha ng isang bagong merge commit:

$ git revert ^M
[master 09f0126] Revert "Revert "Merge branch 'topic'""
$ git merge topic
Kasaysayan pagkatapos ng pagre-re-merge sa isang na revert na merge.
Figure 142. Kasaysayan pagkatapos ng pagre-re-merge sa isang na revert na merge

Sa halimbawa na ito, M at ^M ay kinansela. Ang ^^M ay epektibong nag-merge sa mga pagbabago mula sa C3 at C4, at C8 na mga merge sa pagbabago mula C7, kaya ngayon ang topic ay ganap na na-merge.

Ibang mga Uri ng mga Merge

Sa ngayon natalakay natin ang karaniwang na merge sa dalawang mga branch, karaniwang hinahawakan na may isang tinatawag na “recursive” na diskarte ng pag-merge. May ibang paraan sa pag-merge ng mga branch gayunpaman. Talakayin natin ang ilan sa kanila ng mabilis.

Amin o Kanilang Kagustuhan

Una sa lahat, may isang pang kapaki-pakinabang na bagay na maaari nating gawin sa normal na “recursive” na mode sa pag-merge. Nakikita na natin ang ignore-all-space at ignore-space-change na mga opsyon na kung saan ay dumadaan sa isang -X ngunit maaari din naming sabihin kay Git na paboran ang isang panig o ang iba kapag ito ay nakakita ng isang salungat.

Bilang default, kapag ang Git ay nakakita ng isang salungat sa pagitan ng dalawang mga branch na na-merge, ito ay idinaragdag ang merge na kasalungat na mga marka sa iyong code at markahan ang file bilang salungat at hayaan ka na malutas ito. Kung nais mong mas gusto para sa Git upang pumili lamang ng isang partikular na panig at huwag pansinin ang ibang panig na sa halip ng pinapayagan kang mag-manu-mano na resulbahin ang salungat, maaari mong ipasa ang merge na utos alinman sa isang -Xours o -Xtheirs.

Kung ang Git ay nakakita nito, ito ay hindi nagdagdag ng salungat na mga marka. Anumang kaibahan na pwedeng ma-merge, ito ay ma-merge. Anumang mga pagkakaiba na magkasalungat, ito ay pipili lang ng panig na tinutukoy mo nang buo, kabilang ang binary na mga file.

Kung babalik tayo sa “hello world” na halimbawa na ginagamit natin noon, makikita natin na ang merge sa ating branch ay nagdudulot ng mga pagkasalungat.

$ 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.

Gayunpaman kung tayo ay magpatakbo nito ng -Xours o -Xtheirs ito ay hindi.

$ 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

Sa kasong iyon, sa halip na makakuha ka ng pagkasalungat na mga marka sa file sa “hello mundo” sa isang panig at ang “hola world” sa kabila, ito ay pipili sa “hola world”. Gayunpaman, lahat ng ibang hindi nagsasalungat na mga pagbabago sa branch na iyon ay magtagumpay na nai-merge.

Ang opsyon na ito ay maaari ding ipasa sa git merge-file na utos na nakita namin kamakailan lang sa pamamagitan sa pagpatakbo ng isang bagay na tulad ng git merge-file --ours para sa indibidwal na file na mga merge.

Kung nais mong gawin ang isang bagay na tulad nito pero walang Git at kahit na subukan upang i-merge ang mga pagbabago mula sa ibang panig, mayroong higit pa na draconian na opsyon, kung saan ay ang “ours” merge na diskarte. This is different from the “ours” recursive merge option.

Ito ay karaniwang gumawa ng pekeng merge. Ito ay nagtatala ng isang bagong commit na may parehong mga branch bilang magulang, ngunit hindi ito tumitingin sa branch na iyong pinag-merge. Ito lamang ay simpleng nagtatala bilang resulta sa merge na eksaktong code sa iyong kasalukuyang branch.

$ git merge -s ours mundo
Merge made by the 'ours' strategy.
$ git diff HEAD HEAD~
$

Maaari mong makita na doon na walang pagkakaiba sa pagitan ng branch na kami ay nandoon at ang resulta sa pag-merge.

Madalas itong maging kapaki-pakinabang sa karaniwang dayain ang Git sa pag-iisip na ang branch ay na-merge na kapag gagawa ka ng merge sa susunod. Halimbawa, sabihin mo na branched off sa isang release na branch at gumawa ng ilang trabaho nito na gusto mong i-merge pabalik sa iyong master na branch sa isang punto. Kasabay nito ang ilang bugfix sa master kinakailangan na naka-backported sa iyong release na branch. Maaari mong i-merge ang bugfix na branch sa release na branch at ang merge -s ours`din na parehong branch sa iyong `master na branch (kahit na ang pag-aayos ay naroroon na) kaya kapag sa paglaon mo i-merge ang release na branch muli, walang mga salungatan mula sa bugfix.

Subtree na Pag-merge

Ang ideya ng subtree na merge ay ikaw ay mayroong dalawang proyekto, at isa sa mga proyekto ay nagmamapa sa isang subdirektoryo ng isa pa. Kapag tinukoy mo ang isang subtree merge, ang git ay kadalasang sapat na matalino na malaman na ang isa ay isang subtree sa iba at merge na angkop.

Tayo ay tumingin ng malalim sa isang halimbawa sa pagdagdag ng hiwalay na proyekto sa isang umiiral na proyekto at pagkatapos ang pag-merge sa code sa pangalawa sa isang subdirektoryo ng una.

Una, tayo ay magdagdag ng Rack na aplikasyon sa ating proyekto. Idagdag natin ang Rack na proyekto bilang isang remote na reperensiya sa iyong sariling proyekto at pagkatapos tingnan ito sa sarili mong branch:

$ 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"

Ngayon meron na tayong root sa Rack na proyekti sa ating rack_branch na branch at ating sariling proyekto sa master na branch. Kung ikaw ay magcheck out ng isa at pagkatapos sa iba, maaari mong makita na sila ay may ibang mga root na proyekto:

$ ls
AUTHORS         KNOWN-ISSUES   Rakefile      contrib         lib
COPYING         README         bin           example         test
$ git checkout master
Switched to branch "master"
$ ls
README

Ito ay uri ng isang kakaibang konsepto. Hindi lahat ng mga branch sa iyong repositoryo ay talagang may mga branch sa parehong proyekto. Ito ay hindi karaniwan, dahil ito ay bihirang kapaki-pakinabang, ngunit ito ay medyo madali na magkaroon ng mga branch na naglalaman ng ganap na kakaibang kasaysayan.

Sa kasong ito, gusto nating i-pull ang Rack na proyekto sa ating master na proyekto bilang isang subdirektoryo. Magagawa natin iyan sa Git na may git read-tree. Matutunan mo ang higit na tungkol sa read-tree at ang mga kaibigan nito sa Mga Panloob ng GIT, ngunit para sa ngayon ay malaman ang root tree sa isang branch sa iyong kasalukuyang staging area at tinatrabahuang direktoryo. Kakalipat lang natin pabalik sa iyong master na branch, at nagpull tayo sa rack_branch na branch sa rack na subdirektoryo sa ating master na branch sa ating pangunahing proyekto:

$ git read-tree --prefix=rack/ -u rack_branch

When we commit, it looks like we have all the Rack files under that subdirectory – as though we copied them in from a tarball. What gets interesting is that we can fairly easily merge changes from one of the branches to the other. So, if the Rack project updates, we can pull in upstream changes by switching to that branch and pulling:

$ git checkout rack_branch
$ git pull

Then, we can merge those changes back into our master branch. To pull in the changes and prepopulate the commit message, use the --squash option, as well as the recursive merge strategy’s -Xsubtree option. (The recursive strategy is the default here, but we include it for clarity.)

$ 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

All the changes from the Rack project are merged in and ready to be committed locally. You can also do the opposite – make changes in the rack subdirectory of your master branch and then merge them into your rack_branch branch later to submit them to the maintainers or push them upstream.

This gives us a way to have a workflow somewhat similar to the submodule workflow without using submodules (which we will cover in Mga Submodule). We can keep branches with other related projects in our repository and subtree merge them into our project occasionally. It is nice in some ways, for example all the code is committed to a single place. However, it has other drawbacks in that it’s a bit more complex and easier to make mistakes in reintegrating changes or accidentally pushing a branch into an unrelated repository.

Another slightly weird thing is that to get a diff between what you have in your rack subdirectory and the code in your rack_branch branch – to see if you need to merge them – you can’t use the normal diff command. Instead, you must run git diff-tree with the branch you want to compare to:

$ git diff-tree -p rack_branch

Or, to compare what is in your rack subdirectory with what the master branch on the server was the last time you fetched, you can run

$ git diff-tree -p rack_remote/master