Отменить изменения в git (не переписывая историю) - PullRequest
49 голосов
/ 13 марта 2009

Я внес изменение в скрипт и зафиксировал его. Затем я сделал несколько других изменений и отправил их в удаленный репозиторий и все такое.

Затем я понял, что первое упомянутое мной изменение было глупым, и хочу отменить его. Могу ли я «отменить» этот коммит, без ручного копирования / вставки diff?

В качестве примера: у меня есть два файла, a.py и b.py:

Commit 1:
I delete a function in a.py

Commit 2:
I change a few lines in b.py

Commit 3:
I change the docstring in a.py

Могу ли я отменить удаление этой функции и сделать ее похожей на "commit 4" (вместо удаления commit 1)

Ответы [ 4 ]

60 голосов
/ 13 марта 2009

Да, для этого вы можете использовать git revert . См. руководство по git на этом для получения дополнительной информации.

Суть в том, что вы можете сказать:

git revert 4f4k2a

Где 4f4k2a - идентификатор коммита, который вы хотите отменить, и он попытается отменить его.

33 голосов
/ 13 марта 2009

Просто комментарий:

git revert aCommit

возвращает все коммит (как в « все файлы часть коммита»):
он вычисляет обратный патч, применяет его к HEAD и фиксирует.

Итак, две проблемы здесь (первая легко решаема):

  • он всегда фиксирует, так что вы можете добавить -no-commit параметр: "git revert --no-commit aCommit": это полезно при возврате эффекта более одного коммита в ваш индекс в строке.
  • это не относится к конкретному файлу (что, если вы a.py входили в коммит с 1000 другими изменениями, которые вы, возможно, не захотите отменить)?
    Для этого, если вы хотите извлечь определенные файлы, как они были в другом коммите, вы должны увидеть git-checkout, в частности синтаксис git checkout <commit> <filename> (который это не совсем то, что вам нужно в этом случае)

Easy Git (Элайджа Ньюрен) пытался внести более "полный возврат" в список рассылки Git ; но без особого успеха:

Люди иногда хотят «отменить изменения».

Теперь это может быть:

  • изменения между 32 и 29 ревизиями назад,
  • это могут быть все изменения с момента последнего коммита,
  • это могут быть изменения с 3 коммитов назад или
  • это может быть только один конкретный коммит.
  • Пользователь может захотеть поместить такие реверсии только в определенные файлы ,

(eg revert здесь задокументировано , но я не уверен, что это часть текущего распределения, например, хотя)

но все это сводится к "отмене изменений" в конце.

eg revert --since HEAD~3  # Undo all changes since HEAD~3
eg revert --in HEAD~8     # much like git revert HEAD~8, but nocommit by default
eg revert --since HEAD foo.py  # Undo changes to foo.py since last commit
eg revert foo.py               # Same as above
eg revert --in trial~7 bar.c baz.  # Undo changes made in trial~7 to bar.[ch]

Действительно ли эти виды «возврата данных» настолько различны, что должны быть разные команды, или что некоторые из этих операций не должны поддерживаться простой командой возврата?
Конечно, большинство пользователей большую часть времени, вероятно, будут использовать "eg revert FILE1 FILE2..." форма, но я не видел вреда в поддержке дополнительных возможностей.

Кроме того ... есть ли что-то фундаментальное, что удерживало бы ядро ​​от такого поведения?

Элайджа

Примечание: коммиты по умолчанию не имеют смысла для обобщенной команды revert, и "git revert REVISION" выдает ошибку с инструкциями (сообщающими пользователю добавить флаг --in).


Допустим, у вас есть из 50 подтвержденных 20 файлов, которые, как вы понимаете, в старой фиксации X были внесены изменения, которых не должно было быть.
Немного сантехники в порядке.
Что вам нужно, это способ перечислить все конкретные файлы, которые вам нужно вернуть
(как в «отменить изменения, сделанные в коммите X, сохранив все последующие изменения» ),
а затем для каждого из них:

git-merge-file -p a.py X X^

Проблема здесь в том, чтобы восстановить потерянную функцию без удаления всех последующих изменений в a.py, которые вы, возможно, захотите сохранить.
Эту технику иногда называют «негативным слиянием».

С git merge-file <current-file> <base-file> <other-file> означает :
включает все изменения, которые ведут от <base-file> к <other-file> в <current-file>, вы можете восстановить удаленную функцию, сказав, что хотите включить все изменения.)

  • из: X (где функция была удалена)
  • to: X ^ (предыдущий коммит до X, где функция все еще была там)

Примечание: аргумент '-p' , который позволяет вам сначала просмотреть изменения, ничего не делая с текущим файлом. Если вы уверены, удалите эту опцию.

Примечание : git merge-file это не так просто : вы не можете ссылаться на предыдущие версии файла просто так.
(у вас снова и снова появляется разочаровывающее сообщение: error: Could not stat X)
Вы должны:

git cat-file blob a.py > tmp/ori # current file before any modification
git cat-file blob HEAD~2:a.py > tmp/X # file with the function deleted
git cat-file blob HEAD~3:a.py > tmp/F # file with the function which was still there

git merge-file a.py tmp/X tmp/F # basically a RCS-style merge
                                 # note the inversed commit order: X as based, then F
                                 # that is why is is a "negative merge"
diff -u a.py tmp/ori # eyeball the merge result
git add a.py 
git commit -m "function restored" # and any other changes made from X are preserved!

Если это должно быть сделано для большого количества файлов в рамках предыдущего коммита ... некоторые сценарии в порядке;)

4 голосов
/ 14 марта 2009

Чтобы отменить изменения только в одном файле в коммите, как указывало VonC , я бы checkout выбрал ветку (master или trunk или что-то еще), а затем checkout версию файла Я хотел отменить и обработать его как новый коммит:

$ git checkout trunk
$ git checkout 4f4k2a^ a.py
$ git add a.py
$ git diff              #verify I'm only changing what I want; edit as needed
$ git commit -m 'recover function deleted from a.py in 4f4k2a'

Вероятно, есть сантехническая команда, которая сделает это напрямую, но я бы не стал ее использовать, если бы знал это. Дело не в том, что я не доверяю Git, но я не доверяю себе - я бы не стал доверять тому, что знал, не посмотрев, что изменилось в этом файле в этом коммите и с тех пор. И как только я посмотрю, проще создать новый коммит, отредактировав diff. Возможно, это просто личный стиль работы.

1 голос
/ 15 апреля 2012

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

...