git revert remote к конкретному коммиту со слиянием не работает с -m

Я пытаюсь вернуть наше удаленное репо к предыдущей фиксации. Дерево выглядит так, как показано ниже:

введите здесь описание изображения

Идея состоит в том, что мы хотим вернуться к этому коммиту, не требуя, чтобы кто-либо, вытащенный из основной ветки, имел дело с проблемами, которые возникнут при возврате к коммиту 2dda031. Поэтому вместо этого я использую git revert.

Я пытаюсь сделать это, используя git revert --no-commit 2dda031..HEAD

Однако я получаю эту ошибку:

error: commit d064f7c3b04a2bda30c43a32afac822c6af633c0 is a merge but no -m  option was given. 
fatal: revert failed

Это ожидается, так как d064f7c является слиянием (как и 47d4161). Итак, как было предложено здесь, я делаю:

git revert --abort
git revert --no-commit -m 1 2dda031..HEAD

Затем я получаю сообщение об ошибке:

error: mainline was specified but commit cb420e0 is not a merge.
fatal: revert failed

Так что я просто чувствую, что зацикливаюсь. Может ли кто-нибудь показать мне правильный способ вернуться к этой фиксации (при восстановлении истории)?


person GenAsis    schedule 11.10.2017    source источник


Ответы (1)


Изменить (исходный ответ ниже): позвольте мне начать с преобразования вашей графики в текст, без (надеюсь) опечаток или других серьезных ошибок. Это то, что у вас есть сейчас, как может показать git log --graph --oneline (хотя --graph --oneline может выбрать немного другой порядок коммитов — синие и зеленые линии, сгенерированные вашим графическим просмотрщиком, вероятно, отсортированы по дате коммита без учета топологии, а не по топологии первый):

* cb420e0  (master, ...) evert "Update README.md"
* 7a16df4  Update README.md
* 7564754  Update README.md
* 214cd47  Update README.md
* d064f7c  Merge pull request #6 from ...
|\
* | d936a24  Changing Run instructions
* | 2cbd7c2  Minor edits for Google Drive link
* | 1a3d871  Updated process documentation with google drive link
| * 0594132  (TrustM..., ...) Added some comments to various scripts.
| * 7e060c4  Updated the JSON dialogues and implemented the Trust mechanism
|/
* 4d7f49b  Configured script inputs and enabled mouse during pause screen.
* 47d4161  Merge pull request #5 from ...
|\
| * e999b3d  (origin/Trying...) Adjusted ray cast length to be more realistic.
| * 953e4c3  Fully functional dialogue system implemented.
* | 1f33079  updated wiki to reflect marking of prototype
| * 09e350b  Added in most of the Yarn framework
| * 2dda031  fixed heirarchy of files
| * bf667cc  Merge branch 'develop' of ...
| |\
| * |  79e068d  Character placement

(и мы не можем видеть ничего ниже этой точки, хотя очевидно, что коммитов должно быть намного больше).

Состояние коммита, к которому вы хотели вернуться, я думаю, 2dda031 fixed heirarchy of files.

Теперь самая сложная часть этого заключается в том, что это состояние «живет» в том, что, по-видимому, было побочной ветвью, под фиксацией «Merge pull request #5 from ...". Если в какой-то момент процесса отката вы запустите git revert -m <some-number> 47d4161, вы скажете Git сравнивать 47d4161 либо с его первым родителем, 1ff3079, либо со вторым родителем, d999b3d. Первый из этих различий показывает эффект от каждого коммита, начиная с базы слияния — какой бы коммит это ни был: мы не можем увидеть его из этого фрагмента графика; нам нужно больше графика, чтобы найти его, так как здесь он находится в нижней части «экрана» — к одному из этих двух, а другой diff показывает эффект каждой фиксации, начиная с базы слияния, до другой. Таким образом, возврат с помощью -m 1 в основном устраняет эффект:

* e999b3d  (origin/Trying...) Adjusted ray cast length to be more realistic.
* 953e4c3  Fully functional dialogue system implemented.
* 09e350b  Added in most of the Yarn framework
* 2dda031  fixed heirarchy of files
* bf667cc  Merge branch 'develop' of ...
...

при использовании -m 2 в основном снимается эффект:

* 1f33079  updated wiki to reflect marking of prototype
...

(в обоих случаях может быть гораздо больше коммитов, которые мы здесь не видим). Мне кажется довольно ясным, что вы не хотите этого делать, но я не знаю наверняка.

Однако обратите внимание, что если вы просто извлекаете содержимое коммита 2dda031, вы все еще теряете эффект 1f33079 и любых других коммитов, которые могут быть ниже него, потому что вы получаете состояние на момент нескольких коммитов. до origin/Trying....

С коммитом d064f7c все проще, потому что он просто объединяет d936a24 и 0594132 из базы слияния 4d7f49b. Если вы хотите отменить эффект коммитов 1a3d871+2cbd7c2+d936a24, вы можете git revert -m 1 d064f7c. Если вы хотите отменить действие коммитов 0594132+7e060c4, вы можете git revert -m 2 d064f7c. Но поскольку вы (предположительно) хотите отменить эффекты всех из них, проще просто отменить их все по отдельности, полностью пропустив слияние.

Поскольку вы, по-видимому, также хотите отменить cb420e0 обратно через 214cd47, вы просто отмените их по отдельности.

Если вы хотите сохранить эффект 1f33079 и более ранних коммитов, просто не отменяйте их. Если вы хотите отменить их эффект, вы можете git revert -m 2 47d4161 отменить их все сразу. Сомневаюсь, что ты этого хочешь, но, как и прежде, решать тебе.

Обратите внимание, что любой откат можно запустить с помощью -n (откат в индексе и рабочем дереве без фиксации), но как только вы начали серию операций -n, вы должны продолжить с -n и, в конце концов, зафиксировать, прежде чем вы сможете начать новый откат без -n.

Возможно, самый простой подход, в зависимости от желаемого результата, состоит в том, чтобы начать с извлечения содержимого коммита 47d4161 — состояния дерева настолько далеко, насколько это возможно без отдельных возвратов коммитов, — а затем используйте git revert -n для каждой дополнительной фиксации для возврата. Чтобы извлечь это содержимое, вы можете либо использовать git checkout <commit> -- . с его небольшим риском не удалять записи индекса и рабочего дерева для файлов, которые были новыми с тех пор, либо git read-tree --reset -u <commit>, чтобы избежать риска (см. примечания ниже, также в исходном ответе). Я бы пошел с последним, давая:

git read-tree --reset -u 47d4161
git revert -n e999b3d
git revert -n 953e4c3
git revert -n 09e350b

(при условии, что вы получите окончательное рабочее дерево и состояния индекса, которые вы хотели бы иметь, конечно).

(Исходный ответ ниже строки.)


Git's revert не возвращает в фиксацию (из-за чего команда плохо названа: она использует неправильный глагол). Что он делает, так это возврат (т. е. "возврат") одного конкретного коммита или, возможно, набора коммитов.1 Вы на полпути там с 2dda031..HEAD, поскольку этот синтаксис диапазона на самом деле означает HEAD ^2dda031, то есть набор всех коммитов, достижимых из HEAD, исключая (вычитая, с вычитанием набора) набор всех коммитов, достижимых из 2dda031.

Теперь здесь есть несколько проблем из-за слияний. Во-первых, исключение 2dda031 и его родителей не исключает другую часть слияния, поэтому вы будете отменять слишком много коммитов. Вторая проблема заключается в том, что, в некотором смысле, коммит слияния — это коммит, который делает, как однократное изменение, «все изменения вносятся побочной ветвью».2 Третий заключается в том, что для отмены коммита слияния вы должны указать, какую "сторону" рассматривать, но для отмены фиксации без слияния вы не должны указывать " боковая сторона".

Решение некоторых из этих проблем состоит в том, чтобы вообще избежать отмены слияния, а решение других — отменить только слияние, где это применимо. Но есть еще один, более простой способ, в зависимости от вашей реальной цели: если действительно нужно вернуться к коммиту, глагол Git для этого на самом деле будет git checkout, но есть несколько ловушек. См. этот ответ на связанный (возможно, даже дублирующий, в зависимости от вашей цели) вопрос. Причина git rm -r . состоит в том, чтобы удалить все файлы в текущем индексе, которые не будут извлечены на шаге git checkout <hash> -- ..

Вместо последовательности git rm -r . && git checkout <hash> -- . можно использовать сокращение, которое также не зависит от текущего рабочего каталога: вы можете запустить git read-tree --reset -u <hash>. Это отбрасывает текущее содержимое индекса (--reset) и заменяет его содержимым указанной фиксации (аргумент <hash>), а затем обновляет рабочее дерево для соответствия, удаляя все файлы, удаленные из индекса, и обновляя все файлы, обновленные в индекс.

Обратите внимание, что во всех случаях конечный результат находится в индексе и рабочем дереве, но еще не зафиксирован, поэтому вы должны запустить git commit.

Обратите внимание: если вашей целью на самом деле является не возврат к конкретному коммиту, а скорее возврат серии внесенных изменений, способ сделать это с помощью git revert -n — использовать как можно больше отдельных git revert команды по мере необходимости: по одной для каждого набора изменений, который необходимо отменить. Некоторые из них могут быть git reverts неслияниями, а некоторые могут быть git reverts слияниями. Тем не менее, см. сноску 2 и помните, какой бы метод вы ни использовали, отмена изменения, которое вы хотели сохранить, в конечном итоге будет успешным (изменение исчезнет), даже если вы хотели его сохранить.


1По этой причине в некоторых других системах контроля версий используется глагол "отступить".

2Это описание неверно в тонком, но очень важном смысле: слияние объединяет изменения. Входными данными для слияния являются два набора изменений: один из базового коммита слияния в коммит --ours, а другой — из базового коммита слияния в коммит --theirs. Эти два набора изменений могут перекрываться. Если это так, и если перекрытие «достаточно похоже» в какой-либо точке, Git берет только одну копию изменения. Если Git взял одну копию некоторого изменения ∆, где ∆ присутствует в обоих наборах изменений, и вы отменяете набор изменений --theirs, Git отменяет ∆ даже если оно было и в наборе изменений --ours. (То же самое рассуждение применимо, если вы отказываетесь от дельты --ours: Git не должен, но делает, забирать ее из версии, введенной как --theirs.)

person torek    schedule 11.10.2017
comment
Спасибо за исчерпывающий и отличный ответ :). Моя цель на самом деле состоит в том, чтобы отменить серию коммитов, и, как вы сказали, я думаю, что лучший способ — постепенно возвращать их все. Но когда я пытаюсь отменить коммиты слияния, я получаю несколько конфликтов, которые, к сожалению, не могу разрешить. Альтернативой может быть переименование другой ветки в master. Это приемлемая практика? Спасибо - person GenAsis; 11.10.2017
comment
Приемлема ли такая практика? Приемлемо то, с чем согласны вы и все остальные. В любом случае, еще одна вещь, заслуживающая внимания в отношении git-reverts (обычных коммитов или слияний), заключается в том, что они добавляют новые коммиты, но не изменяют существующую историю. Это означает, что отмена слияния не меняет того, как слияние (которое является частью истории) влияет на следующее слияние. А пока я попробую нарисовать что-нибудь еще здесь... - person torek; 11.10.2017