Перемістіть найновіші комісії в нову філію з Git


4981

Я хотів би перенести останні кілька комітетів, які я зобов’язав освоїти, до нової гілки і повернути головного до того, як ці комісії були здійснені. На жаль, мій Git-fu ще недостатньо сильний, будь-яка допомога?

Тобто як я можу піти з цього

master A - B - C - D - E

до цього?

newbranch     C - D - E
             /
master A - B 

114
Примітка: Я задав протилежний питання тут
Benjol


6
Чи були тут викладені коментарі? Я прошу, бо під час мого візиту на це щомісяця до цього питання я завжди прокручую цей коментар.
Техас Кале

Відповіді:


6449

Перехід до існуючої філії

Якщо ви хочете перемістити свої зобов’язання до існуючої гілки , це буде виглядати приблизно так:

git checkout existingbranch
git merge master         # Bring the commits here
git checkout master
git reset --keep HEAD~3  # Move master back by 3 commits.
git checkout existingbranch

--keepОпція зберігає будь-які непідтверджені зміни , які можуть виникнути в незв'язаних файлах, або відкотиться , якщо ці зміни повинні бути перезаписані - аналогічно тому , що git checkoutробить. Якщо вона перервана, git stashви можете змінити і повторити або використати --hardдля втрати змін (навіть із файлів, які не змінювались між комісіями!)

Перехід до нової філії

Цей метод працює, створюючи нову гілку з першою командою ( git branch newbranch), але не перемикаючись на неї. Потім відкочуємо поточну гілку (головну) та переходимо до нової гілки, щоб продовжувати працювати.

git branch newbranch      # Create a new branch, containing all current commits
git reset --keep 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
# Warning: after this it's not safe to do a rebase in newbranch without extra care.

Але переконайтеся, скільки зобов’язань повернутися назад. Крім того, замість цього HEAD~3, ви можете просто надати хеш коміту (або посилання на зразок origin/master), до якого потрібно повернути, наприклад:

git reset --keep a1b2c3d4

ПОПЕРЕДЖЕННЯ. Якщо Git версії 2.0 і пізнішої версії, якщо ви пізніше git rebaseнову гілку на оригінальній ( master) гілці, вам може знадобитися явний --no-fork-pointпараметр під час ребазації, щоб уникнути втрати комісій, які ви перемістили з ведучої гілки. Маючи branch.autosetuprebase alwaysнабір робить це більш ймовірно. Див відповідь Джона Меллора в подробиці.


249
І зокрема, не намагайтеся повертатися далі, ніж точка, коли ви востаннє натискали комісії, до іншого сховища, з якого, можливо, хтось інший витягнув.
Грег Хьюгілл

107
Цікаво, чи можете ви пояснити, ЧОМУ це працює. Для мене ви створюєте нову гілку, видаляючи 3 комітки зі старої гілки, на якій ви все ще перебуваєте, а потім перевіряєте гілку, яку ви зробили. Тож як у новій гілці відображаються віддані вами зобов'язання, які ви видалили?
Джонатан Дюман

142
@Jonathan Dumaine: Тому що я створив нову гілку перед тим, як видалити коміти зі старої гілки. Вони все ще там, у новій філії.
sykora

86
гілки в git - це лише маркери, які вказують на вчинки в історії, нічого не клонують, створюють або видаляють (крім маркерів)
knittl

218
Також зверніть увагу: Не робіть цього з невмілими змінами вашої робочої копії! Це мене просто вкусило! :(
Адам Таттл

1038

Для тих, хто цікавиться, чому це працює (як я був спочатку):

Ви хочете повернутися до 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переміщення основного вказівника назад два коміти. Але оскільки ви не переїхали на новий Branch , він все ще вказує на зобов'язання, яке він спочатку робив.


56
Мені також потрібно було зробити git push origin master --forceзміни, щоб відобразитися в головному сховищі.
Dženan

9
Ця відповідь призводить до втрати комітетів: наступного разу, коли ви git rebaseбудете беззвучно відкинуті, 3 коміти newbranch. Детальну інформацію та безпечніші варіанти див. У моїй відповіді .
Джон Меллор

14
@John, це нісенітниця. Звільнення, не знаючи, що ви робите, спричиняє втрату зобов’язань. Якщо ви втратили зобов’язання, вибачте за вас, але ця відповідь не втратила ваших зобов'язань. Зверніть увагу, що origin/masterна наведеній діаграмі не відображається. Якщо б ви підштовхнули origin/masterта потім внесли зміни вище, звичайно, все піде смішно. Але це проблема "Доктор, боляче, коли я роблю це". І це не виходить за рамки того, що було задано оригінальним запитанням. Я пропоную вам написати власне питання, щоб вивчити свій сценарій, а не викрадати цей.
Райан Лунді

1
@John, у своїй відповіді ви сказали "Не робіть цього! git branch -t newbranch". Поверніться і ще раз прочитайте відповіді. Ніхто не пропонував цього робити.
Райан Лунді

1
@Kyralessa, звичайно, але якщо ви подивитесь на діаграму у питанні, то зрозуміло, що вони хочуть newbranchбазуватися на своєму місцевому masterвідділенні. Після виконання обслуговується відповіді, коли користувач отримує навколо працює git rebaseв newbranch, мерзотник буде нагадувати їм , що вони забули встановити вгору по течії гілки, так що вони будуть працювати , git branch --set-upstream-to=masterто git rebaseі та ж проблема. Вони також можуть в git branch -t newbranchпершу чергу використовувати.
Джон Меллор

454

В загальному...

Спосіб, що піддається сикорі, є найкращим варіантом у цьому випадку. Але іноді це не найпростіший і це не загальний метод. Для загального методу використовують git cherry-pick :

Щоб досягти того, що хоче ОП, його двоетапний процес:

Крок 1 - Зауважте, що здійснює команда від майстра, який ви хочете в newbranch

Виконати

git checkout master
git log

Зверніть увагу, що хеші (скажімо 3) здійснюють потрібні вами дії newbranch. Тут я буду використовувати:
C фіксація: 9aa1233
D фіксація: 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

git cherry-pick застосовує ці три версії до новобранства.


13
Хіба ОП не намагалася перейти від майстра до нового відділення? Якщо ви підбираєте вишню, коли будете майстрами, ви б додавали до гілки майстра - додавання фактів, які у нього вже були. І вона не рухається назад ні головою відділення до Б. Або є щось тонке і прохолодне, я не отримую?
RaveTheTadpole

6
Це дуже добре спрацьовує, якщо ви випадково вчинили неправильну гілку, яка не є головним, коли ви повинні створити нову гілку функцій.
julianc

5
+1 - корисний підхід у деяких ситуаціях. Це добре, якщо ви хочете лише перетягнути свої власні зобов'язання (які перемежовуються з іншими) у нову галузь.
Тайлер В.

7
Краще відповідь. Таким чином ви можете переміщувати комісії в будь-яку галузь.
skywinder

8
Чи важливий порядок збору вишні?
kon psych

328

Ще один спосіб зробити це, використовуючи лише 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щоб .це хороший трюк , щоб знати.


1
Поточний каталог. Напевно, це спрацювало б лише в тому випадку, якщо ви знаходитесь у топ-каталозі.
араґер

1
Місцевий поштовх викликає всмішку, але, подумавши, як це по-іншому git branch -f?
jthill

2
@GerardSexton .- нинішній директор. git може натиснути на REMOTES або GIT URL. path to local directoryпідтримується синтаксис URL-адрес Git. Дивіться розділ URL-адрес GIT в git help clone.
слабкий

31
Я не знаю, чому це не оцінюється вище. Мертвий простий і без невеликої, але потенційної небезпеки скидання кишки - твердий.
Godsmith

4
@Godsmith Гадаю, люди віддають перевагу трьом простим командам, ніж двом дещо незрозумілішим командам. Крім того, відповіді, проголосовані вище, отримують більше результатів за характером того, що вони відображаються першими.
JS_Riddler

322

Більшість попередніх відповідей небезпечно неправильні!

Не роби цього:

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 .


14
Ця відповідь говорить "НЕ робіть цього!" вище чогось, що ніхто не запропонував зробити.
Райан Лунді

3
Більшість людей не переписують опубліковану історію, особливо на master. Так ні, вони не небезпечно помиляються.
Вальф

4
@Kyralessa, те, про що -tви посилаєтесь, git branchвідбувається неявно, якщо ви git config --global branch.autosetuprebase alwaysвстановили. Навіть якщо ви цього не зробите, я вже пояснив вам, що така ж проблема виникає, якщо ви налаштуєте відстеження після виконання цих команд, як це, швидше за все, планує зробити ОП з огляду на їх питання.
Джон Меллор

2
@RockLee, так, загальним способом виправити подібні ситуації є створення свіжої гілки (newbranch2) з безпечної вихідної точки, а потім вишня - виберіть усі зобов'язання, які ви хочете зберегти (від badnewbranch до newbranch2). Вибір вишень надасть новим хешам, тож ви зможете безпечно перезавантажити newbranch2 (і тепер можете видалити badnewbranch).
Джон Меллор

2
@Walf, ви неправильно зрозуміли: git rebase розроблений таким чином, щоб бути надійним у відношенні потоків із переписаною історією. На жаль, побічні ефекти цієї стійкості впливають на всіх, навіть якщо ні вони, ні їхній верхівка ніколи не переписують історію.
Джон Меллор

150

Набагато простіше рішення з використанням git stash

Ось набагато простіше рішення для дозволів на неправильну гілку. Починаючи з гілки, masterяка має три помилкові коміти:

git reset HEAD~3
git stash
git checkout newbranch
git stash pop

Коли це використовувати?

  • Якщо ваша основна мета - відкотити master
  • Ви хочете зберегти зміни файлів
  • Вам не байдуже повідомлення про помилкові коміти
  • Ви ще не натиснули
  • Ви хочете, щоб це було легко запам'ятати
  • Ви не хочете ускладнень, таких як тимчасові / нові гілки, пошук та копіювання виконувати хеші та інші головні болі

Що це робить, за номером рядка

  1. Скасовує три останніх комісії (та їх повідомлення) master, але всі робочі файли залишаються недоторканими
  2. Приховує всі зміни робочого файлу, завдяки чому masterробоче дерево точно дорівнює стану HEAD ~ 3
  3. Перемикається на існуючу гілку newbranch
  4. Застосовує приховані зміни до вашої робочої директорії та очищує копію

Тепер ви можете використовувати git addта git commitяк зазвичай. Усі нові комісії будуть додані до newbranch.

Що це не робить

  • Це не залишає випадкових тимчасових гілок, що захаращують ваше дерево
  • Він не зберігає помилкові повідомлення про фіксацію, тому вам потрібно буде додати нове повідомлення про фіксацію
  • Оновлення! Використовуйте стрілку вгору, щоб прокрутити буфер команд, щоб повторно застосувати попереднє фіксування з повідомленням про фіксацію (дякую @ARK)

Цілі

ОП заявила, що метою було "повернути керівника до того, як ці комісії були здійснені", не втрачаючи змін, і це рішення робить це.

Я роблю це щонайменше раз на тиждень, коли випадково masterзамість цього роблю нові зобов’язання develop. Зазвичай у мене є лише одна фіксація відкату, і в цьому випадку, використовуючи git reset HEAD^рядок 1, є більш простим способом відкатати лише одну фіксацію.

Не робіть цього, якщо ви натиснули зміни господаря вище за течією

Можливо, хтось із них змінив ці зміни. Якщо ви переписуєте лише свого місцевого майстра, це не вплине, коли воно буде висунуте вгору за течією, але поштовх переписаної історії до співробітників може призвести до головних болів.


4
Дякую, я дуже радий, що я прочитав минуле / через стільки, щоб дістатися сюди, тому що це досить поширений випадок використання і для мене. Ми такі нетипові?
Джим Мак

6
Я думаю, що ми абсолютно типові, і "ой, що я взяв на себе майстер помилково" - це найпоширеніший випадок використання, коли потрібно повернути кілька або менше комісій. Пощастило це рішення настільки просте, що я його запам’ятав зараз.
Шлем

9
Це має бути прийнятою відповіддю. Це просто, легко зрозуміти і легко запам’ятати
Сина Мадані

1
Я не думаю, що приховування є необхідним. Я просто це зробив без і добре працював.
Кампос

1
Ви також можете легко повернути свої повідомлення про фіксацію, якщо вони трапляються в історії CLI (командного рядка). У мене трапилось git addі git commitкоманди, і команди, які я використовував, тому все, що я повинен був зробити, це натиснути стрілку вгору і ввести кілька разів і бум! Все повернулося, але зараз на правій гілці.
Лука Гедеон

30

Це не "переміщує" їх у технічному сенсі, але має такий же ефект:

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)

1
Хіба ти не можеш використовувати rebaseте саме?
Бергі

Так, ви можете альтернативно використовувати rebaseна відокремленій гілці у вищезазначеному сценарії.
Сукіма

24

Для цього потрібно не переписувати історію (тобто, якщо ви вже натиснули коміти):

git checkout master
git revert <commitID(s)>
git checkout -b new-branch
git cherry-pick <commitID(s)>

Обидві гілки потім можна натиснути без сили!


Але тоді вам доведеться розібратися зі сценарієм повернення, який, залежно від ваших обставин, може бути набагато складнішим. Якщо ви скасуєте комісію на гілці, Git все одно буде бачити ті комітети, що відбулися, тож для того, щоб скасувати це, вам доведеться відновити відновлення. Це спалює досить багато людей, особливо коли вони скасовують злиття і намагаються об'єднати гілку назад, лише виявивши, що Гіт вважає, що вона вже об'єднала цю гілку (що цілком вірно).
Макото

1
Ось чому я підбираю вишні в кінці, на нову гілку. Таким чином git сприймає їх як нові коміти, що вирішує вашу проблему.
teh_senaus

Це небезпечніше, ніж здається спочатку, оскільки ви змінюєте стан історії сховища, не розуміючи наслідків цього стану.
Макото

5
Я не дотримуюся вашого аргументу - суть цієї відповіді полягає в тому, що ви не змінюєте історію, просто додаєте нові коміти (що фактично скасовує повтор змін). Ці нові комітети можна висунути та об'єднати як звичайні.
teh_senaus

13

Була тільки така ситуація:

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

7

Як я можу з цього піти

A - B - C - D - E 
                |
                master

до цього?

A - B - C - D - E 
    |           |
    master      newbranch

З двома командами

  • git гілка -m master newbranch

давання

A - B - C - D - E 
                |
                newbranch

і

  • майстер відділення git B

давання

A - B - C - D - E
    |           |
    master      newbranch

Так, це працює і досить легко. Графічний інтерфейс Sourcetree трохи заплутаний щодо змін, внесених в оболонку git, але після вибору знову все в порядку.
lars k.

Так, вони як і в питанні. Перші кілька діаграм мають бути еквівалентними наведеним у запитанні, просто перемальовували так, як хотілося б для ілюстрації у відповіді. В основному перейменуйте головну гілку як нову галузь і створіть нову головну гілку там, де вам потрібно.
Іван

4

Якщо вам просто потрібно перемістити всі свої незапущені зобов’язання до нової гілки , то вам просто потрібно,

  1. створити в нову гілку від поточної:git branch new-branch-name

  2. натисніть свою нову гілку :git push origin new-branch-name

  3. повернути свою стару (поточну) гілка до останнього штовхає / стійкого стану:git reset --hard origin/old-branch-name

У деяких людей є і інші, upstreamsа не originвони повинні використовувати відповіднеupstream


3

1) Створіть нову гілку, яка переміщує всі ваші зміни до new_branch.

git checkout -b new_branch

2) Потім поверніться до старої гілки.

git checkout master

3) Робіть git rebase

git rebase -i <short-hash-of-B-commit>

4) Потім відкритий редактор містить три останні дані про фіксацію.

...
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) Зараз останні три комісії видалено з поточної гілки ( master). Тепер натисніть на гілку сильно, зі +знаком перед назвою гілки.

git push origin +master

2

Ви можете зробити це лише 3 простих кроки, якими я користувався.

1) зробіть нову гілку, де ви хочете здійснити нещодавнє оновлення.

git branch <branch name>

2) Знайдіть нещодавній ідентифікатор комісії для фіксації в новій гілці.

git log

3) Скопіюйте ідентифікатор фіксації, що список останніх фіксованих комісій відбувається вгорі. тож ви зможете знайти свою прихильність Ви також знаходите це через повідомлення.

git cherry-pick d34bcef232f6c...

Ви також можете надати деякий дзвінок ідентифікатора фіксації.

git cherry-pick d34bcef...86d2aec

Тепер ваша робота зроблена. Якщо ви вибрали правильний ідентифікатор та правильну гілку, тоді ви отримаєте успіх. Тому перед цим будьте уважні. ще може виникнути інша проблема.

Тепер ви можете натиснути свій код

git push


0

Ще один спосіб зробити це:

[1] Перейменуйте masterгілку у свою newbranch(якщо ви перебуваєте у masterвідділенні):

git branch -m newbranch

[2] Створіть masterгілку з потрібного вам зобов’язання:

git checkout -b master <seven_char_commit_id>

наприклад, git checkout -b master a34bc22

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.