Отменить изменение в git (не переписывая историю)

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

Тогда я понял, что первое изменение, о котором я упоминал, было глупо, и хочу отменить его. Могу ли я "не использовать", который фиксирует, без ручной копирования/вставки 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" (а не удалять фиксацию 1)

Ответ 1

Да, вы можете использовать git для этого. Дополнительную информацию см. В разделе git в этом разделе.

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

git revert 4f4k2a

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

Ответ 2

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

git revert aCommit

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

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

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

Простой 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]

Являются ли эти виды "реверсивных данных" настолько разными, что должны быть разные команды или что некоторые из этих операций не должны поддерживаться простой командой revert?

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

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

Илия

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


Предположим, что у вас из 50 совершенных 20 файлов вы понимаете, что старый commit 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>, вы можете восстановить удаленную функцию, указав, что хотите включить все изменения.)

  • from: 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!

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

Ответ 3

Чтобы вернуть изменения только к одному файлу в коммите, как отметил VonC, я бы checkout филиал (мастер или магистраль или что-то еще), а затем 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. Возможно, это просто личный стиль работы.

Ответ 4

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