Більшість попередніх відповідей небезпечно неправильні!
Не роби цього:
git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch
Тому що наступного разу, коли будете виконувати git rebase
(або git pull --rebase
), ці 3 коміти будуть мовчки відкинуті newbranch
! (див. пояснення нижче)
Замість цього робіть:
git reset --keep HEAD~3
git checkout -t -b newbranch
git cherry-pick ..HEAD@{2}
- По-перше, він відкидає 3 останніх комісії (
--keep
це як --hard
, але безпечніше, оскільки не дає змоги замість того, щоб викинути невмілі зміни).
- Потім він відщеплюється
newbranch
.
- Потім він вибирає ці 3 зобов'язання назад на
newbranch
. Оскільки гілка більше не посилається на цю гілку, вона робить це за допомогою рефлогу git : HEAD@{2}
це фіксація, яка HEAD
використовувалась для посилань на 2 операції тому, тобто перед тим, як ми 1. перевірили newbranch
і 2. використали, git reset
щоб відкинути 3 коміти.
Попередження: відмикання ввімкнено за замовчуванням, але якщо ви його вручну відключили (наприклад, за допомогою "голого" сховища git), ви не зможете повернути 3 комісії після запуску 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
відмовитися від 3-х комітів після першого прикладу? Це тому, що 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
Використовуючи локальний рефлог, 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
це тому, що вони залишають рефлог в неправильному стані. Git вважає newbranch
, що він відхилив від гілки вище за течією, що включає 3 коміти, потім reset --hard
переписує історію висхідного потоку, щоб видалити коміти, і тому наступного разу, коли ви запустите git rebase
його, відкидає їх, як і будь-який інший комітет, який був видалений з висхідного потоку.
Але в цьому конкретному випадку ми хочемо, щоб ці 3 комітети були розглянуті як частина тематичної галузі. Щоб досягти цього, нам потрібно відхилитись від висхідного потоку при попередній редакції, яка не включає 3 коміти. Ось що роблять запропоновані нами рішення, тому вони обидва залишають рефлог у правильному стані.
Докладніше див. --fork-point
У розділі git rebase та git merge-base .