Как удалить коммит по хешу
Я работал с другом над проектом, и он редактировал кучу файлов, которые не должны были редактироваться. Каким-то образом я слил его работу с моей, или когда вытащил ее, или когда я попытался просто выбрать конкретные файлы, которые я хотел. Я долго искал и играл, пытаясь выяснить, как удалить коммиты, которые содержат изменения в этих файлах, кажется, что это сложность между возвратом и перебазированием, и нет простых примеров, и Предполагается, что я знаю больше, чем знаю.
Итак, вот упрощенная версия вопроса:
Учитывая следующий сценарий, как мне удалить коммит 2?
Вот пример того, как я пытался вернуться
Если кто-то обнаружит это во время поиска той же проблемы, вот что я в итоге сделал: скопируйте и вставьте. Шутки в сторону. Потратил 6+ часов, пытаясь заставить предложенные решения работать, но безрезультатно. В конце концов, я был вне времени, вытащил оригинал и просто скопировал / вставил около 20 файлов. Прошло менее 5 минут, и с тех пор все было хорошо (даже когда эти файлы объединялись с изменениями в других ветвях, которые произошли до этого фиаско). Я предлагаю вам также принять этот подход. Это не только самое простое, я также подозреваю, что это единственное, что работает.
Я столкнулся с аналогичной проблемой, но, возможно, более сложной: у меня была ветка с сотнями коммитов, которые я хотел раздавить. К сожалению, коммиты из другой ветки были объединены обратно в ветку в промежуточной точке, поэтому для того, чтобы я мог раздавить, потребовалось «слияние». Я пошел по тому же пути, как предложено в tk ниже (выбор вишни + использование обозначения диапазона), но в некоторых других файлах возникли конфликты. В конце концов, копирование и вставка + несколько ручных изменений стали самым простым и предсказуемым способом продвижения вперед. Определенно стоит задуматься, если вы обнаружите, что тратите на это слишком много времени.
Алгоритм, который Git использует при расчете различий, требует, чтобы
- возвращаемые строки не изменяются никакими последующими коммитами.
- что не будет никаких других «смежных» коммитов позже в истории.
Определение «смежных» основано на количестве строк по умолчанию из контекста diff, которое равно 3. Так что если «myfile» был построен следующим образом:
Тогда все работает как положено.
Второй ответ был очень интересным. Существует функция, которая еще не была официально выпущена (хотя она доступна в Git v1.7.2-rc2), которая называется Revert Strategy. Вы можете вызвать git так:
git revert - стратегия разрешения
и он должен лучше понять, что вы имели в виду. Я не знаю, каков список доступных стратегий, и при этом я не знаю определения любой стратегии.
Есть четыре способа сделать это:
Чистый путь, возвращаясь, но сохраняйте в журнале возврат:
Суровым образом удалите всего лишь последний коммит:
Примечание. Избегайте, git reset --hard поскольку при этом также будут отменены все изменения в файлах с момента последней фиксации. Если --soft не работает, попробуйте --mixed или --keep .
Перебазировать (показать журнал последних 5 коммитов и удалить ненужные строки, или переупорядочить, или сжать несколько коммитов в одном, или сделать что-то еще, что вы хотите, это очень универсальный инструмент):
И если допущена ошибка:
Быстрая перебазировка: удалить только определенный коммит, используя его идентификатор:
Альтернативы: вы также можете попробовать:
Еще одна альтернатива:
В крайнем случае, если вам нужна полная свобода редактирования истории (например, потому что git не позволяет вам редактировать то, что вы хотите), вы можете использовать это очень быстрое приложение с открытым исходным кодом: reposurgeon .
Примечание: конечно, все эти изменения выполняются локально, git push после чего вы должны применить изменения к удаленному. И в случае, если ваше хранилище не хочет удалять коммит («ускоренная пересылка не разрешена», что происходит, когда вы хотите удалить коммит, который вы уже выдвинули), вы можете использовать git push -f для принудительной отправки изменений.
Примечание 2: если вы работаете над веткой и вам нужно принудительно нажать, вам следует избегать git push --force этого, поскольку это может перезаписать другие ветви (если вы внесли изменения в них, даже если ваша текущая проверка находится в другой ветви). Предпочитает всегда указывать удаленный филиал , когда вы вынуждаете толчок : git push --force origin your_branch .
Основываясь на предложениях @gaborous: выполните "git rebase -i HEAD ~ 2". Теперь у вас есть несколько вариантов. В vim вы можете увидеть некоторые закомментированные строки: одна из них говорит вам, что вы можете просто удалить строку (которая должна быть коммитом, от которого вы хотите избавиться), и этот коммит будет удален вместе с его журналом в вашей истории.
git rebase -i HEAD~5 работал на меня. Затем я просто удалил коммиты, которые мне не нужны, и смог решить проблему менее чем за 30 секунд. Спасибо.
Вот простое решение:
(Примечание: x это количество коммитов)
после выполнения файл блокнота откроется. Введите drop помимо вашего коммита.
Если вы не знаете Vim, просто нажмите на каждый выбор слов, который вы хотите отредактировать, а затем нажмите клавишу «I» (для режима вставки). Когда вы закончите ввод, нажмите клавишу «esc», чтобы выйти из режима вставки.
и все, готово . Просто синхронизируйте панель инструментов git, и изменения будут перенесены на удаленный компьютер.
Если коммит, который вы сбросили, уже был на пульте, вам придется принудительно нажать. Так как --force считается вредным , используйте его git push --force-with-lease .
Есть ли хороший способ узнать, каким должен быть «x», если вы знаете только хэш коммита, о котором идет речь?
Для людей cpp-менталитета: x (количество коммитов) включительно. например, HEAD ~ 4 включает в себя последние 4 коммита.
попытался отменить 3 коммита: git rebase -i HEAD-3 получил фатальную ошибку: требуется единственная недействительная редакция в восходящем направлении 'HEAD-3'
Ваш выбор между
- сохраняя ошибку и представляя исправление и
- устранение ошибки и изменение истории.
Вы должны выбрать (1), если ошибочное изменение было обнаружено кем-либо еще, и (2), если ошибка ограничена частной ветвью без отправления.
Git revert - это автоматизированный инструмент для выполнения (1), он создает новый коммит, отменяя предыдущий коммит. Вы увидите ошибки и удаление в истории проекта, но люди, которые извлекают данные из вашего хранилища, не столкнутся с проблемами при обновлении. В вашем примере это не работает автоматически, поэтому вам нужно отредактировать 'myfile' (удалить строку 2), сделать git add myfile и git commit разобраться с конфликтом. После этого вы получите четыре коммита в своей истории с коммитом 4, возвращающим коммит 2.
git rebase -i 8230fa3 и удаление строки коммита отлично работали для меня с моими локальными изменениями. Спасибо!
Подход 1
Сначала получите хеш коммита (например: 1406cd61), который вам нужно вернуть. простое исправление будет ниже команды,
Если после фиксации 1406cd61 вы произвели больше изменений, относящихся к файлам 1406cd61, простая команда «Выше» работать не будет. Затем вы должны сделать следующие шаги, которые являются сбор вишни.
Подход 2
Пожалуйста, следуйте приведенным ниже инструкциям, поскольку мы используем --force, чтобы сделать это, вам необходимы права администратора git-репо.
Шаг 1. Найдите коммит до коммита, который вы хотите удалить git log
Шаг 2: Оформить заказ git checkout
Шаг 3: Создайте новую ветку, используя свой текущий коммит проверки git checkout -b
Шаг 4: Теперь вам нужно добавить коммит после удаленного коммита git cherry-pick
Шаг 5: Теперь повторите Шаг 4 для всех остальных коммитов, которые вы хотите сохранить.
Шаг 6: После того, как все коммиты были добавлены в вашу новую ветку и были зафиксированы. Убедитесь, что все в правильном состоянии и работает как задумано. Дважды проверьте все было совершено: git status
Шаг 7: Переключитесь на сломанную ветку git checkout
Шаг 8: Теперь выполните полную перезагрузку прерванной ветки для коммита до того, который вы хотите удалить. git reset --hard
Шаг 9: объединить вашу фиксированную ветку с этой веткой git merge
Шаг 10: Верните объединенные изменения в исходное состояние. ВНИМАНИЕ: Это перезапишет удаленный репозиторий! git push --force origin
Вы можете выполнить этот процесс, не создавая новую ветку, заменив Шаг 2 и 3 на Шаг 8, а затем не выполняйте Шаг 7 и 9.
Первый подход работал как шарм. Он включает в себя коммит, указывающий, что желаемый коммит был отменен, что очень удобно для целей отслеживания.
Вы можете удалить нежелательные коммиты с git rebase . Скажем, вы включили некоторые коммиты из ветки темы коллеги в ветку темы, но позже решите, что вам не нужны эти коммиты.
На этом этапе ваш текстовый редактор откроет интерактивное представление rebase. Например
- Удалить коммиты, которые вы не хотите, удалив их строки
- Сохранить и выйти
Если перебазировка не удалась, удалите временную ветку и попробуйте другую стратегию. В противном случае продолжайте со следующими инструкциями.
Если вы перемещаете ветку своей темы на удаленный компьютер, вам может потребоваться принудительное нажатие, так как история изменений изменилась. Если другие работают над той же веткой, дайте им голову.
Вот bash скрипт, который вы можете вставить, чтобы создать тестовое хранилище в /tmp папке:
На данный момент, у нас есть file.txt с этим содержанием:
На этом этапе HEAD находится на 5-м коммите, HEAD ~ 1 будет 4-м - и HEAD ~ 4 будет 1-м коммитом (поэтому HEAD ~ 5 не будет существовать). Допустим, мы хотим удалить 3-й коммит - мы можем выполнить эту команду в myrepo_git каталоге:
( Обратите внимание, что в git rebase -i HEAD~5 результате получается «fatal: необходима одна ревизия; недопустимый верхний колонтитул ~ 5». ) Откроется текстовый редактор (см. Скриншот в ответе @Dennis ) со следующим содержимым:
Таким образом, мы получаем все коммиты, так как (но не включая ) наш запрошенный HEAD ~ 4. Удалить строку pick 448c212 3rd git commit и сохранить файл; Вы получите этот ответ от git rebase :
В этот момент откройте myrepo_git / folder/file.txt в текстовом редакторе; вы увидите, что он был изменен:
По сути, git видит, что когда HEAD получил 2-й коммит, было содержимое aaaa + bbbb ; и затем у него есть патч cccc +, dddd который он не знает, как добавить к существующему контенту.
Так что здесь git не может решить за вас - это вы должны принять решение: удаляя 3-й коммит, вы либо сохраняете внесенные им изменения (здесь, строка cccc ) - либо нет. Если вы этого не сделаете, просто удалите лишние строки - в том числе cccc - в folder/file.txt текстовом редакторе, чтобы он выглядел так:
. а затем сохранить folder/file.txt . Теперь вы можете выполнить следующие команды в myrepo_git каталоге:
Ах - так, чтобы отметить , что мы решили конфликт, мы должны , прежде чем делать : git add folder/file.txt git rebase --continue
На данный момент, теперь у вас есть такая история (которую вы также можете проверить, скажем, с помощью gitk . других инструментов), например, содержимого folder/file.txt (с, по-видимому, неизменными временными метками оригинальных коммитов):
И если ранее мы решили сохранить строку cccc (содержимое 3-го git коммита, который мы удалили), мы бы имели:
Ну, это было то чтение, которое я надеялся найти, чтобы начать понимать, как git rebase работает с точки зрения удаления коммитов / ревизий; так что надеюсь, что это может помочь и другим .
Похоже, что в какой-то момент плохой коммит был включен в коммит слияния. Ваш коммит слияния уже отменен? Если да, то вы захотите использовать git revert ; вам придется стиснуть зубы и работать через конфликты. Если нет, то вы можете либо перебазировать, либо вернуть, но вы можете сделать это раньше фиксации слияния, а затем повторить слияние.
Во втором случае, устраняющем проблему до слияния, есть два подслоя, в зависимости от того, проделали ли вы больше работы после слияния. Если у вас нет, вы можете просто git reset --hard HEAD^ сбросить слияние, сделать возврат, а затем повторить слияние. Но я думаю, у вас есть. Итак, в итоге вы сделаете что-то вроде этого:
- создайте временную ветку перед слиянием и проверьте ее
- сделать возврат (или использовать git rebase -i для удаления неверного коммита)
- повторить слияние
- перебазировать вашу последующую работу обратно на: git rebase --onto
- удалить временную ветку
Итак, вы проделали некоторую работу и выдвинули ее, давайте назовем их коммитами A и B. Ваш коллега также выполнил некоторую работу, коммиты C и D. Вы объединили работу ваших коллег с вашей (слияние коммит E), затем продолжили работу, зафиксировали это, тоже (коммит F) и обнаружил, что ваш коллега изменил некоторые вещи, которые он не должен был делать.
Итак, ваша история коммитов выглядит так:
Вы действительно хотите избавиться от C, D и D '. Так как вы говорите, что объединили ваши коллеги с вашими, эти коммиты уже «там», поэтому удаление коммитов с помощью, например, git rebase - нет-нет. Поверьте мне, я пытался.
Теперь я вижу два выхода:
если вы еще не добавили E и F своему коллеге или кому-либо еще (как правило, вашему серверу-источнику), вы все же можете на время удалить их из истории. Это ваша работа, которую вы хотите сохранить. Это можно сделать с помощью
(замените D 'фактическим хешем коммита, который вы можете получить из git log
В этот момент коммиты E и F исчезли, и изменения снова стали незафиксированными изменениями в вашей локальной рабочей области. В этот момент я бы переместил их в ветку или превратил в патч и сохранил бы их на потом. Теперь верните работу вашего коллеги, автоматически с помощью git revert или вручную. Когда вы это сделаете, переиграйте свою работу поверх этого. У вас могут быть конфликты слияния, но, по крайней мере, они будут в написанном вами коде , а не в коде вашего коллеги.
Если вы уже выполнили работу, проделанную после коммитов вашего коллеги, вы все равно можете попытаться получить «обратный патч» либо вручную, либо с помощью git revert , но поскольку ваша работа «в пути», так сказать, вы, вероятно, получите больше конфликтов слияния и более запутанных. Похоже, это то, что вы оказались в .
I was working with a friend on a project, and he edited a bunch of files that shouldn't have been edited. Somehow I merged his work into mine, either when I pulled it, or when I tried to just pick the specific files out that I wanted. I've been looking and playing for a long time, trying to figure out how to remove the commits that contain the edits to those files, it seems to be a toss up between revert and rebase, and there are no straightforward examples, and the docs assume I know more than I do.
So here is a simplified version of the question:
Given the following scenario, how do I remove commit 2?
The expected result is
Here is an example of how I have been trying to revert
If anyone finds this while searching for the same problem, here is what I ended up doing: Copy and paste. Seriously. Spent 6+ hours trying to get the suggested solutions to work, to no avail. In the end, I was out of time, pulled up the original, and just copy/pasted about 20 files. Took under 5 minutes, and things have been fine ever since (even when those files are being merged with changes in other branches that happened before this fiasco). I suggest you take this approach also. Not only is it the simplest, I also suspect it is the only thing that works.
I faced a similar issue, but perhaps more complex: had a branch with hundreds of commits that I wanted to squash. Unfortunately commits from another branch were merged back into the branch at an intermediate point, so an "unmerge" was needed before I could squash. I went down a similar path as suggested by tk below (cherry picking + using the range notation), but it produced conflicts in some other files. In the end copy & paste + a few manual edits was the easiest and most predictable path forward. Definitely worth a consideration if you find yourself spending too much time on this.
One problem I always have is that I am never the person with the problem commits. I'm the Release Manager and our developers come to me to fix things. I never have the commits in my local to fix, so a lot of the assumptions "ie: if YOU have included the wrong commit" don't really apply. My local clone never has the history to work with.
14 Answers 14
There are four ways of doing so:
Clean way, reverting but keep in log the revert:
Harsh way, remove altogether only the last commit:
Note: Avoid git reset --hard as it will also discard all changes in files since the last commit. If --soft does not work, rather try --mixed or --keep .
Rebase (show the log of the last 5 commits and delete the lines you don't want, or reorder, or squash multiple commits in one, or do anything else you want, this is a very versatile tool):
And if a mistake is made:
Quick rebase: remove only a specific commit using its id:
Alternatives: you could also try:
Yet another alternative:
As a last resort, if you need full freedom of history editing (eg, because git don't allow you to edit what you want to), you can use this very fast open source application: reposurgeon.
Note: of course, all these changes are done locally, you should git push afterwards to apply the changes to the remote. And in case your repo doesn't want to remove the commit ("no fast-forward allowed", which happens when you want to remove a commit you already pushed), you can use git push -f to force push the changes.
Note2: if working on a branch and you need to force push, you should absolutely avoid git push --force because this may overwrite other branches (if you have made changes in them, even if your current checkout is on another branch). Prefer to always specify the remote branch when you force push: git push --force origin your_branch .
Based on suggestions of @gaborous: do a "git rebase -i HEAD~2". Now you have several options. In the vim you can see some commented lines: one of them tells you that you simply can delete a line (which should be the commit you want to get rid of) and this commit will be removed together with it's log in your history.
git rebase -i HEAD~5 worked for me. Then I just removed the commit(s) that I didn't need and I was able to solve the problem in less than 30 seconds. Thank you.
For what it's worth, the first command git revert . helped me hold back commits from production branch (e.g. master ) and then with combination of git cherry-pick was able to include back those reverted commits into my develop branch in order to not lose any development work while only deploying what needed to be deployed.
git revert --strategy resolve
Here is an easy solution:
(Note: x is the number of commits)
upon executing notepad file will open. Enter drop besides your commit.
If you don’t know Vim, just click on each word pick that you want to edit and then hit the "I" key (for insert mode). Once you’re done typing hit the "esc" key to exit insert mode.
and that's it, you are done. Just sync the git dashboard and the changes will be pushed to remote.
If the commit you drop was already on the remote, you will have to force push. Since --force is considered harmful, use git push --force-with-lease .
The algorithm that Git uses when calculating diffs to be reverted requires that
- The lines being reverted are not modified by any later commits.
- There not be any other "adjacent" commits later in history.
The definition of "adjacent" is based on the default number of lines from a context diff, which is 3. So if 'myfile' was constructed like this:
Then it all works as expected.
The second answer was very interesting. There is a feature that has not yet been officially released (though it is available in Git v1.7.2-rc2) called Revert Strategy. You can invoke git like this:
git revert --strategy resolve
and it should do a better job of figuring out what you meant. I do not know what the list of available strategies is, nor do I know the definition of any strategy.
perl -p is useful for writing very short (one line) programs which loop and pass their input through to the output, similar to sed. perl -i is used for editing files in place. perl -e is how to submit the code to be evaluated.
Approach 1
First get the commit hash(ex:1406cd61) that you need to revert. simple fix will be below command,
if you have commit more changes related to 1406cd61 files after 1406cd61 commit, Above simple command will not work. Then you have to do the below steps, which is cherry picking.
Approach 2
Please follow below sequence of actions, Since we are using --force you need to have admin rights over the git repo to do this.
Step 1: Find the commit before the commit you want to remove git log
Step 2: Checkout that commit git checkout
Step 3: Make a new branch using your current checkout commit git checkout -b
Step 4: Now you need to add the commit after the removed commit git cherry-pick
Step 5: Now repeat Step 4 for all other commits you want to keep.
Step 6: Once all commits have been added to your new branch and have been committed. Check that everything is in the correct state and working as intended. Double check everything has been committed: git status
Step 7: Switch to your broken branch git checkout
Step 8: Now perform a hard reset on the broken branch to the commit prior to the one your want to remove git reset --hard
Step 9: Merge your fixed branch into this branch git merge
Step 10: Push the merged changes back to origin. WARNING: This will overwrite the remote repo! git push --force origin
You can do the process without creating a new branch by replacing Step 2 & 3 with Step 8 then not carry out Step 7 & 9.
The first approach worked like a charm. It includes you a commit specifying that your desired commit has been reverted, which is really nice for tracking purposes.
Your choice is between
- keeping the error and introducing a fix and
- removing the error and changing the history.
You should choose (1) if the erroneous change has been picked up by anybody else and (2) if the error is limited to a private un-pushed branch.
Git revert is an automated tool to do (1), it creates a new commit undoing some previous commit. You'll see the error and removal in the project history but people who pull from your repository won't run into problems when they update. It's not working in an automated manner in your example so you need to edit 'myfile' (to remove line 2), do git add myfile and git commit to deal with the conflict. You will then end up with four commits in your history, with commit 4 reverting commit 2.
If nobody cares that your history changes, you can rewrite it and remove commit 2 (choice 2). The easy way to do this is to use git rebase -i 8230fa3 . This will drop you into an editor and you can choose not to include the erroneous commit by removing the commit (and keeping "pick" next to the other commit messages. Do read up on the consequences of doing this.
git rebase -i 8230fa3, and delete the commit's line worked great for me with my local-only changes. Thanks!
You can remove unwanted commits with git rebase . Say you included some commits from a coworker's topic branch into your topic branch, but later decide you don't want those commits.
At this point your text editor will open the interactive rebase view. For example
- Remove the commits you don't want by deleting their lines
- Save and quit
If the rebase wasn't successful, delete the temporary branch and try another strategy. Otherwise continue with the following instructions.
If you're pushing your topic branch to a remote, you may need to force push since the commit history has changed. If others are working on the same branch, give them a heads up.
From other answers here, I was kind of confused with how git rebase -i could be used to remove a commit, so I hope it's OK to jot down my test case here (very similar to the OP).
Here is a bash script that you can paste in to create a test repository in the /tmp folder:
At this point, we have a file.txt with these contents:
At this point, HEAD is at the 5th commit, HEAD~1 would be the 4th - and HEAD~4 would be the 1st commit (so HEAD~5 wouldn't exist). Let's say we want to remove the 3rd commit - we can issue this command in the myrepo_git directory:
(Note that git rebase -i HEAD~5 results with "fatal: Needed a single revision; invalid upstream HEAD~5".) A text editor (see screenshot in @Dennis' answer) will open with these contents:
So we get all commits since (but not including) our requested HEAD~4. Delete the line pick 448c212 3rd git commit and save the file; you'll get this response from git rebase :
At this point open myrepo_git/ folder/file.txt in a text editor; you'll see it has been modified:
Basically, git sees that when HEAD got to 2nd commit, there was content of aaaa + bbbb ; and then it has a patch of added cccc + dddd which it doesn't know how to append to the existing content.
So here git cannot decide for you - it is you who has to make a decision: by removing the 3rd commit, you either keep the changes introduced by it (here, the line cccc ) -- or you don't. If you don't, simply remove the extra lines - including the cccc - in folder/file.txt using a text editor, so it looks like this:
. and then save folder/file.txt . Now you can issue the following commands in myrepo_git directory:
Ah - so in order to mark that we've solved the conflict, we must git add the folder/file.txt , before doing git rebase --continue :
Here a text editor opens again, showing the line 4th git commit - here we have a chance to change the commit message (which in this case could be meaningfully changed to 4th (and removed 3rd) commit or similar). Let's say you don't want to - so just exit the text editor without saving; once you do that, you'll get:
At this point, now you have a history like this (which you could also inspect with say gitk . or other tools) of the contents of folder/file.txt (with, apparently, unchanged timestamps of the original commits):
And if previously, we decided to keep the line cccc (the contents of the 3rd git commit that we removed), we would have had:
Well, this was the kind of reading I hoped I'd have found, to start grokking how git rebase works in terms of deleting commits/revisions; so hope it might help others too.
Коллеги, приветствую. Сегодня мы немного поговорим о работе с коммитами в системе контроля версиями — Git.
Целью этой статьи является обзор популярных сценариев проблем и методов их решения. Мы узнаем о том, как можно решить проблемы связанные с человеческими ошибками при создании коммитов в Git, какой способ устранения ошибок лучше применять в конкретных ситуациях.
Возникающие проблемы
В ходе разработки каких-либо проектов в Git, мы можем столкнуться с различными ситуациями, такими как:
Безопасные изменения для локального репозитория
Изменить последний коммит
Давайте предположим, что мы работаем над каким-либо проектом локально.
Но что делать, если мы забыли добавить или изменить какие-то файлы в наш последний коммит?
Для этого импортируем нужные файлы в наш проект или изменяем уже существующие и добавляем их в индекс.
Для теста я добавлю файл forgotten file.txt в проект. После этого еще раз выполним команду:
В случае необходимости удаления ненужного файла из последнего коммита, мы можем удалить его из проекта, добавить изменения в индекс и воспользоваться все той же командой:
Удалить коммит
Для того, чтобы удалить один или несколько коммитов, мы можем воспользоваться командой git reset. Существует два типа выполнения reset:
- Soft — удаляет коммит из истории и переводит файлы из него в состояние рабочего каталога.
- Hard — удаляет коммит из истории вместе с файлами.
Сначала разберем soft reset. Возвращаемся к нашему проекту.
В один момент мы осознали, что допустили ошибку и хотели бы переделать последний коммит.
Для этого выполним команду:
Проверим историю коммитов. Мы можем убедится, что последний коммит был удален.
Файлы, которые были созданы или изменены в удаленном коммите, перешли в состояние рабочего каталога. Мы можем продолжить работу и, например, создать новый коммит на основе этих файлов.
Подобные действия мы можем выполнять для нескольких коммитов сразу. Для примера введем команду:
3 последних коммита пропали из истории.
Проверим состояние файлов, которых коснулась процедура удаления коммитов.
Мы можем полностью удалить коммиты без сохранения связанных с ними файлов. Для этого выполним процедуру hard reset:
И проверим состояние.
Массовое изменение коммитов
Для этой задачи хорошо подходит выполнение команды git rebase. Ее запуск в интерактивном режиме позволит нам последовательно сообщить Git’у как бы мы хотели выполнить оптимизацию.
Мы считаем, что 4 последних коммита можно смело объединить в один.
Для этого выполним команду:
Перед нами откроется текстовый редактор по умолчанию в котором мы будем выполнять изменения. Используем коммит с хэшем aab958a как начальный, путем указания команды pick напротив него и укажем напротив трех остальных команду squash. Как мы можем видеть ниже в описании, команда suqash объединит коммит с предыдущим в списке.
После выполнения — выходим из текстового редактора.
Снова выходим из текстового редактора.
Похоже, что наше объединение нескольких коммитов прошло успешно. Давайте проверим это командой:
Снова вводим команду:
В этот раз в качестве начального коммита будет использоваться с коммит хэшем 4c17043. Для его переименования укажем напротив команду reword. Затем объединяем с ним два последующих коммита командой squash. Последний коммит мы никак не изменяем.
После переименования выходим из текстового редактора и проверяем историю.
Похоже, что нам удалось провести историю коммитов в порядок и теперь нет причин откладывать загрузку нашего проекта на удаленный репозиторий. 🙂
Безопасные изменения для удаленных репозиториев
До этого мы рассматривали случаи локальной разработки. По большому счету, пока мы не публикуем изменения, наши действия — не могут повлиять на деятельность наших коллег по проекту. Применение вышеописанных способов до публикации изменений на удаленном репозитории — вполне оправдано.
Однако, что делать, если мы совместно работаем над проектом и только спустя какое-то время понимаем, что один из коммитов — лишний? Если мы воспользуемся вышеописанными методами, это может спровоцировать ряд проблем в работе над проектом у наших коллег.
В подобных случаях имеет смысл откатывать коммит. Подобная процедура создает еще один коммит, который отменит изменения ненужного. Таким образом мы не порушим историю проекта и избавим наших коллег от лишних проблем.
Снова вовзращаемся к исходному состоянию проекта.
Предположим, что изменения в коммите с хэшем и 5140d80 — лишние. Выполним команду:
Git сообщит нам об изменениях, после отката.
Проверим историю. Как мы можем убедиться, был создан новый коммит, «отменяющий» изменения ненужного.
К сожалению, в реальности могут возникнуть проблемы при откате коммитов, требующие дополнительных мер для их решения. В этой статье мы рассмотрели достаточно простые ситуации, чтобы наглядно увидеть что и как делает каждая команда.
Итоги
В этой статье мы узнали о четырех способах изменять коммиты:
- git commit —amend — позволяет изменять последний коммит.
- git reset — позволяет удалять коммиты.
- git rebase — позволяет производить массовое изменение коммитов, приводить в порядок историю.
- git revert — позволяет откатывать изменения коммитов.
Первые три команды — подойдут для использования в условиях локальной разработки.
Последняя команда — лучший способ, не создавать лишних проблем коллегам при совместной разработке.
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Open with Desktop
- View raw
- Copy raw contents Copy raw contents
Copy raw contents
Copy raw contents
Вспомогательные инструменты Git
Ссылки на коммиты
Контрольная сумма (хэш) SHA-1 является стандартным способом идентификации коммита в истории Git. Как правило, достаточно указать ее первые несколько знаков, при условии, что они не дублируются с другими хэшами в базе и их количество не менее четырех. Просмотреть хэши коммитов в укороченном виде можно командой git log --abbrev-commit .
Ветки и тэги являются указателями на коммит, поэтому все команды, которые принимают хэш, также могут принимать имя ветки или тэга. Узнать хэш, на которую указывает ветка или тэг, можно командой git show или, в более компактном виде, git rev-parse .
Все изменения веток и указателя HEAD сохраняются в журналах, которые также можно использовать для навигации по коммитам. Журнал можно просмотреть командой git relog , записи в нем имеют вид @ и позволяют сослаться не на номер коммита, а на этап в истории ветки. В качестве номера можно использовать дату и время, в том числе относительные. Например, команда git show master@ покажет состояние ветки master на день раньше текущего времени. Журналы изменений для веток носят локальный характер, они не переносятся между копиями репозитория при клонировании. По умолчанию, записи в них хранятся не более 90 дней.
Указатель коммита может принимать суффикс-модификатор ^ , который означает, что ссылка идет не на сам коммит, а на его родителя. Например, git show HEAD^ покажет не последний коммит ветки, а идущий непосредственно перед ним. После ^ можно указать номер родительского коммита, если их было несколько, т.е. коммит был результатом множественного слияния. Например git show HEAD^2 сошлется на второго непосредственного родителя HEAD , если он у него есть. Несколько ^ подряд ссылаются на родителей родителей и т.д. Например, чтобы показать третий коммит от текущего, можно воспользоваться командой git show HEAD^^^ . Чтобы не указывать длинные цепочки из символа ^ , можно воспользоваться символом ~ и номером. Ссылка HEAD~3 указывает на тот же коммит, что и HEAD^^^ .
Такие команды, как git log и git diff позволяют просматривать подмножество коммитов (диапазон), которое задается ссылками на коммит, разделенными двумя точками ( .. ). Например, команда git log origin/master..HEAD означает, что Git должен выбрать все коммиты, доступные (являющиеся предками) указателю HEAD , но недоступные из указателя origin/master . В результате, можно увидеть, что добавилось в текущей ветке по сравнению с origin/master . Запрос на диапазон коммитов можно делать и без двух точек, просто перечисляя указатели в командной строке. Чтобы отсечь коммиты, недоступные из какого-либо указателя, перед ним ставится символ ^ или ключ --not . Список коммитов из предыдущего примера можно вывести командой git log ^origin/master HEAD или git log HEAD --not origin/master . Число указателей в командной строке не ограничено двумя, благодаря чему можно составлять достаточно сложные запросы к веткам.
Три точки между ссылками на коммиты означают, что нужно выбрать те из них, которые доступны из того или иного указателя, но не из обоих сразу. Например, можно посмотреть, в каких изменениях разошлись ветки master и feature командой git log master. feature . В этом случае, также может быть полезен ключ --left-right , который добавляет к записям журнала подсказку, символ < или >. С ее помощью можно увидеть, к какой из веток относится изменение.
Команды git reset и git checkout
Команда git reset представляет собой средство для управления рабочим деревом проекта, индексом и историей коммитов. Для понимания принципов ее работы, рассмотрим три хранилища данных, доступные в репозитории Git:
- ветку, на которую указывает HEAD
- индекс изменений, которые войдут в следующий коммит
- рабочее дерево проекта
В зависимости от указанных в командной строке параметров, команда git reset работает в одном из следующих режимов:
Режим --soft сбрасывает указатель ветки, на которую в данный момент указывает HEAD , в состояние, соответствующее переданному в командной строке коммиту. При этом не затрагивается индекс и рабочее дерево проекта, поэтому все изменения, сохраненные в индексе и еще не добавленные в него, могут образовать один или более новых коммитов. На практике, сброс в режиме --soft применяют, когда нужно заменить несколько последних коммитов в ветке на один. Для этого командой git log находят хэш последнего коммита, который не будет затронут, а затем выполняют git reset --soft . Изменения, которые шли в истории коммитов за ним, будут отброшены, а текущее состояние дерева проекта можно будет добавить в индекс и сформировать коммит. Фактически, этот коммит будет представлять собой все изменения, которые накопились после выбранного, и заменит их собой в истории коммитов. Команда git commit --amend производит аналогичные действия, но только с последним коммитом в текущей ветке.
Режим --mixed сбрасывает не только указатель текущей ветки, но и состояние индекса в указанный в командной строке коммит. Если коммит не задан, то будет выбран последний на вершине текущей ветки (т.е. HEAD ). Таким образом, команда git reset --mixed или, по умолчанию, просто git reset , отменяет действие предыдущих команд git add , очищая индекс, но не затрагивая других хранилищ проекта. В этом режиме, команду можно вызывать с именем файла или директории проекта. При этом указатель ветки не будет сдвинут, но состояние заданных файлов в индексе будет приведено в соответствие с заданным коммитом. Например, чтобы отменить добавление в индекс файла file.txt , достаточно выполнить git reset file.txt .
Режим --hard сбрасывает проект в состояние, соответствующее выбранному коммиту, причем изменяются все три хранилища: текущая ветка, индекс и рабочее дерево проекта. Все изменения, сделанные в файлах проекта, будут утеряны при вызове этой команды. Ее можно использовать, чтобы отказаться от неудачного слияния или для отмены сделанных в рабочем дереве изменений.
Команды git checkout и git reset --hard действуют похожим образом в том смысле, что рабочее дерево проекта приводится в состояние, соответствующее коммиту. Однако, в отличие от reset , git checkout не передвигает указатель ветки, а только HEAD , что позволяет не потерять коммиты, образующие ветку. Кроме того, если в дереве проекта имеются изменения, производится попытка их слияния с состоянием выбранного коммита.
Если передать команде git checkout файл или директорию, результат ее выполнения затронет как индекс, так и рабочее дерево. Содержимое файлов будет сброшено в заданное состояние, изменения утеряны как из рабочего дерева, так и из индекса, но указатель ветки и HEAD останутся прежними.
Команда git stash и "тайники"
При работе с проектом, часто возникает необходимость временно переключиться на какой-нибудь другой коммит или ветку. Если на этот момент в рабочем дереве проекта имеются изменения, они будут утеряны, что может быть нежелательно. Один из вариантов -- сформировать из них промежуточный коммит и сохранить его в ветке, а затем переключиться на новую. Изменения при этом сохранятся, но в истории коммитов появится лишняя запись, от которой впоследствии придется избавляться командой git commit --amend или git reset --soft .
Чтобы избежать ненужных вмешательств в историю коммитов, Git предоставляет команду git stash и механизм "тайников". Пусть в рабочем дереве проекта есть изменения, не добавленные в коммит. Для их сохранения нужно выполнить команду git stash save или просто git stash , в результате которой индекс и изменения в рабочем дереве будут сохранены в специальном хранилище-тайнике. Дерево проекта и индекс, при этом, окажутся сброшенными в вершину ветки. Затем, можно будет свободно сменить ветку и начать работу над ней. Если изменения потребуется вернуть из тайника, достаточно выполнить команду git stash apply . Причем, сохранить изменения можно в одной ветке, а применить их впоследствии -- к другой. По умолчанию, команда git stash apply не возвращает индекс в исходное состояние, а предоставляет пользователю самому добавлять в него файлы. Если это нежелательно, ее можно вызывать с ключом --index . Наоборот, если вызвать git stash save --keep-index , то в тайник сохранятся только непроиндексированные изменения. Это дает возможность сформировать коммит из текущего индекса, а несохраненные изменения записать в тайник, чтобы вернуться к ним позже. Ключ --include-untracked позволяет также спрятать в тайник файлы, которые есть в рабочем дереве, но не отслеживаются репозиторием.
Тайники организованы в виде стека, в который команда git stash добавляет очередную запись на вершину. Команда git stash pop не только применяет изменения из последнего тайника, но и удаляет его с вершины стека. Помимо собственно изменений, тайник содержит ссылку на коммит, от которого он был вызван. Благодаря этому, командой git stash branch можно выполнить сразу несколько операций: создать ветку от сохраненного в тайнике коммита, применить к ней изменения из тайника и удалить тайник из стека.
Командой git stash list можно просмотреть состояние стека и найти идентификаторы тайников, которые имеют вид stash@ . Просмотреть тайник можно командой git stash show . Также по идентификатору можно применить произвольный тайник из стека командой git stash apply или git stash branch . Удалить один из тайников можно git stash drop , а полностью очистить стек -- командой git stash clear .
Еще одна команда, которая изменяет рабочее дерево проекта -- git clean . С ее помощью можно удалить из проекта файлы, не отслеживаемые в репозитории, чтобы случайно не добавить их в коммит. С ключом -d , команда удаляет не только файлы, но и директории.
Рассмотренные выше команды управляют положением указателя ветки, но не затрагивают ее связность. Перебазирование позволяет более глубоко вмешиваться в историю изменений, переставляя родительские указатели коммитов. Например, может быть желательно перенести коммиты из одной ветки на вершину другой, для того, чтобы сделать историю изменений проекта более линейной и читаемой.
Пусть имеется ветка develop со текущим состоянием проекта, и ветка feature с экспериментальными изменениями, которые были ответвлены от некоего коммита в прошлом. Стандартный способ добавить эти изменения в develop , это слияние, с образованием нового коммита. Однако, можно перенести коммиты из feature на вершину ветки develop так, как будто они были сделаны непосредственно в ней. Это достигается командами:
При перебазировании, Git определяет общего предка двух веток, в данном случае, feature и develop , находит разницу между вершиной develop и общим предком, а затем -- применяет эту разницу и все коммиты от общего предка до вершины feature к вершине develop . В результате, корень ветки feature , с учетом разницы, оказывается перенесен на вершину develop , а указатель develop остается на месте. После перебазирования, можно слить develop и feature путем перемотки вперед:
После выполнения всех команд, состояние рабочего дерева проекта будет в точности таким же, как если бы было выполнено обычное слияние, но коммиты ветки feature будут образовывать не боковую ветку, а отрезок на линейной последовательности изменений в ветке develop . Если впоследствии удалить feature , уже будет невозможно определить, были ли изменения внесены отдельной веткой или добавлялись непосредственно в develop .
Перебазирование ветки на саму себя возможно, но в общем случае никак на ней не отражается. Исключением является интерактивный режим выполнения команды, в котором Git предоставляет возможность выбирать коммиты и действия над ними из истории ветки. Например, если выполнить git rebase --interactive HEAD~5 , то будет открыт редактор с текстом примерно следующего вида:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
1. Получаем хэш-код коммита, к которому хотим вернуться. |
2. Заходим в папку репозитория и пишем в консоль: |
$ git reset --hard a3775a5485af0af20375cedf46112db5f813322a |
$ git push --force |
Lylko commented Mar 5, 2021
Kuryliak commented Mar 29, 2021
Cy-real commented Apr 5, 2021
ink-kin commented Apr 7, 2021
Спасибо большое!
ps
мне не помогло . откат на -1 commit не спас .
но я сам виноват походу .
Aleksandra-ui commented Apr 27, 2021
volodymyr-lykhvar-axon commented May 13, 2021
alexandra-stepanova commented May 24, 2021
akaCroc commented Jun 26, 2021
А если в первом коммите есть лишние файлы?
levosay commented Jul 7, 2021
alex-sansei commented Jul 15, 2021
JerryCash-x commented Jul 20, 2021
zose43 commented Aug 13, 2021
fellainthewagon commented Aug 21, 2021 •
be careful with: git reset --hard a3775a5485af0af20375cedf46112db5f813322a
it delete all your current changes, use that command without --hard , then git push --force
chall2chall4 commented Sep 20, 2021
ZodiacGP commented Sep 24, 2021
Sirena-byte commented Sep 29, 2021
огромное спасибо! помогло)
danilbogomolovv commented Oct 1, 2021
Che-p-bot commented Nov 26, 2021
Спасибо, это очень помогло!
yurasts commented Nov 27, 2021
SlimDaddy228 commented Dec 12, 2021
Tim-Kids commented Dec 29, 2021
спасибо! все-таки русскоязычные запросы в Google тоже хорошо порой помагают.
KarimullinArthur commented Jan 17, 2022 •
Fobiya commented Mar 5, 2022
instasanjik commented Mar 24, 2022
Спасибо вам большое! Вы нас очень выручили!
Moon1706 commented Apr 17, 2022
@vorozhba
Хелло! А можно ли как-то удалить не последние коммиты путем перемещения HEAD назад, а точечно одни коммит из последовательности? Пример, [1,2,3,4] -> [1,3,4]
olek07 commented Apr 18, 2022
@vorozhba Хелло! А можно ли как-то удалить не последние коммиты путем перемещения HEAD назад, а точечно одни коммит из последовательности? Пример, [1,2,3,4] -> [1,3,4]
mpodanev commented Apr 18, 2022
@vorozhba
Хелло! А можно ли как-то удалить не последние коммиты путем перемещения HEAD назад, а точечно одни коммит из последовательности? Пример, [1,2,3,4] -> [1,3,4]
Через Вебшторм можно, просто удаляешь нужные коммит и пушишь через force. А как с помощью консольных команд не знаю, но точно можно.
olek07 commented Apr 19, 2022
@vorozhba
Хелло! А можно ли как-то удалить не последние коммиты путем перемещения HEAD назад, а точечно одни коммит из последовательности? Пример, [1,2,3,4] -> [1,3,4]
Через Вебшторм можно, просто удаляешь нужные коммит и пушишь через force. А как с помощью консольных команд не знаю, но точно можно.
Разве так можно? Там же что-то вроде блокчейна. Следующий зависит от предыдущего. Разве нет?
mpodanev commented Apr 19, 2022
@vorozhba
Хелло! А можно ли как-то удалить не последние коммиты путем перемещения HEAD назад, а точечно одни коммит из последовательности? Пример, [1,2,3,4] -> [1,3,4]
Через Вебшторм можно, просто удаляешь нужные коммит и пушишь через force. А как с помощью консольных команд не знаю, но точно можно.
Разве так можно? Там же что-то вроде блокчейна. Следующий зависит от предыдущего. Разве нет?
Не знаю на счет блокчейна, и думаю что такого нет в гите.
Но постоянно так делаю, когда в фичебранче что-то разрабатываю, а потом оказывается что в каком-то коммите что-то не нужное добавил, а это могло быть десять коммитов назад к примеру. Просто удалил его локально и через форс пуш обновил данные в удаленном репозитории.
Все, в удаленном репозитории больше нет этого коммита
Читайте также: