Git workflow و rebase vs merge questions




version-control git-merge (7)

على أي حال ، كنت أتبع سير العمل في فرع حديث ، وعندما حاولت دمجها مرة أخرى لإتقان ، ذهب كل شيء إلى الجحيم. كان هناك أطنان من الصراعات مع الأشياء التي كان ينبغي أن لا يهم. لم تكن النزاعات منطقية بالنسبة لي. استغرق مني الأمر يومًا واحدًا لفرز كل شيء ، وتوجت في نهاية المطاف بدفعة إجبارية إلى المعلم البعيد ، نظرًا لأن سيدتي المحلية قد حلّت جميع النزاعات ، لكن النائية لم تكن سعيدة بعد.

في أي من أعمال شريكك أو سير العمل المقترحة ، يجب أن تكون قد صادفت صراعات لم تكن منطقية. حتى لو كان لديك ، إذا كنت تتابع سير العمل المقترحة ثم بعد القرار لا ينبغي أن يكون مطلوبا دفع "القسري". تشير إلى أنك لم تقم فعليًا بدمج الفرع الذي كنت تدفعه ، ولكن اضطررت إلى دفع فرع لم يكن من نواة الطرف البعيد.

أعتقد أنك بحاجة إلى النظر بعناية في ما حدث. هل يمكن لأي شخص آخر (عمدا أم لا) إعادة لف الفرع الرئيسي البعيد بين إنشاء الفرع المحلي والنقطة التي حاولت فيها دمجها مرة أخرى في الفرع المحلي؟

مقارنةً بالعديد من أنظمة التحكم الأخرى في الإصدارات ، وجدت أن استخدام Git ينطوي على قدر أقل من محاربة الأداة ويسمح لك بالوصول إلى المشاكل الأساسية لتيارات المصدر الخاصة بك. لا يؤدي Git أداءً سحريًا ، لذلك تتسبب التغييرات المتعارضة في حدوث تعارضات ، ولكن يجب أن تسهِّل عملية الكتابة من خلال تتبعها لنسب الالتزام.

أستخدم Git الآن لبضعة أشهر في مشروع مع مطور واحد آخر. لدي عدة سنوات من الخبرة مع SVN ، لذا أعتقد أنني أحمل الكثير من الأمتعة للعلاقة.

لقد سمعت أن Git ممتازة للتفرع ودمج ، وحتى الآن ، أنا لا أرى ذلك. من المؤكد أن التفرع أمر بسيط ، ولكن عندما أحاول الدمج ، كل شيء يذهب إلى الجحيم. الآن ، اعتدت على ذلك من SVN ، ولكن يبدو لي أنني قمت فقط بتداول نظام إصدار تجزيئي واحد لآخر.

أخبرني شريكي أن مشاكلي تنبع من رغبتي في الاندماج ، وأنني يجب أن أستخدم ريباز بدلاً من الدمج في العديد من المواقف. على سبيل المثال ، هنا سير العمل الذي وضعه:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature
git checkout master
git merge my_new_feature

أساسا ، إنشاء فرع ميزة ، ALSAYS rebase من الرئيسي إلى الفرع ، والدمج من الفرع مرة أخرى إلى الرئيسي. من المهم ملاحظة أن الفرع يبقى دائمًا محليًا.

هنا سير العمل الذي بدأت به

clone remote repository
create my_new_feature branch on remote repository
git checkout -b --track my_new_feature origin/my_new_feature
..work, commit, push to origin/my_new_feature
git merge master (to get some changes that my partner added)
..work, commit, push to origin/my_new_feature
git merge master
..finish my_new_feature, push to origin/my_new_feature
git checkout master
git merge my_new_feature
delete remote branch
delete local branch

هناك نوعان من الاختلافات الأساسية (على ما أظن): يمكنني استخدام الدمج دائمًا بدلاً من إعادة الرسم ، وأقوم بدفع فرع الميزة الخاص بي (والفرع الخاص بالخصائص الذي أقوم به) إلى المستودع البعيد.

السبب المنطقي للفرع البعيد هو أنني أريد أن يدعم عملي أثناء عملي. يتم الاحتفاظ بنسخة احتياطية من مستودعنا تلقائيًا ويمكن استعادته إذا حدث خطأ ما. جهاز الكمبيوتر المحمول الخاص بي ليس ، أو ليس تماما. لذلك ، أكره أن يكون رمز على جهاز الكمبيوتر المحمول الخاص بي الذي لا ينعكس في مكان آخر.

السبب المنطقي للدمج بدلاً من rebase هو أن الدمج يبدو قياسيًا ويبدو أن rebase ميزة متقدمة. شعوري الغريزي هو أن ما أحاول القيام به ليس إعدادًا متقدمًا ، لذا يجب أن يكون التمديد غير ضروري. لقد أطلعت حتى على كتاب البرمجة الواقعية الجديد في Git ، وهي تغطي الدمج على نطاق واسع ولا تكاد تذكر ذكر rebase.

على أي حال ، كنت أتبع سير العمل في فرع حديث ، وعندما حاولت دمجها مرة أخرى لإتقان ، ذهب كل شيء إلى الجحيم. كان هناك أطنان من الصراعات مع الأشياء التي كان ينبغي أن لا يهم. لم تكن النزاعات منطقية بالنسبة لي. استغرق مني الأمر يومًا واحدًا لفرز كل شيء ، وتوجت في نهاية المطاف بدفعة إجبارية إلى المعلم البعيد ، نظرًا لأن سيدتي المحلية قد حلّت جميع النزاعات ، لكن النائية لم تكن سعيدة بعد.

ما هو سير العمل "الصحيح" لشيء مثل هذا؟ من المفترض أن تقوم شركة Git بالتفرع ودمج سهلة فائقة ، وأنا لا أرى ذلك.

تحديث 2011-04-15

يبدو أن هذا سؤالًا شائعًا جدًا ، لذا فكرت أن أقوم بالتحديث مع خبرتي لمدة عامين منذ أن سألت أولاً.

اتضح أن سير العمل الأصلي صحيح ، على الأقل في حالتنا. بمعنى آخر ، هذا ما نقوم به ويعمل:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git checkout master
git merge my_new_feature

في الواقع ، إن سير العمل لدينا مختلف قليلاً ، لأننا نميل إلى القيام بدمج الاسكواش بدلاً من الدمج الخام. ( ملاحظة: هذا أمر مثير للجدل ، انظر أدناه ) . وهذا يسمح لنا بتحويل فرع الميزات بالكامل إلى التزام واحد على الصفحة الرئيسية. ثم نقوم بحذف فرع الميزات الخاص بنا. وهذا يسمح لنا بتنظيم التزامنا منطقيًا على المعلم ، حتى لو كانت فوضويًا قليلاً على فروعنا. إذن ، هذا ما نفعله:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git checkout master
git merge --squash my_new_feature
git commit -m "added my_new_feature"
git branch -D my_new_feature

الجدل حول دمج الاسكواش - كما أشار العديد من المعلقين ، سيؤدي دمج الاسكواش إلى التخلص من كل التاريخ على فرع ميزة الخاص بك. كما يوحي الاسم ، فإنه يسحق كل الإلتزامات إلى الأسفل في واحد. بالنسبة للميزات الصغيرة ، يكون هذا الأمر معقولاً لأنه يتكثف في حزمة واحدة. بالنسبة للميزات الأكبر ، قد لا تكون فكرة رائعة ، خاصة إذا كانت إلتزاماتك الفردية ذرية بالفعل. يتعلق الأمر في الواقع إلى تفضيل شخصي.

Github و Bitbucket (آخرون؟) طلبات السحب - في حال كنت تتساءل عن كيفية ارتباط الدمج / rebase بطلبات السحب ، فإنني أوصي باتباع جميع الخطوات المذكورة أعلاه حتى تصبح مستعدًا للرجوع إلى الصفحة الرئيسية. بدلاً من الدمج يدويًا مع git ، فإنك تقبل PR فقط. على وجه التحديد ، تعمل على النحو التالي:

clone the remote repository
git checkout -b my_new_feature
..work and commit some stuff
git rebase master
..work and commit some stuff
git rebase master
..finish the feature, commit
git rebase master
git push # May need to force push
...submit PR, wait for a review, make any changes requested for the PR
git rebase master
git push # Will probably need to force push (-f), due to previous rebases from master
...accept the PR, most likely also deleting the feature branch in the process
git checkout master
git branch -d my_new_feature
git remote prune origin

جئت لأحب Git ولا أريد أن أعود إلى SVN. إذا كنت تكافح ، فقم بالتعامل معها وفي النهاية سترى الضوء في نهاية النفق.


TL، DR

لا يحميك سير عمل git rebase من أشخاص سيئين في حل التعارض أو الأشخاص الذين اعتادوا على سير عمل SVN ، كما هو مقترح في تجنب Git Disasters: A Gory Story . فهو يجعل حل النزاع أكثر مملة بالنسبة لهم ويجعل من الصعب التعافي من حل النزاعات السيئة. بدلاً من ذلك ، استخدم diff3 بحيث لا يكون صعبًا في المقام الأول.

Rebase workflow ليس أفضل لحل النزاعات!

أنا مؤيد جدا rebase لتنظيف التاريخ. ولكن إذا تعرضت لصراع ما ، فأجهض على الفور الخصم وأقوم بعملية دمج بدلاً من ذلك! إنه يقتلني حقاً أن الناس يوصون بتدفق سير العمل كبديل أفضل لسير عمل الدمج لحل النزاع (وهو بالضبط ما كان عليه هذا السؤال).

إذا ذهب "كل إلى جهنم" أثناء الدمج ، فإنه سيذهب "إلى الجحيم" أثناء إعادة التمديد ، ويحتمل أن يكون هناك الكثير من الجحيم أيضًا! اليك السبب:

السبب الأول: حل النزاعات مرة واحدة ، بدلاً من مرة واحدة لكل التزام

عندما تقوم بالتمرير بدلاً من الدمج ، سيكون عليك تنفيذ حل التعارض حتى أكبر عدد من المرات التي تلتزم فيها بالتماسك ، لنفس التعارض!

سيناريو حقيقي

أنا فرع من سيد إلى refactor طريقة معقدة في فرع. يتألف عملي من إعادة التعمير لدي من 15 إجمالاً حيث أعمل على إعادة ملئها والحصول على مراجعات الكود. جزء من إعادة بناء بلدي ينطوي على تحديد علامات التبويب والمساحات المختلطة التي كانت موجودة في الماجستير من قبل. هذا ضروري ، ولكن لسوء الحظ فإنه سوف يتعارض مع أي تغيير يتم بعد ذلك على هذه الطريقة الرئيسية. بالتأكيد ، بينما أعمل على هذه الطريقة ، يقوم شخص ما بتغيير بسيط وشرعي لنفس الطريقة في الفرع الرئيسي الذي يجب دمجه مع التغييرات.

عندما يحين وقت دمج الفرع مرة أخرى مع الرئيسية ، لدي خياران:

دمج git: أحصل على صراع. أرى التغيير الذي قاموا به لإتقان ودمجه مع (المنتج النهائي) لفرعي. فعله.

git rebase: أحصل على تعارض مع أول التزام. أنا حل الصراع ومتابعة rebase. أحصل على تعارض مع ملتزمتي الثانية . أنا حل الصراع ومتابعة rebase. أحصل على تعارض مع تعهدي الثالث . أنا حل الصراع ومتابعة rebase. أحصل على تعارض مع ملتزمتي الرابعة . أنا حل الصراع ومتابعة rebase. أحصل على تعارض مع ملتزمتي الخامسة . أنا حل الصراع ومتابعة rebase. أحصل على تعارض مع تعهداتي السادسة . أنا حل الصراع ومتابعة rebase. أحصل على صراع مع ملتزمتي السابعة . أنا حل الصراع ومتابعة rebase. أحصل على صراع مع ملتزمتي الثامنة . أنا حل الصراع ومتابعة rebase. أحصل على صراع مع التواطؤ التاسع . أنا حل الصراع ومتابعة rebase. أحصل على صراع مع تعهدتي العاشرة . أنا حل الصراع ومتابعة rebase. أحصل على تعارض مع الملتزم الحادي عشر . أنا حل الصراع ومتابعة rebase. أحصل على تعارض مع التزامي الثاني عشر . أنا حل الصراع ومتابعة rebase. أحصل على تعارض مع تعهدي الثالث عشر . أنا حل الصراع ومتابعة rebase. أحصل على تعارض مع التزامي الرابع عشر . أنا حل الصراع ومتابعة rebase. أحصل على تعارض مع التزامي الخامس عشر . أنا حل الصراع ومتابعة rebase.

يجب أن تمزح معي إذا كان هذا هو سير العمل المفضل لديك. كل ما يتطلبه الأمر هو تحديد مساحة بيضاء تتعارض مع تغيير واحد يتم إجراؤه على الصفحة الرئيسية ، وستتعارض كل التزام وتتعين حلها. وهذا سيناريو بسيط مع وجود صراع أبيض فقط. لا سمح الله لديك صراع حقيقي يشمل تغييرات رمز رئيسية عبر الملفات ويجب أن حل ذلك عدة مرات.

مع كل حل الصراع الإضافي الذي تحتاجه ، فإنه يزيد من احتمال ارتكاب خطأ ما . لكن الأخطاء على ما يرام في git منذ يمكنك التراجع ، أليس كذلك؟ ما عدا بالطبع ...

السبب # 2: مع rebase ، لا يوجد أي تراجع!

أعتقد أننا يمكن أن نتفق جميعا على أن حل الصراع قد يكون صعبا ، وأيضا أن بعض الناس سيئون للغاية في ذلك. يمكن أن يكون عرضة للأخطاء ، ولهذا السبب من الرائع أن تجعل من السهل التراجع!

عندما تقوم بدمج فرع ، تقوم git بإنشاء عملية دمج دمج يمكن استبعادها أو تعديلها إذا كان حل التعارض غير سليم. حتى إذا كنت قد قمت بالفعل بدفع الدمج السيئ إلى الريبو العام / الموثوق ، فيمكنك استخدام git revert للتراجع عن التغييرات التي أدخلها الدمج وإعادة دمج الدمج بشكل صحيح في عملية دمج جديدة.

عندما تقوم بتمزيق فرع ، في الحدث المحتمل أن يتم حل التعارض بشكل خاطئ ، فإنك تكون ثملًا. يحتوي كل التزام الآن على الدمج السيئ ، ولا يمكنك إعادة التصحيح * فقط. في أفضل الأحوال ، يجب عليك الرجوع وتعديل كل من الإلتزامات المتأثرة. غير مسلي.

بعد التمديد ، من المستحيل تحديد ما كان في الأصل جزءًا من الالتزام وما تم تقديمه كنتيجة لحل النزاعات السيئة.

* يمكن أن يكون من الممكن التراجع عن rebase إذا كان بإمكانك حفر الحواف القديمة من سجلات git الداخلية ، أو إذا قمت بإنشاء فرع ثالث يشير إلى آخر ارتكاب قبل rebasing.

خذ الجحيم من حل النزاعات: استخدم diff3

خذ هذا التعارض على سبيل المثال:

<<<<<<< HEAD
TextMessage.send(:include_timestamp => true)
=======
EmailMessage.send(:include_timestamp => false)
>>>>>>> feature-branch

بالنظر إلى الصراع ، من المستحيل معرفة ما الذي تغير كل فرع أو ما كانت نيته. هذا هو السبب الأكبر في رأيي لماذا حل الصراع مربكًا وصعبًا.

diff3 إلى الإنقاذ!

git config --global merge.conflictstyle diff3

عند استخدام الدالة diff3 ، سيكون لكل تعارض جديد قسم ثالث ، السلف المشترك المدمج.

<<<<<<< HEAD
TextMessage.send(:include_timestamp => true)
||||||| merged common ancestor
EmailMessage.send(:include_timestamp => true)
=======
EmailMessage.send(:include_timestamp => false)
>>>>>>> feature-branch

أولا فحص السلف المشترك المدمج. ثم قارن كل جانب لتحديد نية كل فرع. يمكنك أن ترى أن HEAD غيرت EmailMessage إلى TextMessage. الغرض منه هو تغيير الفئة المستخدمة TextMessage ، تمرير نفس المعلمات. يمكنك أيضًا رؤية أن هدف العنصر الفرعي هو تمرير false بدلاً من true للحصول على خيار: include_timestamp. لدمج هذه التغييرات ، اجمع بين الغرض من كلا:

TextMessage.send(:include_timestamp => false)

بشكل عام:

  1. قارن بين سلف مشترك مع كل فرع ، وحدد أي فرع لديه أبسط تغيير
  2. قم بتطبيق هذا التغيير البسيط على نسخة الفرع الأخرى من الكود ، بحيث يحتوي على التغيير الأبسط والأكثر تعقيدًا
  3. قم بإزالة كافة مقاطع رمز التعارض غير تلك التي دمجتها معًا في التغييرات

البديل: حل بالتطبيق اليدوي لتغييرات الفرع

أخيراً ، هناك بعض الخلافات الرهيبة في الفهم حتى مع diff3. يحدث هذا بشكل خاص عندما يجد الفرق خطوطًا مشتركة لا تكون شائعة من الناحية اللغوية (على سبيل المثال ، حدث لكلا الفرعين خط فارغ في نفس المكان!). على سبيل المثال ، يقوم فرع واحد بتغيير المسافة البادئة لجسم فئة أو يقوم بإعادة ترتيب أساليب مشابهة. في هذه الحالات ، يمكن أن تكون استراتيجية أفضل الدقة هي فحص التغيير من أي جانب من الدمج وتطبيق الاختلاف يدوياً على الملف الآخر.

دعونا ننظر في كيفية حل نزاع في سيناريو حيث يدمج origin/feature1 حيث تتعارض lib/message.rb .

  1. قرر ما إذا كان فرعنا --ours حالياً ( HEAD ، أو - أو) أو الفرع الذي نقوم --theirs ( origin/feature1 --theirs ، أو --theirs ) هو تغيير أبسط لتطبيقه. باستخدام الاختلاف مع النقطة الثلاثية ( git diff a...b ) يُظهر التغييرات التي حدثت على b منذ التباعد الأخير من a ، أو بعبارة أخرى ، قارن السلف المشترك a و b ب.

    git diff HEAD...origin/feature1 -- lib/message.rb # show the change in feature1
    git diff origin/feature1...HEAD -- lib/message.rb # show the change in our branch
    
  2. تحقق من الإصدار الأكثر تعقيدًا من الملف. سيؤدي هذا إلى إزالة جميع علامات التعارض واستخدام الجانب الذي تختاره.

    git checkout --ours -- lib/message.rb   # if our branch's change is more complicated
    git checkout --theirs -- lib/message.rb # if origin/feature1's change is more complicated
    
  3. مع تغيير التغيير المعقد ، اسحب فرق التغيير الأبسط (راجع الخطوة 1). قم بتطبيق كل تغيير من هذا الاختلاف على الملف المتعارض.


"حتى إذا كنت مطورًا واحدًا يحتوي على عدد قليل من الفروع ، فيجدر استخدامه في العادة استخدام rebase ودمجه بشكل صحيح. سيبدو نمط العمل الأساسي كما يلي:

  • إنشاء فرع جديد B من الفرع الحالي A

  • إضافة / ارتكاب التغييرات على الفرع ب

  • Rebase التحديثات من الفرع A

  • دمج التغييرات من الفرع B على الفرع A "

https://www.atlassian.com/git/tutorials/merging-vs-rebasing/


في حالتك ، أعتقد أن شريكك صحيح. ما هو لطيف في إعادة التوازن هو أن التغييرات التي تطرأ على الخارج تبدو وكأنها حدثت جميعها في تسلسل نظيف من تلقاء نفسه. هذا يعنى

  • التغييرات الخاصة بك من السهل جدا للمراجعة
  • يمكنك الاستمرار في عمل إلتزامات صغيرة وصغيرة ، ومع ذلك يمكنك إنشاء مجموعات من هذه الالتزامات العامة (عن طريق الدمج إلى الأساسي) في وقت واحد
  • عند إلقاء نظرة على الفرع الرئيسي العام ، سترى سلسلة مختلفة من الإلتزامات لميزات مختلفة من قبل مطورين مختلفين ولكن لن يتم دمجها جميعًا

لا يزال بإمكانك الاستمرار في دفع فرع التطوير الخاص إلى المستودع عن بعد من أجل النسخ الاحتياطي لكن البعض الآخر يجب ألا يعامل ذلك على أنه فرع "عام" منذ أن تقوم بإعادة الشراء. راجع للشغل ، أمر سهل للقيام بذلك هو git push --mirror origin .

المقال يقوم برنامج التغليف باستخدام Git بعمل جيد إلى حد ما لشرح المقايضات في الدمج مقابل إعادة الشراء. إنه سياق مختلف بعض الشيء ، لكن المبادئ الأساسية هي نفسها - فهي تأتي في الأساس إلى ما إذا كانت فروعك عامة أو خاصة وكيف تخطط لدمجها في الخط الرئيسي.


لدي سؤال واحد بعد قراءة تفسيرك: هل يمكن أن يكون ذلك لم تفعل أبدا

git checkout master
git pull origin
git checkout my_new_feature

قبل إجراء "git rebase / merge master" في فرع الميزة؟

لأن فرعك الرئيسي لن يتم تحديثه تلقائيًا من مستودع صديقك. عليك القيام بذلك مع git pull origin . أي ربما كنت دائما متمرد من فرع رئيسي محلي لم يتغير أبدا؟ ثم يأتي وقت الدفع ، فأنت تضغط في مستودع يرتكب (محلي) يرتكبك ولم يره ، وبالتالي يفشل الدفع.


مع Git لا يوجد سير عمل "صحيح". استخدام كل ما يطفو قاربك. ومع ذلك ، إذا كنت تحصل دائمًا على صراعات عند دمج الفروع ، فربما يجب عليك تنسيق جهودك بشكل أفضل مع مطور (مطوّري البرامج) الآخرين لديك؟ يبدو وكأنه اثنين منكم الاستمرار في تحرير نفس الملفات. أيضا ، احترس من الكلمات البيضاء والفساد (مثل "$ Id $" وغيرها).


لا تستخدم الأصل git push --mirror تحت معظم أي CIRCUMSTANCE.

لا تسأل إذا كنت متأكدًا من رغبتك في القيام بذلك ، ومن الأفضل أن تكون متأكدًا ، لأنه سيؤدي إلى محو جميع فروعك البعيدة غير الموجودة في صندوقك المحلي.

http://twitter.com/dysinger/status/1273652486







git-rebase