Як відновити кілька git-комітетів?


982

У мене є сховище git, яке виглядає приблизно так:

A -> B -> C -> D -> HEAD

Я хочу, щоб керівник відділення вказував на A, тобто я хочу, щоб B, C, D і HEAD зникли, і я хочу, щоб голова була синонімом A.

Здається, я можу спробувати відновити базу даних (не застосовується, оскільки я натиснув зміни між ними), або повернути назад. Але як я можу відновити кілька комісій? Я повертаю їх по черзі? Чи важливий порядок?


3
Якщо ви просто хочете скинути пульт дистанційного керування, ви можете його заглушити будь-чим! Але давайте скористаємось четвертим фіксом тому: git push -f HEAD~4:master(припустимо, що віддалений відділення є головним). Так, ви можете підштовхнути будь-які такі дії.
u0b34a0f6ae

21
Якщо люди витягнули вас, ви повинні взяти на себе зобов’язання, яке повертає зміни, використовуючи git revert.
Якуб Нарбський

1
Використовуйте git show HEAD ~ 4, щоб переконатися, що ви натискаєте праворуч на пульт
Mâtt Frëëman,

1
Можливий дублікат способу скасування останніх комісій у Git?
Джим Фелл

5
"Чи важливий порядок?" Так, якщо коміти впливають на однакові рядки в одних і тих же файлах. Тоді вам слід почати скасовувати останню версію, і повернутись назад.
avandeursen

Відповіді:


1333

Розширюючи те, що я написав у коментарі

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

Таким чином, рішення полягає в створенні нового комітету, який скасовує зміни, які ви хочете позбутися. Це можна зробити за допомогою команди git revert .

У вас є така ситуація:

A <- B <- C <- D <- майстер <- ГОЛОВА

(стрілки тут посилаються на напрямок вказівника: посилання "батьків" у разі комітів, верхнє фіксування у випадку голови відділення (відгалуження гілки) та назва гілки у випадку посилання HEAD).

Вам потрібно створити наступне:

A <- B <- C <- D <- [(BCD) ^ - 1] <- master <- HEAD

де "[(BCD) ^ - 1]" означає коміту, яка повертає зміни у комітах B, C, D. Математика говорить нам, що (BCD) ^ - 1 = D ^ -1 C ^ -1 B ^ -1, так ви можете отримати потрібну ситуацію, скориставшись такими командами:

$ git revert --no-commit D
$ git revert --no-commit C
$ git revert --no-commit B
$ git commit -m "the commit message"

Альтернативним рішенням було б перевірити вміст комірки А та здійснити цей стан:

$ git checkout -f A -- .
$ git commit -a

Тоді у вас виникла б така ситуація:

A <- B <- C <- D <- A '<- майстер <- ГОЛОВА

Здійснення A 'має той самий зміст, що і команда A, але це інше виконання (повідомлення про вчинення, батьки, дата вчинення).

Рішення Джеффа Ферланд, модифіковане Чарльз Бейлі грунтується на ту ж ідею, але використовує GIT скидання :

$ git reset --hard A
$ git reset --soft @{1}  # (or ORIG_HEAD), which is D
$ git commit

40
Якщо ви додали файли в B, C або D. git checkout -f A -- .Ці дані не буде видалено, вам доведеться зробити це вручну. Я застосував цю стратегію зараз, дякую Якуб
ома

18
Ці рішення не рівноцінні. Перший не видаляє новостворені файли.
m33lky

10
@Jerry: git checkout fooможе означати відділення каси foo(перехід на відділення) або файл каси для оформлення замовлення (з індексу). --використовується для розмежування, наприклад git checkout -- foo, завжди йдеться про файл.
Якуб Нарубський

87
Окрім чудової відповіді. Ця стенограма працює для менеgit revert --no-commit D C B
welldan97

9
@ welldan97: Дякую за коментар. Під час написання цієї відповіді git revertне було прийнято декількох комісій; це зовсім нове доповнення.
Якуб Нарбський

247

Чистий спосіб, який я вважав корисним

git revert --no-commit HEAD~3..

Ця команда повертає останні 3 коміти лише з одним комітом.

Також не переписує історію.


16
Це проста і найкраща відповідь
Жульєн Деняу

3
@JohnLittle він стадіює зміни. git commitзвідти буде фактично виконувати зобов’язання.
x1a4

14
Це не спрацює, якщо деякі коміти є об'єднаннями.
MegaManX

5
Що роблять дві точки в кінці?
кардамон

5
@cardamom Ці задають діапазон. HEAD~3..те саме, щоHEAD~3..HEAD
Toine H

238

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

Беручи до уваги свій приклад, вам доведеться це зробити (якщо припустити, що ви перебуваєте в "майстер" відділення):

git revert master~3..master

Це створить новий комітет у вашому локальному регіоні з оберненими комітками B, C і D (це означає, що він скасує зміни, внесені цими комісіями):

A <- B <- C <- D <- BCD' <- HEAD

129
git revert --no-commit HEAD~2..це трохи більш ідіоматичний спосіб зробити це. Якщо ви перебуваєте на головній гілці, більше не потрібно вказувати майстра. Цей --no-commitпараметр дозволяє git спробувати відновити всі комісії відразу, замість того, щоб засмічувати історію декількома revert commit ...повідомленнями (припускаючи, що це потрібно).
кубі

6
@Victor Я виправив діапазон ваших зобов’язань. Початок асортименту є ексклюзивним, тобто він не включений. Отже, якщо ви хочете відновити останні 3 коміти, вам потрібно запустити діапазон з батьківського третього коміту, тобто master~3.

2
@kubi Чи немає способу включити SHA в повідомлення про фіксацію, використовуючи єдиний фіксатор (ваш метод, але без необхідності вручну вводити комітети, які були повернені)?
Кріс С

@ChrisS Першою моєю думкою було б не користуватися --no-commit(тому ви отримуєте окрему комісію за кожне відновлення), а потім розбийте їх усі в інтерактивній базі даних. Комбіноване повідомлення про фіксацію буде містити всі SHA, і ви можете їх упорядкувати, як вам подобається, використовуючи улюблений редактор повідомлень про фіксацію.
Радон Росборо

71

Подібно до відповіді Якуба, це дозволяє легко вибирати послідовні зобов’язання для повернення.

# revert all commits from B to HEAD, inclusively
$ git revert --no-commit B..HEAD  
$ git commit -m 'message'

9
Ваше рішення працювало для мене чудово, але з незначною модифікацією. Якщо у нас є цей випадок Z -> A -> B -> C -> D -> HEAD, і якщо я хотів би повернутися до стану A, тоді дивно, мені довелося б виконати відновлення git - no-commit Z .. ГОЛОВА
Богдан

3
Погодьтеся з @Bogdan, діапазон повернення такий: SHA_TO_REVERT_TO..HEAD
Вадим Тиєміров

11
Діапазон неправильний. Це повинно бути B^..HEAD, інакше B виключено.
тесс

3
Погодьтеся з @tessus, тож правильним було б: git revert --no-commit B^..HEADабоgit revert --no-commit A..HEAD
Yoho

64
git reset --hard a
git reset --mixed d
git commit

Це діятиме як повернення для всіх відразу. Дайте гарне повідомлення про вчинення.


4
Якщо він хоче HEADвиглядати таким Aчином, він, ймовірно, хоче, щоб індекс відповідав тому git reset --soft D, мабуть, більш підходящим.
CB Bailey

2
- софт скидання не переміщує індекс, тому, коли він здійснює, це буде виглядати так, що фіксація прийшла безпосередньо з, а не з D. Це призвело б до розбиття гілки. - змішаний залишає зміни, але переміщує покажчик індексу, тому D стане батьківським комітом.
Джефф Ферланд

5
Так, я думаю, що git reset --keep - це саме те, що я маю вище. Він з'явився у версії 1.7.1, випущеній у квітні 2010 року, тому відповіді на той момент не було.
Джефф Ферланд

git checkout Aтоді git commitвище для мене не працювало, але ця відповідь зробила.
SimplGy

Чому це git reset --mixed Dпотрібно? А саме чому reset? Це тому, що, не скидаючи на D, HEAD би вказував на A, в результаті чого B, C і D були "звисаючими" та зібраними сміттям - а це не те, чого він хоче? Але тоді чому --mixed? Ви вже відповіли " --softскидання не переміщує індекс ..." Отже, перемістивши індекс, це означає, що індекс буде містити зміни D, тоді як робочий каталог буде містити зміни A - таким чином a git statusабо git diff(що порівнює Index [D] з Робочий каталог [A]) покаже речовину; що користувач повертається з D назад до A?
Червоний горох

39

Спочатку переконайтеся, що ваша робоча копія не змінена. Тоді:

git diff HEAD commit_sha_you_want_to_revert_to | git apply

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


1
Працювали для мене! Виникла проблема із гілкою функції застарілих змін, які були внесені в гілку розробки (деякі виправлення помилок), тому гілка функції повинна була перезаписати всі зміни, внесені в розробку (включаючи видалення деяких файлів).
мовчанець

1
Не працюватимуть з бінарними файлами:error: cannot apply binary patch to 'some/image.png' without full index line error: some/image.png: patch does not apply
GabLeRoux

2
Це набагато гнучкіше рішення, ніж прийнята відповідь. Дякую!
Брайан Кунг

2
re: бінарні файли використовують - двійковий варіант: git diff --binary HEAD commit_sha_you_want_to_revert_to | git apply
weinerk

2
Це буде спрацьовувати, навіть якщо ви хочете відновити діапазон комітетів, що містять об'єднання об'єднань. При використанні git revert A..Zви отримаєтеerror: commit X is a merge but no -m option was given.
Джуліуш Гонера

35

Я так засмучений, що на це питання не можна просто відповісти. Кожне інше питання стосується того, як правильно повернути та зберегти історію. Це питання говорить: "Я хочу, щоб керівник відділення вказував на A, тобто я хочу, щоб B, C, D і HEAD зникли, і я хочу, щоб голова була синонімом A."

git checkout <branch_name>
git reset --hard <commit Hash for A>
git push -f

Я багато чому навчився читати пост Якуба, але якийсь хлопець у компанії (з доступом до нашої «тестувальної» філії без Pull-Request) підштовхнув, як 5 поганих доручень, намагаючись виправити та виправити помилку, яку він зробив 5 фільмів тому. Мало того, але були прийняті один-два запити на виклик, які тепер були погані. Тож забудьте про це, я знайшов останню хорошу команду (abc1234) і просто запустив базовий сценарій:

git checkout testing
git reset --hard abc1234
git push -f

Я сказав іншим 5 хлопцям, які працюють в цьому репо, що вони краще помітять свої зміни за останні кілька годин, і протріть / повторно відділитись від останнього тестування. Кінець історії.


2
Я не публікував комітети, тому це була відповідь, яка мені була потрібна. Дякую, @Suamere.
Том Баррон

Кращий спосіб зробити це - git push --force-with-leaseпереписати історію лише в тому випадку, якщо ніхто більше не взяв на себе зобов'язання після або в межах діапазону зобов’язань щодо вапуризації. Якщо інші люди використовували цю гілку, то її історію ніколи не слід переписувати, а комітет повинен бути просто помітно скасований.
frandroid

1
@frandroid "історію ніколи не слід переписувати", лише sith розбирається в абсолютних. Питання цієї теми та суть моєї відповіді полягає саме в тому, що для конкретного сценарію всю історію слід витерти.
Суамер

@Suamere Звичайно, це питання. Але оскільки у вашій відповіді згадується про те, що вам довелося сказати іншим хлопцям, можливі проблеми. З особистого досвіду, push -f може зіпсувати вашу базу коду, якщо інші люди взяли на себе зобов’язання після того, що ви намагаєтесь стерти. - сила з орендою досягає того ж результату, за винятком того, що це економить вашу дупу, якщо ви збиралися зіпсувати репо. Навіщо ризикувати? Якщо - примусово з орендою виходить з ладу, ви можете бачити, який комітет перешкоджає, правильно оцінювати, коригувати та повторити спробу.
frandroid

1
@Suamere Дякую! Я погоджуюся, що питання чітко говорить про те, що він хоче переписати історію. Я перебуваю в тій самій ситуації, що і ви, і я здогадуюсь, що ОП у тому, що хтось зробив десятки потворних ревертів і непарних доручень та повернення ревертів випадково (поки я був у відпустці), і державу потрібно повернути. У будь-якому випадку, разом із хорошим здоровим попередженням, це має бути прийнятою відповіддю.
Лі Річардсон

9

Це розширення одного з рішень, наданих у відповіді Якуба

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

Спочатку перевірте вміст цільової комісії, залишаючи HEAD на кінчику гілки:

$ git checkout -f <target-commit> -- .

(Переконайтеся <target-commit>, що інтерпретується як комітка, а не як файл;. Посилається на поточний каталог.)

Потім визначте, які файли були додані до комітетів, які відмовляються, і таким чином їх потрібно видалити:

$ git diff --name-status --cached <target-commit>

Файли, які були додані, повинні відображатися з "A" на початку рядка, а інших відмінностей не повинно бути. Тепер, якщо якісь файли потрібно видалити, відправте ці файли на видалення:

$ git rm <filespec>[ <filespec> ...]

Нарешті, здійснити реверсію:

$ git commit -m 'revert to <target-commit>'

За бажанням переконайтеся, що ми повернулися до потрібного стану:

$git diff <target-commit> <current-commit>

Різниць не повинно бути.


Ви впевнені, що можете gitВИГОЛОВИТИ лише кінчиком гілки?
Суамер

2
Це було набагато кращим рішенням для мене, оскільки в шахті я мав об'єднати коміти.
сувеніт

3

Найпростіший спосіб відновити групу комітетів у спільному сховищі (якими користуються люди, а ви хочете зберегти історію) - це використовувати git revertспільно з git rev-list. Останній надасть вам список зобов’язань, перший зробить повернення самостійно.

Є два способи зробити це. Якщо ви хочете скасувати декілька комітетів в одному використанні:

for i in `git rev-list <first-commit-sha>^..<last-commit-sha>`; do git revert -n $i; done

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

Іншим варіантом є наявність одного коміту за скасовану зміну:

for i in `git rev-list <first-commit-sha>^..<last-commit-sha>`; do git revert --no-edit -s $i; done

Наприклад, якщо у вас є дерево комітів

 o---o---o---o---o---o--->    
fff eee ddd ccc bbb aaa

щоб повернути зміни від eee до bbb , запустіть

for i in `git rev-list eee^..bbb`; do git revert --no-edit -s $i; done

просто використав це. Дякую!
Ран Бірон

2

Ніхто з них не працював на мене, тому у мене було три комісії для відновлення (останні три зміни), тому я зробив:

git revert HEAD
git revert HEAD~2
git revert HEAD~4
git rebase -i HEAD~3 # pick, squash, squash

Працював як шарм :)


2
Це життєздатний варіант лише тоді, коли ваші зміни ще не натиснуті.
kboom

0

На мою думку, дуже легким і чистим способом може бути:

повернутися до А

git checkout -f A

вкажіть голову магістра на поточний стан

git symbolic-ref HEAD refs/heads/master

зберегти

git commit

1
Чи можете ви пояснити причину відмови?
nulll

Це чудово працює. Який сенс дати позитивну думку за цю корисну відповідь чи хтось може пояснити, що є найкращою практикою?
Левент Дівіліоглу

Це те саме, що git checkout master; git reset --hard A? Або якби не могли б ви пояснити трохи більше про те, що це робить?
ММ

просто працює, але символіко-іая голова видається не бути командою «безпечної»
SERGIO

еее я не хочу майстер виправити, я хочу виправити гілку
Серджіу

-6

Якщо ви хочете тимчасово повернути комітети функції, ви можете використовувати ряд наступних команд.

Ось як це працює

git log --pretty = oneline | grep 'ім'я функції' | вирізати -d '' -f1 | xargs -n1 git revert --no-edit

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