विवरण में 'git मर्ज' कैसे काम करता है?


93

मैं 'git मर्ज' के पीछे एक सटीक एल्गोरिदम (या पास) जानना चाहता हूं। कम से कम इन उप-प्रश्नों के उत्तर सहायक होंगे:

  • जीआईटी एक विशेष गैर-परस्पर विरोधी परिवर्तन के संदर्भ का पता कैसे लगाता है?
  • Git को कैसे पता चलता है कि इन सटीक लाइनों में संघर्ष है?
  • ऑटो-मर्ज किन चीजों से होता है?
  • जब मर्जिंग शाखाओं के लिए कोई सामान्य आधार नहीं होता है तो प्रदर्शन कैसे होता है?
  • जब मर्जिंग शाखाओं के लिए कई सामान्य आधार होते हैं, तो प्रदर्शन कैसे होता है?
  • जब मैं एक साथ कई शाखाएँ मिलाता हूँ तो क्या होता है?
  • मर्ज रणनीतियों के बीच अंतर क्या है?

लेकिन एक पूरे एल्गोरिथ्म का वर्णन बहुत बेहतर होगा।


8
मुझे लगता है कि आप इन उत्तरों के साथ एक पूरी किताब भर सकते हैं ...
डैनियल हिलगर्थ

2
या आप बस कोड को पढ़ सकते हैं, जो "पूरे एल्गोरिथ्म का वर्णन करने में" के रूप में लंबे समय तक ले जाएगा
नेविक रीहेल

3
@DanielHilgarth मुझे यह जानकर खुशी होगी, अगर पहले से ही ऐसी कोई किताब है। संदर्भों का स्वागत है।
abyss.7

5
@NevikRehnel हाँ, मैं कर सकता हूँ। लेकिन यह बहुत आसान हो सकता है, अगर कोई पहले से ही इस कोड के पीछे के सिद्धांत को जानता है।
abyss.7

1. "एक विशेष गैर-परस्पर विरोधी परिवर्तन का संदर्भ" क्या है? अंक 2. और 3. समान हैं, लेकिन नकारात्मक हैं, आइए उन दो प्रश्नों को मिलाएं?
सिरो सेंटिल्ली 郝海东 冠状 iro i 法轮功 '

जवाबों:


65

3-तरफा मर्ज एल्गोरिथ्म के विवरण की तलाश में आप सर्वश्रेष्ठ हो सकते हैं। एक उच्च-स्तरीय विवरण कुछ इस तरह होगा:

  1. एक उपयुक्त मर्ज बेस खोजें B- फ़ाइल का एक संस्करण जो दोनों नए संस्करणों ( Xऔर Y) का पूर्वज है , और आमतौर पर सबसे हाल का ऐसा आधार (हालांकि ऐसे मामले हैं जहां इसे आगे वापस जाना होगा, जो कि एक है gitडिफ़ॉल्ट डिफ़ॉल्ट recursiveमर्ज की विशेषताएं )
  2. की डिफ प्रदर्शन Xके साथ Bऔर Yसाथ B
  3. दो अलग-अलग हिस्सों में पहचाने गए परिवर्तन ब्लॉकों से गुजरें। यदि दोनों पक्ष एक ही स्थान पर समान परिवर्तन का परिचय देते हैं, तो दोनों में से किसी एक को स्वीकार करें; यदि एक परिवर्तन का परिचय देता है और दूसरा उस क्षेत्र को छोड़ता है, तो अंतिम में परिवर्तन का परिचय दें; यदि दोनों एक स्थान में परिवर्तन शुरू करते हैं, लेकिन वे मेल नहीं खाते हैं, तो एक संघर्ष को मैन्युअल रूप से हल करने के लिए चिह्नित करें।

पूर्ण एल्गोरिथ्म बहुत अधिक विस्तार से इससे संबंधित है, और यहां तक कि पृष्ठों के साथ कुछ दस्तावेज़ीकरण ( https://github.com/git/git/blob/master/Documentation/technical/trivial-merge.txt भी है git help XXX, जहां XXX में से एक है merge-base, merge-file, merge, merge-one-fileऔर संभवतः कुछ अन्य)। यदि यह पर्याप्त गहरा नहीं है, तो हमेशा स्रोत कोड होता है ...


11

जब मर्जिंग शाखाओं के लिए कई सामान्य आधार होते हैं, तो प्रदर्शन कैसे होता है?

यह लेख बहुत सहायक था: http://codicesoftware.blogspot.com/2011/09/merge-recursive-strategy.html (यहाँ भाग 2 है )।

पुनरावर्ती उपयोग करता है diff3 पुनरावर्ती रूप से एक आभासी शाखा उत्पन्न करने के लिए जिसे पूर्वज के रूप में उपयोग किया जाएगा।

उदाहरण के लिए:

(A)----(B)----(C)-----(F)
        |      |       |
        |      |   +---+
        |      |   |
        |      +-------+
        |          |   |
        |      +---+   |
        |      |       |
        +-----(D)-----(E)

फिर:

git checkout E
git merge F

2 सर्वश्रेष्ठ सामान्य पूर्वज (सामान्य पूर्वज जो किसी अन्य के पूर्वज नहीं हैं), Cऔर हैं D। Git उन्हें एक नई आभासी शाखा में विलय कर देता है V, और फिर Vआधार के रूप में उपयोग करता है ।

(A)----(B)----(C)--------(F)
        |      |          |
        |      |      +---+
        |      |      |
        |      +----------+
        |      |      |   |
        |      +--(V) |   |
        |          |  |   |
        |      +---+  |   |
        |      |      |   |
        |      +------+   |
        |      |          |
        +-----(D)--------(E)

मुझे लगता है कि Git अभी भी जारी रहेगा यदि Vअगले सबसे अच्छे पूर्वजों का विलय हो गया , तो अगले के साथ विलय हो जाएगा।

लेख में कहा गया है कि अगर वर्चुअल ब्रांच जनरेट करते समय कोई मर्ज संघर्ष होता है, तो बस संघर्ष मार्करों को छोड़ देता है जहां वे हैं और जारी है।

जब मैं एक साथ कई शाखाएँ मिलाता हूँ तो क्या होता है?

जैसा कि @Nevik Rehnel ने समझाया, यह रणनीति पर निर्भर करता है, यह man git-merge MERGE STRATEGIESखंड पर अच्छी तरह से समझाया गया है ।

केवल octopusऔर एक ही बार में कई शाखाओं को मिलाने का समर्थन ours/theirsrecursive उदाहरण के लिए नहीं करता है।

octopus यदि कोई विरोध होगा, तो विलय करने से इंकार कर देगा ours एक तुच्छ विलय है, इसलिए कोई टकराव नहीं हो सकता है।

उन आदेशों से एक नई प्रतिबद्धता उत्पन्न होती है जिसमें 2 से अधिक माता-पिता होंगे।

मैंने एक किया merge -X octopus Git 1.8.5 पर संघर्ष के बिना यह देखने के लिए कि यह कैसे जाता है।

प्रारम्भिक अवस्था:

   +--B
   |
A--+--C
   |
   +--D

क्रिया:

git checkout B
git merge -Xoctopus C D

नई स्थिति:

   +--B--+
   |     |
A--+--C--+--E
   |     |
   +--D--+

जैसी कि उम्मीद थी, E3 माता-पिता हैं।

TODO: एकल फाइल संशोधनों पर ऑक्टोपस कैसे संचालित होता है। पुनरावर्ती दो-दो-तीन तरह से विलय?

जब मर्जिंग शाखाओं के लिए कोई सामान्य आधार नहीं होता है तो प्रदर्शन कैसे होता है?

@ टॉर्क का उल्लेख है कि 2.9 के बाद से मर्ज बिना विफल रहता है --allow-unrelated-histories

मैंने इसे जीआईटी 1.8.5 पर अनुभवजन्य रूप से आज़माया:

git init
printf 'a\nc\n' > a
git add .
git commit -m a

git checkout --orphan b
printf 'a\nb\nc\n' > a
git add .
git commit -m b
git merge master

a शामिल हैं:

a
<<<<<<< ours
b
=======
>>>>>>> theirs
c

फिर:

git checkout --conflict=diff3 -- .

a शामिल हैं:

<<<<<<< ours
a
b
c
||||||| base
=======
a
c
>>>>>>> theirs

व्याख्या:

  • आधार खाली है
  • जब आधार खाली होता है, तो किसी भी फाइल पर किसी भी संशोधन को हल करना संभव नहीं होता है; केवल नई फ़ाइल जोड़ जैसी चीजों को हल किया जा सकता है। उपरोक्त संघर्ष को आधार के साथ 3-वे मर्ज पर हल किया जाएगाa\nc\n एकल लाइन जोड़ के रूप में
  • मुझे लगता है कि बिना बेस फाइल के 3-वे मर्ज को 2-वे मर्ज कहा जाता है, जो सिर्फ एक अंतर है

1
इस प्रश्न के लिए एक नया SO लिंक है, इसलिए मैंने इस उत्तर के माध्यम से स्कैन किया (जो कि काफी अच्छा है) और देखा कि हाल ही में हुए Git परिवर्तन ने अंतिम अनुभाग थोड़ा पुराना कर दिया है। Git संस्करण 2.9 (कमिट e379fdf34fee96cd205be83ff4e71699bdc32b18) के बाद से, Git अब मर्ज करने से इंकार कर देता है जब तक कि आप कोई मर्ज बेस नहीं जोड़ते हैं --allow-unrelated-histories
torek

1
यहाँ एक @Ciro से अनुवर्ती लेख पोस्ट किया गया है: blog.plasticscm.com/2012/01/…
adam0101

जब तक मैंने पिछली बार कोशिश नहीं की थी तब तक व्यवहार में बदलाव नहीं आया है: --allow-unrelated-historiesयदि आप विलय कर रहे हैं तो शाखाओं के बीच कोई आम फ़ाइल पथ नहीं है, तो इसे छोड़ा जा सकता है।
जेरेमी सूची

लघु सुधार: oursमर्ज रणनीति है, लेकिन कोई theirsमर्ज रणनीति नहीं है। recursive+ theirsरणनीति केवल दो शाखाओं को हल कर सकती है। git-scm.com/docs/git-merge#_merge_strategies
nekketsuuu

9

मुझे भी दिलचस्पी है। मैं जवाब नहीं जानता, लेकिन ...

एक जटिल प्रणाली जो काम करती है वह हमेशा एक सरल प्रणाली से विकसित होती है जो काम करती है

मुझे लगता है कि जीआईटी का विलय अत्यधिक परिष्कृत है और इसे समझना बहुत मुश्किल होगा - लेकिन इसके संपर्क का एक तरीका इसके अग्रदूतों से है, और अपनी चिंता के दिल पर ध्यान केंद्रित करना है। अर्थात्, दो फाइलें दी गई हैं, जिनके पास एक सामान्य पूर्वज नहीं है, git मर्ज कैसे कार्य करता है कि उन्हें कैसे मर्ज किया जाए, और कहां विरोध हैं?

आइए कुछ अग्रदूतों को खोजने का प्रयास करें। से git help merge-file:

git merge-file is designed to be a minimal clone of RCS merge; that is,
       it implements all of RCS merge's functionality which is needed by
       git(1).

विकिपीडिया से: http://en.wikipedia.org/wiki/Git_%28software%29 -> http://en.wikipedia.org/wiki/Three-way_merge#Tree-way_merge - http: //en.wikipedia .org / wiki / Diff3 -> http://www.cis.upenn.edu/~bcpierce/papers/diff3-short.pdf

वह आखिरी कड़ी diff3एल्गोरिदम के बारे में विस्तार से वर्णन करते हुए एक कागज का एक पीडीएफ है। यहाँ एक google pdf-दर्शकों का संस्करण है । यह केवल 12 पेज लंबा है, और एल्गोरिथ्म केवल कुछ पृष्ठों का है - लेकिन एक पूर्ण-गणितीय उपचार। यह थोड़ा औपचारिक लग सकता है, लेकिन अगर आप गिट के मर्ज को समझना चाहते हैं, तो आपको पहले सरल संस्करण को समझना होगा। मैंने अभी तक जाँच नहीं की है, लेकिन जैसे नाम के साथ diff3, आपको संभवतः अंतर समझने की भी आवश्यकता होगी (जो सबसे लंबे समय तक सामान्य एल्गोरिथ्म का उपयोग करता है )। हालाँकि, वहाँ एक और अधिक सहज ज्ञान युक्त स्पष्टीकरण हो सकता है diff3, अगर आपके पास एक Google है ...


अब, मैंने सिर्फ एक प्रयोग किया diff3और तुलना की git merge-file। वे एक ही तीन इनपुट फ़ाइलें ले version1 oldversion version2 और मार्क संघर्ष रास्ता एक ही है, साथ <<<<<<< version1, =======, >>>>>>> version2( diff3भी ||||||| oldversion), उनकी साझी विरासत को दर्शाता है।

मैं के लिए एक खाली फ़ाइल का उपयोग किया oldversion , और के लिए लगभग समान फ़ाइलों version1 और version2 सिर्फ एक अतिरिक्त लाइन के साथ करने के लिए जोड़ा version2

परिणाम: git merge-fileसंघर्ष के रूप में एकल परिवर्तित रेखा की पहचान की; लेकिन diff3पूरे दो फाइलों को एक संघर्ष के रूप में माना जाता है। इस प्रकार, diff3 जितना परिष्कृत है, git का मर्ज और भी अधिक परिष्कृत है, यहां तक ​​कि इस सरलतम मामलों के लिए भी।

यहां वास्तविक परिणाम हैं (मैंने पाठ के लिए @ टालबर्ग के उत्तर का उपयोग किया है)। आवश्यक विकल्पों पर ध्यान दें (संबंधित मानपत्र देखें)।

$ git merge-file -p fun1.txt fun0.txt fun2.txt

You might be best off looking for a description of a 3-way merge algorithm. A
high-level description would go something like this:

    Find a suitable merge base B - a version of the file that is an ancestor of
both of the new versions (X and Y), and usually the most recent such base
(although there are cases where it will have to go back further, which is one
of the features of gits default recursive merge) Perform diffs of X with B and
Y with B.  Walk through the change blocks identified in the two diffs. If both
sides introduce the same change in the same spot, accept either one; if one
introduces a change and the other leaves that region alone, introduce the
change in the final; if both introduce changes in a spot, but they don't match,
mark a conflict to be resolved manually.
<<<<<<< fun1.txt
=======
THIS IS A BIT DIFFERENT
>>>>>>> fun2.txt

The full algorithm deals with this in a lot more detail, and even has some
documentation (/usr/share/doc/git-doc/technical/trivial-merge.txt for one,
along with the git help XXX pages, where XXX is one of merge-base, merge-file,
merge, merge-one-file and possibly a few others). If that's not deep enough,
there's always source code...

$ diff3 -m fun1.txt fun0.txt fun2.txt

<<<<<<< fun1.txt
You might be best off looking for a description of a 3-way merge algorithm. A
high-level description would go something like this:

    Find a suitable merge base B - a version of the file that is an ancestor of
both of the new versions (X and Y), and usually the most recent such base
(although there are cases where it will have to go back further, which is one
of the features of gits default recursive merge) Perform diffs of X with B and
Y with B.  Walk through the change blocks identified in the two diffs. If both
sides introduce the same change in the same spot, accept either one; if one
introduces a change and the other leaves that region alone, introduce the
change in the final; if both introduce changes in a spot, but they don't match,
mark a conflict to be resolved manually.

The full algorithm deals with this in a lot more detail, and even has some
documentation (/usr/share/doc/git-doc/technical/trivial-merge.txt for one,
along with the git help XXX pages, where XXX is one of merge-base, merge-file,
merge, merge-one-file and possibly a few others). If that's not deep enough,
there's always source code...
||||||| fun0.txt
=======
You might be best off looking for a description of a 3-way merge algorithm. A
high-level description would go something like this:

    Find a suitable merge base B - a version of the file that is an ancestor of
both of the new versions (X and Y), and usually the most recent such base
(although there are cases where it will have to go back further, which is one
of the features of gits default recursive merge) Perform diffs of X with B and
Y with B.  Walk through the change blocks identified in the two diffs. If both
sides introduce the same change in the same spot, accept either one; if one
introduces a change and the other leaves that region alone, introduce the
change in the final; if both introduce changes in a spot, but they don't match,
mark a conflict to be resolved manually.
THIS IS A BIT DIFFERENT

The full algorithm deals with this in a lot more detail, and even has some
documentation (/usr/share/doc/git-doc/technical/trivial-merge.txt for one,
along with the git help XXX pages, where XXX is one of merge-base, merge-file,
merge, merge-one-file and possibly a few others). If that's not deep enough,
there's always source code...
>>>>>>> fun2.txt

यदि आप वास्तव में इस में रुचि रखते हैं, तो यह एक खरगोश छेद का एक सा है। मेरे लिए, यह नियमित अभिव्यक्तियों के रूप में उतना ही गहरा लगता है, सबसे लंबे समय तक अंतर के सामान्य एल्गोरिथ्म, संदर्भ मुक्त व्याकरण, या संबंधपरक बीजगणित। यदि आप इसकी तह तक जाना चाहते हैं, तो मुझे लगता है कि आप कर सकते हैं, लेकिन यह कुछ निर्धारित अध्ययन करेगा।


2

यहाँ मूल कार्यान्वयन है

http://git.kaarsemaker.net/git/blob/857f26d2f41e16170e48076758d974820af685ff/git-merge-recursive.py

मूल रूप से आप दो कमिट के लिए सामान्य पूर्वजों की एक सूची बनाते हैं और फिर उन्हें तेजी से मर्ज करते हैं, या तो उन्हें तेजी से अग्रेषित करते हैं, या उन वर्चुअल कॉमेट्स का निर्माण करते हैं जो फ़ाइलों पर तीन-तरफ़ा मर्ज के आधार पर उपयोग किए जाते हैं।


0

जीआईटी एक विशेष गैर-परस्पर विरोधी परिवर्तन के संदर्भ का पता कैसे लगाता है?
कैसे पता चलता है कि इन सटीक रेखाओं में कोई विरोध है?

यदि मर्ज के दोनों तरफ एक ही रेखा बदल गई है, तो यह एक संघर्ष है; यदि वे नहीं हैं, तो एक तरफ से परिवर्तन (यदि अस्तित्व में है) स्वीकार किया जाता है।

ऑटो-मर्ज किन चीजों से होता है?

परिवर्तन जो संघर्ष नहीं करते हैं (ऊपर देखें)

जब मर्जिंग शाखाओं के लिए कई सामान्य आधार होते हैं, तो प्रदर्शन कैसे होता है?

Git मर्ज-बेस की परिभाषा से , केवल एक ही (नवीनतम सामान्य पूर्वज) है।

जब मैं एक साथ कई शाखाएँ मिलाता हूँ तो क्या होता है?

यह मर्ज रणनीति पर निर्भर करता है (केवल octopusऔर ours/ / theirsरणनीति दो शाखाओं से अधिक विलय का समर्थन करती है)।

मर्ज रणनीतियों के बीच अंतर क्या है?

इसे git mergeमैनपेज में समझाया गया है ।


2
'समान रेखा ’का क्या अर्थ है? यदि मैं दो अन्य के बीच नई गैर-रिक्त रेखा सम्मिलित करता हूं और विलय करता हूं - कौन सी रेखाएं समान हैं? यदि मैं एक शाखा में कुछ पंक्तियाँ हटाता हूँ, तो कौन-सी शाखा में 'समान' हैं?
abyss.7

1
पाठ में उत्तर देने के लिए यह थोड़ा मुश्किल है। Git दो फ़ाइलों (या किसी फ़ाइल के दो संशोधनों) के बीच अंतर को व्यक्त करने के लिए [diffs] (en.wikipedia.org/wiki/Diff) का उपयोग करता है। यह पता लगा सकता है कि संदर्भ की तुलना करके लाइनों को जोड़ा या हटाया गया है (डिफ़ॉल्ट रूप से, तीन लाइनें)। "समान रेखा" का अर्थ संदर्भ से है, जबकि परिवर्धन और विलोपन को ध्यान में रखते हुए।
नेविक रेहेल

1
आप सुझाव देते हैं कि "एक ही लाइन" बदलने से संघर्ष का संकेत मिलता है। क्या ऑटोमोटिव इंजन वास्तव में आधारित है? या यह हंक-आधारित है? क्या केवल एक ही सामान्य पूर्वज है? यदि हां, तो क्यों git-merge-recursiveमौजूद है?
एडवर्ड थॉमसन

1
@EdwardThomson: हाँ, रिज़ॉल्यूशन लाइन-बेस्ड है (केवल एक लाइन के बचे रहने तक हॉक को छोटे-छोटे हिस्सों तक तोड़ा जा सकता है)। डिफ़ॉल्ट मर्ज रणनीति संदर्भ के रूप में नवीनतम सामान्य पूर्वज का उपयोग करती है, लेकिन कुछ अन्य हैं यदि आप कुछ और उपयोग करना चाहते हैं। और मुझे नहीं पता कि क्या git-merge-recursiveहोना चाहिए (कोई मैन पेज नहीं है और Google पैदावार कुछ भी नहीं है)। इस बारे में अधिक जानकारी पर पाया जा सकता git mergeहै और git merge-baseआदमी पृष्ठों की है।
नेविक रेहेल

1
git-mergeआदमी पेज और git-merge-baseआदमी पृष्ठों है कि आप का कहना है कई आम पूर्वजों और पुनरावर्ती मर्ज चर्चा की। मुझे लगता है कि इस तरह की चर्चा के बिना आपका जवाब अधूरा है।
एडवर्ड थॉमसन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.