Могу ли я удалить коммит git, но сохранить изменения

1060

В одной из моих веток разработки я внес некоторые изменения в свою кодовую базу. Прежде чем я смог завершить функции, над которыми работал, мне пришлось переключить текущую ветку на master, чтобы продемонстрировать некоторые функции. Но просто использование «мастера git checkout» сохранило изменения, которые я также сделал в своей ветке разработки, тем самым нарушив некоторые функции в master. Так что я сделал фиксацию изменений в моей ветке разработки с сообщением фиксации «временная фиксация», а затем извлек мастер для демонстрации.

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

tanookiben
источник
65
В следующий раз:git stash
Мэтт Болл
51
@MattBall, не обязательно. Хотя git stashэто хороший инструмент, одноразовые коммиты «в процессе работы» также являются вполне законным устройством.
kostix
3
Это отличный ресурс от Github: как отменить (почти) что-нибудь с помощью Git
jasonleonhard
9
@MattBall @kostix Да, stash особенно плохо подходит для «длительного хранения», учитывая, что это один глобальный стек для всего репо. Я хочу иметь возможность сохранять изменения в ветке, а затем переходить в другие ветки, делать все остальное, возвращаться через несколько дней, не беспокоясь о том, что я мог использовать git stashкакую-то другую ветку в промежуточный период.
Алек
4
Стоит отметить, stashчто он полностью локальный и будет подвержен потере кода в результате удаления, повторного удаления или восстановления, а также аппаратного сбоя или потери. ИМО, это действительно следует использовать только для очень краткосрочного WIP. Любите коммит WIP перед выходом в отпуск: P .. назовите это коммитом свалки мозгов!
ϹοδεMεδιϲ

Ответы:

1624

Это так просто, как это:

git reset HEAD^

Примечание: некоторые оболочки обрабатываются ^как специальный символ (например, некоторые оболочки Windows или ZSH с включенным глобованием ), поэтому вам может потребоваться процитировать"HEAD^" в этих случаях .

git resetбез --hardили --softперемещает ваш HEADуказатель на указанный коммит, не изменяя никаких файлов.HEAD^ссылается на (первый) родительский коммит вашего текущего коммита, который в вашем случае является коммитом перед временным.

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

git commit --amend [-m … etc]

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

Обратите внимание, что это (как почти с каждым git-ответом) может вызвать проблемы, если вы уже отправили неверный коммит в место, откуда кто-то другой мог его вытащить. Старайтесь избегать этого

Gareth
источник
47
Это, безусловно, самое простое, но если вы уже отправили свой коммит на удаленный компьютер, а кто-то другой вытащил его, я бы очень колебался делать что-либо, кроме извинений.
atw13
11
@sicophrenic, не упустите возможность прочитать «Сбросить демистификацию» по этому поводу.
kostix
33
Я получаю More?после этого. Все, что я fatal: ambiguous argument 'HEADwhateverItypedIn': unknown revision or path not in the working tree.
наберу
50
@DaAwesomeP звучит так, как будто вы используете оболочку, которая обрабатывается ^как специальный символ. Вы можете либо цитировать ссылку "HEAD^", либо использовать альтернативный синтаксис без HEAD~1кавычек
Gareth
8
Работал на меня, должен был уйти от характера, хотяgit reset HEAD\^
cevaris
189

Есть два способа справиться с этим. Что проще, зависит от вашей ситуации

Сброс

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

git reset HEAD^

Возвращает вашу ветку к коммиту как раз перед вашим текущим HEAD. Однако на самом деле это не меняет файлы в вашем рабочем дереве. В результате изменения, внесенные в этот коммит, отображаются как измененные - это похоже на команду «uncommit». На самом деле, у меня есть псевдоним, чтобы сделать это.

git config --global alias.uncommit 'reset HEAD^'

Затем вы можете просто использовать git uncommitв будущем для резервного копирования одного коммита.

давя

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

git rebase -i <ref>

Я говорю выше, потому что хочу прояснить, что это может быть любое количество коммитов назад. Запустите git logи найдите коммит, от которого вы хотите избавиться, скопируйте его SHA1 и используйте его вместо <ref>. Git переведет вас в интерактивный режим перебазирования. Он покажет все коммиты между вашим текущим состоянием и тем, что вы положили на место <ref>. Так что если <ref>10 коммитов назад, он покажет вам все 10 коммитов.

Перед каждым коммитом будет слово pick. Найдите коммит, от которого вы хотите избавиться, и измените его pickна fixupили squash. Использование fixupпросто сбрасывает, что фиксирует сообщение и объединяет изменения в его непосредственного предшественника в списке. squashКлючевое слово делает то же самое, но позволяет редактировать сообщение фиксации на вновь сочетании фиксации.

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

ПРЕДУПРЕЖДЕНИЕ:

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

припрятать

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

git stash save 'some message'

Это сохранит ваши текущие изменения в списке в вашем тайнике. Выше приведена наиболее явная версия команды stash, в которой можно оставить комментарий, чтобы описать, что вы храните. Вы также можете просто запустить git stashи ничего больше, но сообщение не будет сохранено.

Вы можете просмотреть свой список тайников с ...

git stash list

Это покажет вам все ваши тайники, на каких ветвях они были сделаны, а также сообщение и в начале каждой строки, а также идентификатор этого тайника, который выглядит следующим образом, stash@{#}где # - его позиция в массиве тайников.

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

git stash apply stash@{#}

Опять же, # это позиция в массиве тайников. Если тайник, который вы хотите восстановить, находится в 0положении - то есть, если это был последний тайник. Затем вы можете просто запустить команду, не указывая положение тайника, git будет предполагать, что вы имеете в виду последнее:git stash apply .

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

git stash
git checkout <correct_branch>
git stash apply

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

Надеюсь это поможет.

eddiemoya
источник
6
git config --global alias.uncommit reset HEAD^просто псевдонимы uncommit для сброса. Вместо этого делайgit config --global alias.uncommit 'reset HEAD^'
мернст
3
Обратите внимание, что вам придется использовать ^^ вместо ^ при использовании в командной строке Windows.
Прамод БР
106

Я думаю, что вы ищете это

git reset --soft HEAD~1

Он отменяет самый последний коммит, в то же время сохраняя изменения, внесенные в этот коммит, на стадии подготовки.

Судханшу Джайн
источник
7
Спасибо. Это сработало для меня. При вызове git reset HEAD^Windows просто появляется сообщение «Еще?» - что бы это ни значило
Тайрон
12
@Tyron ^- экранирующий символ в DOS. При сопряжении с новой строкой он служит подсказкой продолжения для предыдущей команды. Набор текста git reset HEAD^^должен работать на Windows.
TRK
40

Да, вы можете удалить свой коммит, не удаляя изменения: git reset @ ~

DURGESH
источник
7
Это действительно то, чего я хочу, и это будет принятый ответ, на мой взгляд, большое спасибо!
Карлос Лю
2
Интересный, компактный синтаксис, я не видел и не использовал его раньше. Чем это отличается от git reset --softили git reset --keep?
user776686
18

Вы ищете git reset HEAD^ --softили git reset HEAD^ --mixed.

Есть три режима для команды сброса, как указано в документации :

git reset HEAD^ --soft

отменить git commit. Изменения все еще существуют в рабочем дереве (папка проекта) + индекс (--cached)

git reset HEAD^ --mixed

отменить git commit+ git add. Изменения все еще существуют в рабочем дереве

git reset HEAD^ --hard

Как будто вы никогда не вносили эти изменения в кодовую базу. Изменения ушли из рабочего дерева.

Бар Horing
источник
9

Для тех, кто использует zsh, вам придется использовать следующее:

git reset --soft HEAD\^

Объясняется здесь: https://github.com/robbyrussell/oh-my-zsh/issues/449

В случае, если URL становится мертвым, важная часть:

Побег ^ в вашей команде

В качестве альтернативы вы можете использовать HEAD ~, чтобы вам не приходилось каждый раз избегать его.

Грег Хилстон
источник
15
Я никогда не могу вспомнить эту команду, и мне приходится гуглить это и найти свой ответ, хахахаха
Грег Хилстон
2
git reset HEAD^работает в Zsh для меня, возможно, было исправлено.
Бен Коля
@BenKolyaMansley Какую версию zsh вы используете?
Грег Хилстон
Я используюzsh 5.3 (x86_64-apple-darwin18.0)
Бен Коля
7

В моем случае я уже подтолкнул к репо. Ой!

Вы можете отменить определенный коммит, сохранив изменения в локальных файлах, выполнив:

git revert -n <sha>

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

Вим Фейен
источник
В моем случае мне нужно было вернуть что-то, что я уже отправил в удаленный репозиторий. Еще больше ой! Лучший способ был, чтобы git revert bad-commit-shaпотом git revert -n revert-commit-just-created-shaотремонтировать оттуда. Ты получил меня на полпути. Спасибо!
TinkerTenorSoftwareGuy
Это похоже на обратное, нет? Он создает новые изменения, которые соответствуют отмене изменений, сделанных в выбранном коммите. Если вы будете совершать эти новые изменения, вы отмените работу, которую хотели сохранить.
svaens
3

Использование git 2.9 (точно 2.9.2.windows.1) требует git reset HEAD^большего; не уверен, что ожидаемый вклад здесь. Пожалуйста, смотрите ниже скриншот

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

Нашел другое решение git reset HEAD~#numberOfCommits с помощью которого мы можем выбрать количество локальных коммитов, которые вы хотите сбросить, сохранив ваши изменения без изменений. Следовательно, мы получаем возможность отбросить все локальные коммиты, а также ограниченное количество локальных коммитов.

Смотрите ниже скриншоты, показывающие git reset HEAD~1в действии: введите описание изображения здесь

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

nkharche
источник
Возможно, вам нужно экранировать символ ^ - попробуйте сбросить git "HEAD ^" или сбросить git HEAD \ ^
Марк Фишер
В добавление к этому, HEAD ^^ для git reset работает как одиночный ^ и рассматривается как новая строка.
Джон Осс
2

Еще один способ сделать это.

Добавьте коммит поверх временного коммита и затем выполните:

git rebase -i

Чтобы объединить два коммита в один (команда откроет текстовый файл с явными инструкциями, отредактируйте его).

Руслан Осипов
источник
2
Технически правильно, но далеко не так элегантно, как просто git reset HEAD^. Git rebase имеет много места для ошибки здесь.
TheBuzzSaw
0

2020 простой способ:

git reset <commit_hash>

(Хэш коммита последнего коммита, который вы хотите сохранить).

Если коммит был выдвинут, вы можете сделать:

git push -f

Вы будете хранить незафиксированные изменения локально

XYS
источник