Удалить конкретный коммит

287

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

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

Учитывая следующий сценарий, как мне удалить коммит 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.
Джошуа Чик
источник
5
Если кто-то обнаружит это во время поиска той же проблемы, вот что я в итоге сделал: скопируйте и вставьте. Шутки в сторону. Потратил 6+ часов, пытаясь заставить предложенные решения работать, но безрезультатно. В конце концов, я был вне времени, вытащил оригинал и просто скопировал / вставил около 20 файлов. Прошло менее 5 минут, и с тех пор все было хорошо (даже когда эти файлы объединялись с изменениями в других ветвях, которые произошли до этого фиаско). Я предлагаю вам также принять этот подход. Это не только самое простое, я также подозреваю, что это единственное, что работает.
Джошуа Чик
Я столкнулся с аналогичной проблемой, но, возможно, более сложной: у меня была ветка с сотнями коммитов, которые я хотел раздавить. К сожалению, коммиты из другой ветки были объединены обратно в ветку в промежуточной точке, поэтому для того, чтобы я мог раздавить, потребовалось «слияние». Я пошел по тому же пути, как предложено в tk ниже (выбор вишни + использование обозначения диапазона), но в некоторых других файлах возникли конфликты. В конце концов, копирование и вставка + несколько ручных изменений стали самым простым и предсказуемым способом продвижения вперед. Определенно стоит задуматься, если вы обнаружите, что тратите на это слишком много времени.
Федерико

Ответы:

73

Алгоритм, который 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 - стратегия разрешения <commit>

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

Хэл Эйзен
источник
1
perl -pполезен для написания очень коротких (в одну строку) программ, которые зацикливаются и передают свои данные на выход, подобно sed. perl -iиспользуется для редактирования файлов на месте . perl -eкак отправить код для оценки .
Хэл Эйзен
281

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

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

    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.

gaborous
источник
Основываясь на предложениях @gaborous: выполните "git rebase -i HEAD ~ 2". Теперь у вас есть несколько вариантов. В vim вы можете увидеть некоторые закомментированные строки: одна из них говорит вам, что вы можете просто удалить строку (которая должна быть коммитом, от которого вы хотите избавиться), и этот коммит будет удален вместе с его журналом в вашей истории.
Ilker Cat
git revert --strategy resol <commit>. Эта команда сработала для меня. спасибо :)
Swathin
2
git rebase -i HEAD~5работал на меня. Затем я просто удалил коммиты, которые мне не нужны, и смог решить проблему менее чем за 30 секунд. Спасибо.
17
119

Вот простое решение:

git rebase -i HEAD~x

(Примечание: xэто количество коммитов)

после выполнения файл блокнота откроется. Введите dropпомимо вашего коммита.
Если вы не знаете Vim, просто нажмите на каждый выбор слов, который вы хотите отредактировать, а затем нажмите клавишу «I» (для режима вставки). Когда вы закончите ввод, нажмите клавишу «esc», чтобы выйти из режима вставки.



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

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

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

JD-V
источник
Есть ли хороший способ узнать, каким должен быть «x», если вы знаете только хэш коммита, о котором идет речь?
jlewkovich
1
Для людей cpp-менталитета: x (количество коммитов) включительно. например, HEAD ~ 4 включает в себя последние 4 коммита.
герпеса Инженер
идеальное предложение. просто хочу добавить, это также отбросить изменения из пропущенного коммита.
Celerno
попытался отменить 3 коммита: git rebase -i HEAD-3 получил фатальную ошибку: требуется единственная недействительная редакция в восходящем направлении 'HEAD-3'
Устин
1
@ Устин, это ~ 3, а не -3
JD-V
37

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

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

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

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

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

Эндрю Уокер
источник
Rebase может быть сложно, так как это звучит как слияние.
Каскабель
3
git rebase -i 8230fa3 и удаление строки коммита отлично работали для меня с моими локальными изменениями. Спасибо!
Самуил
27

Подход 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.

tk_
источник
1
Первый подход работал как шарм. Он включает в себя коммит, указывающий, что желаемый коммит был отменен, что очень удобно для целей отслеживания.
suarsenegger
18

Вы можете удалить нежелательные коммиты с 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.

На этом этапе ваш текстовый редактор откроет интерактивное представление 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.

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

Деннис
источник
Не могли бы вы привести пример «попробовать другую стратегию»?
Пфабри
@pfabri, например, вы можете выбрать два диапазона коммитов cherry, где вы пропустите неверный коммит. Вы можете отменить плохой коммит. Вы можете избежать использования git-решения, даже отменив изменения вручную или начав со свежей ветки без неудачной фиксации и вручную вернув хорошие изменения. Если плохая фиксация содержит конфиденциальные данные, вам потребуется более осторожная стратегия: help.github.com/en/articles/…
Деннис
8

Из других ответов, приведенных здесь, я был немного озадачен тем, как 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результате получается «fatal: необходима одна ревизия; недопустимый верхний колонтитул ~ 5». ) Откроется текстовый редактор (см. Скриншот в ответе @Dennis ) со следующим содержимым:

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-й коммит, вы либо сохраняете внесенные им изменения (здесь, строка cccc) - либо нет. Если вы этого не сделаете, просто удалите лишние строки - в том числе 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 addfolder/file.txtgit 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работает с точки зрения удаления коммитов / ревизий; так что надеюсь, что это может помочь и другим ...

sdaau
источник
Какой бесценный обход - точный вариант использования, с которым я боролся, это очень помогло мне.
Пфабри
2

Похоже, что в какой-то момент плохой коммит был включен в коммит слияния. Ваш коммит слияния уже отменен? Если да, то вы захотите использовать 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>
  • удалить временную ветку
Cascabel
источник
1

Итак, вы проделали некоторую работу и выдвинули ее, давайте назовем их коммитами A и B. Ваш коллега также выполнил некоторую работу, коммиты C и D. Вы объединили работу ваших коллег с вашей (слияние коммит E), затем продолжили работу, зафиксировали это, тоже (коммит 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, но поскольку ваша работа «в пути», так сказать, вы, вероятно, получите больше конфликтов слияния и более запутанных. Похоже, это то, что вы оказались в ...

Frans
источник
0

git revert - стратегия принятия, если коммит является слиянием: используйте git revert --strategy resol -m 1

ShaDow RiDeR
источник