انقل الالتزام (الالتزامات) الأحدث إلى فرع جديد باستخدام Git
-
06-07-2019 - |
سؤال
أرغب في نقل الالتزامات العديدة الأخيرة التي التزمت بإتقانها إلى فرع جديد واستعادة السيد مرة أخرى قبل تنفيذ تلك الالتزامات.لسوء الحظ، جهاز Git-fu الخاص بي ليس قويًا بدرجة كافية حتى الآن، هل من مساعدة؟
أي.كيف يمكنني أن أذهب من هذا
master A - B - C - D - E
الى هذا؟
newbranch C - D - E
/
master A - B
المحلول
الانتقال إلى فرع جديد
تحذير: تعمل هذه الطريقة لأنك تقوم بإنشاء فرع جديد باستخدام الأمر الأول: git branch newbranch
.إذا كنت تريد الانتقال يلتزم بـ الفرع الموجود تحتاج إلى دمج تغييراتك في الفرع الحالي قبل التنفيذ git reset --hard HEAD~3
(يرى الانتقال إلى فرع موجود أقل). إذا لم تقم بدمج تغييراتك أولاً، فسيتم فقدها.
ما لم تكن هناك ظروف أخرى، يمكن القيام بذلك بسهولة عن طريق التفرع والتراجع.
# Note: Any changes not committed will be lost.
git branch newbranch # Create a new branch, saving the desired commits
git reset --hard HEAD~3 # Move master back by 3 commits (Make sure you know how many commits you need to go back)
git checkout newbranch # Go to the new branch that still has the desired commits
ولكن تأكد من عدد الالتزامات بالعودة.بدلا من ذلك، يمكنك بدلا من ذلك HEAD~3
, ، ما عليك سوى توفير تجزئة الالتزام (أو المرجع مثل الأصل / سيد) تريد "العودة إلى" على يتقن فرع (/ الحالي)، على سبيل المثال:
git reset --hard a1b2c3d4
*1 سوف تفعل فقط ستكون "خاسرًا" للالتزامات من الفرع الرئيسي، ولكن لا تقلق، سيكون لديك تلك الالتزامات في الفرع الجديد!
تحذير: مع Git الإصدار 2.0 والإصدارات الأحدث، إذا قمت بذلك لاحقًا git rebase
الفرع الجديد على الأصل (master
) فرع، قد تحتاج إلى صريحة --no-fork-point
الخيار أثناء إعادة الأساس لتجنب فقدان الالتزامات المُرحّلة.نأخذ branch.autosetuprebase always
مجموعة تجعل هذا أكثر احتمالا.يرى إجابة جون ميلور للتفاصيل.
الانتقال إلى فرع موجود
إذا كنت تريد نقل التزاماتك إلى الفرع الموجود, ، سيبدو هكذا:
git checkout existingbranch
git merge master
git checkout master
git reset --hard HEAD~3 # Go back 3 commits. You *will* lose uncommitted work.
git checkout existingbranch
نصائح أخرى
لأولئك الذين يتساءلون لماذا يعمل (كما كنت في البداية):
وترغب في العودة إلى C، والانتقال D و E إلى الفرع الجديد. وهنا ما يبدو في البداية:
A-B-C-D-E (HEAD)
↑
master
وبعد git branch newBranch
:
newBranch
↓
A-B-C-D-E (HEAD)
↑
master
وبعد git reset --hard HEAD~2
:
newBranch
↓
A-B-C-D-E (HEAD)
↑
master
ومنذ فرع هو مجرد مؤشر، <م> الرئيسية م> وأشار الى الاخير يرتكبها. عندما قمت بها <م> newBranch م>، التي قمت بها مجرد مؤشر جديد الى الاخير يرتكبها. ثم استخدام git reset
قمت بنقل سيد <م> م> المؤشر يعود اثنين يرتكب. ولكن منذ كنت لا تتحرك <م> newBranch م>، فإنه لا يزال يشير إلى ارتكاب فعل أصلا.
على العموم...
الطريقة التي كشفت عنها sykora هي الخيار الأفضل في هذه الحالة.لكن في بعض الأحيان لا تكون هذه هي الطريقة الأسهل وليست طريقة عامة.لاستخدام طريقة عامة جيت الكرز اختيار:
لتحقيق ما يريده OP، فهي عملية مكونة من خطوتين:
الخطوة 1 - لاحظ الالتزامات التي تريدها من السيد على ملف newbranch
ينفذ
git checkout master
git log
لاحظ تجزئات (على سبيل المثال 3) التي تريدها newbranch
.وهنا سأستخدم:
ج الالتزام: 9aa1233
د الإلتزام : 453ac3d
الالتزام ه: 612ecb3
ملحوظة: يمكنك استخدام الأحرف السبعة الأولى أو تجزئة الالتزام بأكملها
الخطوة 2 - ضعهم على newbranch
git checkout newbranch
git cherry-pick 612ecb3
git cherry-pick 453ac3d
git cherry-pick 9aa1233
أو (على Git 1.7.2+، استخدم النطاقات)
git checkout newbranch
git cherry-pick 612ecb3~1..9aa1233
جيت الكرز اختيار يطبق تلك الالتزامات الثلاثة على الفرع الجديد.
وطريقة أخرى للقيام بذلك، وذلك باستخدام فقط 2 الأوامر. أيضا يحافظ بك شجرة العمل الحالية سليمة.
git checkout -b newbranch # switch to a new branch
git branch -f master HEAD~3 # make master point to some older commit
<القوي> قديم نسخة - يمكنك قبل تعلمت عن git branch -f
git checkout -b newbranch # switch to a new branch
git push . +HEAD~3:master # make master point to some older commit
وتكون قادرة على push
إلى .
هو خدعة لطيفة إلى معرفته.
معظم الإجابات السابقة خاطئة بشكل خطير!
لا تفعل هذا:
git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch
كما في المرة القادمة التي تقوم فيها بتشغيل git rebase
(أو git pull --rebase
) سيتم تجاهل تلك الالتزامات الثلاثة بصمت newbranch
!(انظر الشرح أدناه)
بدلاً من ذلك قم بما يلي:
git reset --keep HEAD~3
git checkout -t -b newbranch
git cherry-pick ..HEAD@{2}
- أولاً، يتجاهل الالتزامات الثلاثة الأخيرة (
--keep
يشبه--hard
, ، ولكنه أكثر أمانًا، لأنه يفشل بدلاً من التخلص من التغييرات غير الملتزم بها). - ثم يتفرع
newbranch
. - ثم يقوم باختيار تلك الالتزامات الثلاثة مرة أخرى
newbranch
.وبما أنه لم يعد يتم الرجوع إليها بواسطة فرع، فإنه يفعل ذلك باستخدام git's إعادة التسجيل:HEAD@{2}
هو الالتزام بذلكHEAD
تستخدم للإشارة إلى عمليتين مضت، أي.قبل أن 1.فحصتnewbranch
و 2.مستخدمgit reset
لتجاهل الالتزامات الثلاثة.
تحذير:يتم تمكين إعادة التسجيل بشكل افتراضي، ولكن إذا قمت بتعطيله يدويًا (على سبيل المثال،باستخدام مستودع git "المجرد"، لن تتمكن من استعادة الالتزامات الثلاثة بعد التشغيل git reset --keep HEAD~3
.
البديل الذي لا يعتمد على إعادة التسجيل هو:
# newbranch will omit the 3 most recent commits.
git checkout -b newbranch HEAD~3
git branch --set-upstream-to=oldbranch
# Cherry-picks the extra commits from oldbranch.
git cherry-pick ..oldbranch
# Discards the 3 most recent commits from oldbranch.
git branch --force oldbranch oldbranch~3
(إذا كنت تفضل ذلك يمكنك الكتابة @{-1}
- الفرع الذي تم فحصه مسبقًا - بدلاً من oldbranch
).
شرح تقني
لماذا أراد git rebase
تجاهل الالتزامات الثلاثة بعد المثال الأول؟انه بسبب git rebase
مع عدم وجود حجج تمكن --fork-point
الخيار افتراضيًا، والذي يستخدم إعادة التسجيل المحلي لمحاولة أن يكون قويًا ضد الفرع الرئيسي الذي يتم دفعه بالقوة.
لنفترض أنك قمت بتقسيم الأصل/الرئيسي عندما كان يحتوي على الالتزامات M1 وM2 وM3، ثم قمت بتنفيذ ثلاثة التزامات بنفسك:
M1--M2--M3 <-- origin/master
\
T1--T2--T3 <-- topic
ولكن بعد ذلك يقوم شخص ما بإعادة كتابة التاريخ عن طريق دفع الأصل/الرئيسي لإزالة M2:
M1--M3' <-- origin/master
\
M2--M3--T1--T2--T3 <-- topic
باستخدام reflog المحلي الخاص بك، git rebase
يمكنك أن ترى أنك تفرعت من تجسيد سابق للفرع الأصلي/الرئيسي، وبالتالي فإن التزامات M2 وM3 ليست في الواقع جزءًا من فرع الموضوع الخاص بك.ومن ثم يفترض بشكل معقول أنه منذ إزالة M2 من الفرع الرئيسي، فإنك لم تعد تريده في فرع الموضوع الخاص بك سواء بمجرد إعادة تأسيس فرع الموضوع:
M1--M3' <-- origin/master
\
T1'--T2'--T3' <-- topic (rebased)
هذا السلوك منطقي، وهو عمومًا الشيء الصحيح الذي يجب فعله عند إعادة التأسيس.
إذن سبب فشل الأوامر التالية:
git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch
لأنهم يتركون إعادة التسجيل في الحالة الخاطئة.يرى جيت newbranch
كما لو كان قد تفرع من الفرع الرئيسي في مراجعة تتضمن الالتزامات الثلاثة، ثم reset --hard
يعيد كتابة سجل المنبع لإزالة الالتزامات، وذلك في المرة القادمة التي تقوم فيها بالتشغيل git rebase
فهو يتجاهلها مثل أي التزام آخر تمت إزالته من المنبع.
ولكن في هذه الحالة بالذات نريد أن يتم اعتبار هذه الالتزامات الثلاثة جزءًا من فرع الموضوع.ولتحقيق ذلك، نحتاج إلى التخلص من المنبع في المراجعة السابقة التي لا تتضمن الالتزامات الثلاثة.هذا ما تفعله الحلول المقترحة، ومن ثم يترك كلاهما إعادة التسجيل في الحالة الصحيحة.
لمزيد من التفاصيل، راجع تعريف --fork-point
في ال جيت rebase و قاعدة دمج git مستندات.
حل أبسط بكثير باستخدام git stash
إليك طريقة أبسط بكثير للالتزام بالفرع الخطأ.البدء بالفرع master
الذي لديه ثلاثة ارتكابات خاطئة:
git reset HEAD~3
git stash
git checkout newbranch
git stash pop
متى تستخدم هذا؟
- إذا كان هدفك الأساسي هو التراجع
master
- تريد الاحتفاظ بتغييرات الملف
- أنت لا تهتم بالرسائل المتعلقة بالالتزامات الخاطئة
- أنت لم تدفع بعد
- تريد أن يكون هذا سهل الحفظ
- لا تريد تعقيدات مثل الفروع المؤقتة/الجديدة، وإيجاد ونسخ تجزئات الالتزام، وغيرها من المشاكل
ما يفعله هذا، عن طريق رقم السطر
- يلغي الالتزامات الثلاثة الأخيرة (ورسائلهم) لـ
master
, ، ومع ذلك يترك كافة ملفات العمل سليمة - يخفي جميع تغييرات ملف العمل، مما يجعل ملف
master
شجرة العمل تساوي تمامًا حالة HEAD~3 - التبديل إلى فرع موجود
newbranch
- يطبق التغييرات المخفية على دليل العمل الخاص بك ويمسح المخزن
يمكنك الآن استخدام git add
و git commit
كما تفعل عادة.سيتم إضافة كافة الالتزامات الجديدة إلى newbranch
.
ما هذا لا يفعل
- لا يترك فروعًا مؤقتة عشوائية تشوش شجرتك
- لا يحافظ على الالتزامات الخاطئة ورسائل الالتزام، لذلك ستحتاج إلى إضافة رسالة التزام جديدة إلى هذا الالتزام الجديد
الأهداف
ذكر OP أن الهدف هو "استعادة السيطرة على ما قبل تنفيذ تلك الالتزامات" دون فقدان التغييرات وهذا الحل يفعل ذلك.
أفعل ذلك مرة واحدة على الأقل في الأسبوع عندما أقوم بالتزامات جديدة عن طريق الخطأ master
بدلاً من develop
.عادةً ما يكون لدي التزام واحد فقط بالتراجع في هذه الحالة باستخدام git reset HEAD^
على السطر 1 هي طريقة أبسط للتراجع عن التزام واحد فقط.
لا تفعل هذا إذا قمت بدفع التغييرات الرئيسية إلى المنبع
ربما قام شخص آخر بسحب هذه التغييرات.إذا كنت تقوم فقط بإعادة كتابة سيدك المحلي، فلن يكون هناك أي تأثير عند دفعه إلى أعلى، ولكن دفع التاريخ المعاد كتابته إلى المتعاونين يمكن أن يسبب الصداع.
وهذا لا "نقل" لهم بالمعنى التقني لكنه لا يملك نفس التأثير:
A--B--C (branch-foo)
\ ^-- I wanted them here!
\
D--E--F--G (branch-bar)
^--^--^-- Opps wrong branch!
While on branch-bar:
$ git reset --hard D # remember the SHAs for E, F, G (or E and G for a range)
A--B--C (branch-foo)
\
\
D-(E--F--G) detached
^-- (branch-bar)
Switch to branch-foo
$ git cherry-pick E..G
A--B--C--E'--F'--G' (branch-foo)
\ E--F--G detached (This can be ignored)
\ /
D--H--I (branch-bar)
Now you won't need to worry about the detached branch because it is basically
like they are in the trash can waiting for the day it gets garbage collected.
Eventually some time in the far future it will look like:
A--B--C--E'--F'--G'--L--M--N--... (branch-foo)
\
\
D--H--I--J--K--.... (branch-bar)
لذلك دون إعادة كتابة التاريخ (أي إذا كنت قد دفعت بالفعل ولجان):
git checkout master
git revert <commitID(s)>
git checkout -b new-branch
git cherry-pick <commitID(s)>
ويمكن بعد ذلك أن يدفع كل الفروع دون استخدام القوة!
وكان مجرد هذا الوضع:
Branch one: A B C D E F J L M
\ (Merge)
Branch two: G I K N
وأديت:
git branch newbranch
git reset --hard HEAD~8
git checkout newbranch
وكنت أتوقع أن ارتكاب سأكون الرأس، ولكن ارتكاب L هو عليه الآن ...
لتأكد على الأرض على المكان الصحيح في تاريخها أسهل للعمل مع تجزئة ارتكاب
git branch newbranch
git reset --hard #########
git checkout newbranch
1) إنشاء فرع جديد، والتي تتحرك كل ما تبذلونه من التغييرات على new_branch.
git checkout -b new_branch
2) ثم انتقل إلى فرع القديمة.
git checkout master
و3) هل بوابة rebase
git rebase -i <short-hash-of-B-commit>
و4) ثم يحتوي على محرر فتحت الماضي 3 ارتكاب المعلومات.
...
pick <C's hash> C
pick <D's hash> D
pick <E's hash> E
...
و5) تغيير pick
إلى drop
في كل تلك يرتكب 3. ثم حفظ وإغلاق المحرر.
...
drop <C's hash> C
drop <D's hash> D
drop <E's hash> E
...
و6) والآن تتم إزالة 3 يرتكب مشاركة من فرع الحالي (master
). الآن دفع فرع بقوة، مع علامة +
قبل اسم الفرع.
git push origin +master
كيف يمكنني أن أذهب من هذا
A - B - C - D - E
|
master
الى هذا؟
A - B - C - D - E
| |
master newbranch
مع أمرين
- فرع git -m master newbranch
إعطاء
A - B - C - D - E
|
newbranch
و
- بوابة فرع رئيسي ب
إعطاء
A - B - C - D - E
| |
master newbranch
يمكنك القيام بذلك من خلال 3 خطوات بسيطة استخدمتها.
1) قم بإنشاء فرع جديد حيث تريد الالتزام بالتحديث الأخير.
git branch <branch name>
2) ابحث عن معرف الالتزام الأخير للالتزام بالفرع الجديد.
git log
3) انسخ معرف الالتزام هذا ولاحظ أن قائمة الالتزام الأحدث موجودة في الأعلى.حتى تتمكن من العثور على الالتزام الخاص بك.تجد هذا أيضًا عبر الرسالة.
git cherry-pick d34bcef232f6c...
يمكنك أيضًا تقديم بعض نطاق معرف الالتزام.
git cherry-pick d34bcef...86d2aec
الآن انتهت مهمتك.إذا اخترت المعرف الصحيح والفرع الصحيح فسوف تنجح.لذلك قبل القيام بذلك كن حذرا.وإلا يمكن أن تحدث مشكلة أخرى.
الآن يمكنك دفع التعليمات البرمجية الخاصة بك
git push
إذا كنت بحاجة فقط إلى نقل كل ما تبذلونه من غير مدفوعة يلتزم أ فرع جديد، ثم تحتاج فقط إلى ،
يخلق أ فرع جديد من الحالي :
git branch new-branch-name
يدفع لك فرع جديد:
git push origin new-branch-name
يرجع لك الفرع القديم (الحالي). إلى آخر حالة مدفوعة/مستقرة:
git reset --hard origin/old-branch-name
بعض الناس لديهم أيضا غيرها upstreams
بدلا من origin
, ، يجب عليهم استخدام مناسب upstream