Удалить конкретный коммит - PullRequest
236 голосов
/ 30 мая 2010

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

Итак, вот упрощенная версия вопроса:

Учитывая следующий сценарий, как мне удалить коммит 2?

$ mkdir git_revert_test && cd git_revert_test

$ git init
Initialized empty Git repository in /Users/josh/deleteme/git_revert_test/.git/

$ echo "line 1" > myfile

$ git add -A

$ git commit -m "commit 1"
[master (root-commit) 8230fa3] commit 1
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 myfile

$ echo "line 2" >> myfile

$ git commit -am "commit 2"
[master 342f9bb] commit 2
 1 files changed, 1 insertions(+), 0 deletions(-)

$ echo "line 3" >> myfile

$ git commit -am "commit 3"
[master 1bcb872] commit 3
 1 files changed, 1 insertions(+), 0 deletions(-)

Ожидаемый результат:

$ cat myfile
line 1
line 3

Вот пример того, как я пытался вернуться

$ git revert 342f9bb
Automatic revert failed.  After resolving the conflicts,
mark the corrected paths with 'git add <paths>' or 'git rm <paths>'
and commit the result.

Ответы [ 9 ]

238 голосов
/ 16 августа 2012

Есть четыре способа сделать это:

  • Чистый путь, возвращаясь, но сохраняйте в журнале возврат:

    git revert --strategy resolve <commit>
    
  • Жесткий путь, удалите всего только последний коммит:

    git reset --soft "HEAD^"
    

Примечание: избегайте git reset --hard, так как это также отменит все изменения в файлах с момента последнего коммита. Если --soft не работает, попробуйте --mixed или --keep.

  • Перебазировать (показать журнал последних 5 коммитов и удалить ненужные строки, или изменить порядок, или раздавить несколько коммитов в одном, или сделать что-то еще, что вы хотите, это очень универсальный инструмент) :

    git rebase -i HEAD~5
    

А если допущена ошибка:

git rebase --abort
  • Быстрая перебазировка: удалить только определенный коммит, используя его идентификатор:

    git rebase --onto commit-id^ commit-id
    
  • Альтернативы: вы также можете попробовать:

    git cherry-pick commit-id
    
  • Еще одна альтернатива:

    git revert --no-commit
    
  • В крайнем случае, если вам нужна полная свобода редактирования истории (например, потому что git не позволяет вам редактировать то, что вы хотите), вы можете использовать это очень быстро открыть исходное приложение: reposurgeon .

Примечание: конечно, все эти изменения выполняются локально, затем вы должны git push применить эти изменения к пульту. И в случае, если ваше хранилище не хочет удалять фиксацию («ускоренная пересылка не разрешена», что происходит, когда вы хотите удалить коммит, который вы уже нажали), вы можете использовать git push -f, чтобы принудительно отправить изменения.

Примечание2: если вы работаете с веткой и вам нужно принудительно нажать, вам следует избегать git push --force, поскольку это может перезаписать другие ветви (если вы внесли изменения в них, даже если ваша текущая проверка находится в другой ветви). Предпочитайте всегда указывать удаленную ветвь при принудительном нажатии : git push --force origin your_branch.

77 голосов
/ 02 декабря 2016

Очень простой способ

git rebase -i HEAD ~ x

(х = количество коммитов)

при выполнении файла блокнота откроется «пут», кроме вашего коммита

и все готово ... просто синхронизируйте панель управления git, и изменения будут перенесены на удаленный компьютер. Если зафиксированный вами коммит уже был на удаленном компьютере, вам придется принудительно выполнить обновление. Поскольку --force считается вредным , используйте git push --force-with-lease.

56 голосов
/ 08 июля 2010

Алгоритм, который Git использует при вычислении различий, которые необходимо вернуть, требует, чтобы

  1. возвращаемые строки не изменяются никакими последующими коммитами.
  2. что позже в истории не будет других "смежных" коммитов.

Определение «смежных» основано на количестве строк по умолчанию из контекста diff, которое равно 3. Так что если «myfile» был построен следующим образом:

$ cat >myfile <<EOF
line 1
junk
junk
junk
junk
line 2
junk
junk
junk
junk
line 3
EOF
$ git add myfile
$ git commit -m "initial check-in"
 1 files changed, 11 insertions(+), 0 deletions(-)
 create mode 100644 myfile

$ perl -p -i -e 's/line 2/this is the second line/;' myfile
$ git commit -am "changed line 2 to second line"
[master d6cbb19] changed line 2
 1 files changed, 1 insertions(+), 1 deletions(-)

$ perl -p -i -e 's/line 3/this is the third line/;' myfile
$ git commit -am "changed line 3 to third line"
[master dd054fe] changed line 3
 1 files changed, 1 insertions(+), 1 deletions(-)

$ git revert d6cbb19
Finished one revert.
[master 2db5c47] Revert "changed line 2"
 1 files changed, 1 insertions(+), 1 deletions(-)

Тогда все работает как положено.

Второй ответ был очень интересным. Существует функция, которая еще не была официально выпущена (хотя она доступна в Git v1.7.2-rc2), которая называется Revert Strategy. Вы можете вызвать git так:

git revert - разрешение стратегии

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

36 голосов
/ 30 мая 2010

Вы можете выбрать между

  1. сохранением ошибки и внесением исправления и
  2. удалением ошибки и изменением истории.

Вы должны выбрать (1), если ошибочное изменение было обнаружено кем-либо еще, и (2), если ошибка ограничена частной ветвью без отправления.

Git revert - это автоматический инструмент для выполнения (1), он создает новый коммит, отменяя некоторый предыдущий коммит.Вы увидите ошибки и удаление в истории проекта, но люди, которые извлекают данные из вашего репозитория, не столкнутся с проблемами при обновлении.В вашем примере это не работает автоматически, поэтому вам нужно отредактировать 'myfile' (чтобы удалить строку 2), выполните git add myfile и git commit для разрешения конфликта.После этого вы получите четыре коммита в своей истории с коммитом 4, возвращающим коммит 2.

Если никому нет дела до изменения вашей истории, вы можете переписать его и удалить коммит 2 (вариант 2).Самый простой способ сделать это - использовать git rebase -i 8230fa3.Это приведет вас к редактору, и вы можете отказаться от ошибочной фиксации, удалив фиксацию (и оставив «pick» рядом с другими сообщениями фиксации. Читайте о последствиях этого .

21 голосов
/ 05 сентября 2017

Приблизительно 1

Сначала получите хеш коммита (например: 1406cd61), который вам нужно отменить. простое исправление будет ниже команды,

$ git revert 1406cd61

если вы зафиксировали больше изменений, связанных с файлами 1406cd61 после фиксации 1406cd61, простая команда выше не будет работать. Затем вы должны выполнить следующие шаги: сбор вишни.

Приблизительно 2

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

Шаг 1: Найти коммит до коммита, который вы хотите удалить git log

Шаг 2: Оформить заказ, который совершит git checkout <commit hash>

Шаг 3: Создайте новую ветку, используя текущую фиксацию проверки git checkout -b <new branch>

Шаг 4: Теперь вам нужно добавить коммит после удаленного коммита git cherry-pick <commit hash>

Шаг 5: Теперь повторите Шаг 4 для всех остальных коммитов, которые вы хотите сохранить.

Шаг 6: Как только все коммиты были добавлены в вашу новую ветку и были зафиксированы. Убедитесь, что все в правильном состоянии и работает как задумано. Дважды проверьте, что все было передано: git status

Шаг 7: Переключиться на сломанную ветку git checkout <broken branch>

Шаг 8: Теперь выполните полную перезагрузку прерванной ветки для коммита до того, который вы хотите удалить git reset --hard <commit hash>

Шаг 9: Слияние вашей фиксированной ветви с этой веткой git merge <branch name>

Шаг 10: Верните объединенные изменения в исходное положение. ВНИМАНИЕ: Это перезапишет удаленный репозиторий! git push --force origin <branch name>

Вы можете выполнить этот процесс, не создавая новую ветку, заменив Шаг 2 и 3 на Шаг 8, затем не выполнив Шаг 7 и 9.

17 голосов
/ 24 января 2014

Вы можете удалить нежелательные коммиты с помощью git rebase. Скажем, вы включили некоторые коммиты из ветки темы коллеги в ветку темы, но позже решите, что вам не нужны эти коммиты.

git checkout -b tmp-branch my-topic-branch  # Use a temporary branch to be safe.
git rebase -i master  # Interactively rebase against master branch.

В этот момент ваш текстовый редактор откроет интерактивное представление перебазирования. Например

git-rebase-todo

  1. Удалите коммиты, которые вам не нужны, удалив их строки
  2. Сохранить и выйти

Если перебазировка не удалась, удалите временную ветку и попробуйте другую стратегию. В противном случае продолжайте выполнять следующие инструкции.

git checkout my-topic-branch
git reset --hard tmp-branch  # Overwrite your topic branch with the temp branch.
git branch -d tmp-branch  # Delete the temporary branch.

Если вы отправляете ветку темы на удаленный компьютер, вам может потребоваться принудительное нажатие, так как история изменений изменилась. Если другие работают над той же веткой, дайте им голову.

7 голосов
/ 15 марта 2015

Из других ответов, приведенных здесь, я был немного озадачен тем, как git rebase -i можно использовать для удаления коммита, поэтому я надеюсь, что можно записать мой тестовый пример здесь (очень похоже на OP).

Вот скрипт bash, который можно вставить для создания тестового репозитория в папке /tmp:

set -x

rm -rf /tmp/myrepo*
cd /tmp

mkdir myrepo_git
cd myrepo_git
git init
git config user.name me
git config user.email me@myself.com

mkdir folder
echo aaaa >> folder/file.txt
git add folder/file.txt
git commit -m "1st git commit"

echo bbbb >> folder/file.txt
git add folder/file.txt
git commit -m "2nd git commit"

echo cccc >> folder/file.txt
git add folder/file.txt
git commit -m "3rd git commit"

echo dddd >> folder/file.txt
git add folder/file.txt
git commit -m "4th git commit"

echo eeee >> folder/file.txt
git add folder/file.txt
git commit -m "5th git commit"

На данный момент у нас есть file.txt с таким содержанием:

aaaa
bbbb
cccc
dddd
eeee

В этот момент HEAD находится на 5-м коммите, HEAD ~ 1 будет 4-м - и HEAD ~ 4 будет 1-м коммитом (поэтому HEAD ~ 5 не будет существовать). Допустим, мы хотим удалить 3-й коммит - мы можем выполнить эту команду в каталоге myrepo_git:

git rebase -i HEAD~4

( Обратите внимание, что git rebase -i HEAD~5 приводит к "фатальному: требуется одна ревизия; недопустимый HEAD ~ 5". ) Текстовый редактор (см. Скриншот в @ Dennis 'answer ) откроется с таким содержанием:

pick 5978582 2nd git commit
pick 448c212 3rd git commit
pick b50213c 4th git commit
pick a9c8fa1 5th git commit

# Rebase b916e7f..a9c8fa1 onto b916e7f
# ...

Таким образом, мы получаем все коммиты , начиная с (но , не включая ) нашего запрашиваемого HEAD ~ 4. Удалить строку pick 448c212 3rd git commit и сохранить файл; Вы получите этот ответ от git rebase:

error: could not apply b50213c... 4th git commit

When you have resolved this problem run "git rebase --continue".
If you would prefer to skip this patch, instead run "git rebase --skip".
To check out the original branch and stop rebasing run "git rebase --abort".
Could not apply b50213c... 4th git commit

В этот момент откройте myrepo_git / folder/file.txt в текстовом редакторе; вы увидите, что оно было изменено:

aaaa
bbbb
<<<<<<< HEAD
=======
cccc
dddd
>>>>>>> b50213c... 4th git commit

По сути, git видит, что когда HEAD получил 2-й коммит, было содержимое aaaa + bbbb; а затем к нему добавляется патч cccc + dddd, который он не знает, как добавить к существующему контенту.

Так что здесь git не может решить за вас - именно вы должны принять решение: удаляя 3-й коммит, вы либо сохраняете внесенные им изменения (здесь, строка * 1048) *) - или нет. Если вы этого не сделаете, просто удалите лишние строки - включая cccc - в folder/file.txt с помощью текстового редактора, чтобы оно выглядело так:

aaaa
bbbb
dddd

... и затем сохраните folder/file.txt. Теперь вы можете выполнить следующие команды в каталоге myrepo_git:

$ nano folder/file.txt  # text editor - edit, save
$ git rebase --continue
folder/file.txt: needs merge
You must edit all merge conflicts and then
mark them as resolved using git add

Ах - так, чтобы отметить, что мы решили конфликт, мы должны git add folder/file.txt, прежде чем делать git rebase --continue:

$ git add folder/file.txt
$ git rebase --continue

Здесь снова открывается текстовый редактор, показывающий строку 4th git commit - здесь у нас есть возможность изменить сообщение фиксации (которое в этом случае может быть существенно изменено на 4th (and removed 3rd) commit или подобное). Допустим, вы не хотите - просто выйдите из текстового редактора без сохранения; как только вы это сделаете, вы получите:

$ git rebase --continue
[detached HEAD b8275fc] 4th git commit
 1 file changed, 1 insertion(+)
Successfully rebased and updated refs/heads/master.

Теперь у вас есть такая история (которую вы также можете проверить, скажем, gitk . или другими инструментами) содержимого folder/file.txt (с, по-видимому, неизменными временными метками исходных коммитов):

1st git commit  |  +aaaa
----------------------------------------------
2nd git commit  |   aaaa
                |  +bbbb
----------------------------------------------
4th git commit  |   aaaa
                |   bbbb
                |  +dddd
----------------------------------------------
5th git commit  |   aaaa
                |   bbbb
                |   dddd
                |  +eeee

И если ранее мы решили оставить строку cccc (содержимое 3-го git коммита, которую мы удалили), у нас было бы:

1st git commit  |  +aaaa
----------------------------------------------
2nd git commit  |   aaaa
                |  +bbbb
----------------------------------------------
4th git commit  |   aaaa
                |   bbbb
                |  +cccc
                |  +dddd
----------------------------------------------
5th git commit  |   aaaa
                |   bbbb
                |   cccc
                |   dddd
                |  +eeee

Что ж, я надеялся, что это было именно то чтение, чтобы начать гадать, как работает git rebase с точки зрения удаления коммитов / ревизий; так что надеюсь, что это может помочь и другим ...

2 голосов
/ 30 мая 2010

Похоже, что в какой-то момент плохой коммит был включен в коммит слияния. Твой коммит слияния уже отменен? Если да, то вы захотите использовать git revert; вам придется стиснуть зубы и работать через конфликты. Если нет, то вы могли бы либо перебазировать, либо вернуть, но вы можете сделать это до коммита слияния, а затем повторить слияние.

На самом деле мы не можем оказать вам большую помощь в первом случае. После попытки откатить и обнаружить, что автоматический сбой, вы должны изучить конфликты и исправить их соответствующим образом. Это точно такой же процесс, как и исправление конфликтов слияния; вы можете использовать git status, чтобы увидеть, где находятся конфликты, отредактировать необработанные файлы, найти конфликтующие фрагменты, выяснить, как их разрешить, добавить конфликтующие файлы и, наконец, зафиксировать. Если вы используете git commit отдельно (без -m <message>), сообщение, которое появляется в вашем редакторе, должно быть шаблоном сообщения, созданным git revert; Вы можете добавить заметку о том, как вы исправили конфликты, затем сохранить и выйти для фиксации.

Во втором случае, устраняющем проблему до вашего слияния, есть два подслоя, в зависимости от того, проделали ли вы больше работы после слияния. Если вы этого не сделали, вы можете просто git reset --hard HEAD^, чтобы сбросить слияние, сделать возврат, а затем повторить слияние. Но я думаю, у вас есть. Итак, в итоге вы сделаете что-то вроде этого:

  • создайте временную ветку перед слиянием и проверьте ее
  • сделать возврат (или использовать git rebase -i <something before the bad commit> <temporary branch> для удаления неверного коммита)
  • повторить слияние
  • вернуть вашу последующую работу обратно: git rebase --onto <temporary branch> <old merge commit> <real branch>
  • удалить временную ветку
1 голос
/ 09 декабря 2014

Итак, вы выполнили некоторую работу и выдвинули ее, давайте назовем их коммитами A и B. Ваш коллега также выполнил некоторую работу, коммиты C и D. Вы объединили работу ваших коллег с вашей (слияние коммит E), затем продолжили работу, совершил это тоже (commit F) и обнаружил, что ваш коллега изменил некоторые вещи, которые он не должен был делать.

Итак, ваша история коммитов выглядит так:

A -- B -- C -- D -- D' -- E -- F

Вы действительно хотите избавиться от C, D и D '. Так как вы говорите, что объединили свои коллеги с вашими, эти коммиты уже "там", поэтому удаление коммитов с помощью, например, git rebase - это нет-нет. Поверьте мне, я пытался.

Теперь я вижу два выхода:

  • если вы еще не выдавили E и F своему коллеге или кому-либо еще (как правило, вашему серверу-источнику), вы все же можете на время удалить их из истории. Это ваша работа, которую вы хотите сохранить. Это можно сделать с помощью

    git reset D'
    

    (замените D 'фактическим хешем коммита, который вы можете получить из git log

    В этот момент коммиты E и F пропали, и изменения снова являются незафиксированными изменениями в вашей локальной рабочей области. В этот момент я бы переместил их в ветку или превратил в патч и сохранил бы их на потом. Теперь верните работу вашего коллеги либо автоматически с помощью git revert, либо вручную. Когда вы это сделаете, переиграйте свою работу поверх этого. У вас могут быть конфликты слияния, но по крайней мере они будут в коде , который вы написали, вместо кода вашего коллеги.

  • Если вы уже выполнили работу, выполненную после коммитов вашего коллеги, вы все равно можете попытаться получить «обратный патч» либо вручную, либо с помощью git revert, но поскольку ваша работа «в пути» так сказать, вы, вероятно, получите больше конфликтов слияния и более запутанных. Похоже, это то, чем ты оказался ...

...