Как изменить локальный коммит

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

Это пост для тех, кто начинает работу с Git. Все, что здесь написано по частям можно найти в многочисленных простынях о Git на Хабре. Но я подумал, что неплохо было бы иметь отдельный предельно понятный топик, который бы гуглился по запросу «git изменение коммитов».

Изменение последнего коммита

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

git add .

Затем закоммитить изменения с параметром —amend (amend /əˈmɛnd/ — вносить поправки, исправлять, улучшать):

git commit --amend

Изменение названия последнего коммита

То же самое, с той лишь разницей что нет необходимости добавлять файлы в коммит. Просто укажите новое название:

git commit --amend -m "Новое название"

Изменение НЕ последнего коммита

Тут чуть посложнее, сделайте для начала два коммита, в моем примере они будут называться С1 и С2:
image

Для начала выполняем:

git rebase --interactive 
# короткая версия: git rebase -i

Откроется редактор, в котором вы можете указать что хотите сделать:
image

Как видите, git rebase -i может послужить когда нужно

  • r reword переименовать коммит
  • e edit изменить коммит
  • s squash склеить два или больше коммитов (squash /skwɒʃ/ — втиснуть, сжимать, тыква :) )

Рядом с коммитом С1 вместо pick впишите e для редактирования коммита или r для переименования. Сохранив файл, вы увидите подсказку от Git:
image

Разберемся подробнее что произошло. Мы переместились на коммит С1, «спрыгнув» с ветки master. Это можно проверить, запустив:

git branch

В ответ получим:

* (no branch, rebasing master)
  master

Дальше, как первой части поста где мы меняли последний коммит, делаем изменения и добавляем их:

git add .

и коммитим с параметром --amend:

git commit --amend

После успешного коммита, следуя подсказке (чуть выше на скриншоте), выполняем:

git rebase --continue

Тем самым мы возвратимся на ветку master с измененным коммитом, что и требовалось.

Пост получился короткий и, надеюсь, ясный. Commit early, commit often.

Many times, when working with Git, you may want to revise your local commit history.
One of the great things about Git is that it allows you to make decisions at the last possible moment.
You can decide what files go into which commits right before you commit with the staging area, you can decide that you didn’t mean to be working on something yet with git stash, and you can rewrite commits that already happened so they look like they happened in a different way.
This can involve changing the order of the commits, changing messages or modifying files in a commit, squashing together or splitting apart commits, or removing commits entirely — all before you share your work with others.

In this section, you’ll see how to accomplish these tasks so that you can make your commit history look the way you want before you share it with others.

Note

Don’t push your work until you’re happy with it

One of the cardinal rules of Git is that, since so much work is local within your clone, you have a great deal of freedom to rewrite your history locally.
However, once you push your work, it is a different story entirely, and you should consider pushed work as final unless you have good reason to change it.
In short, you should avoid pushing your work until you’re happy with it and ready to share it with the rest of the world.

Changing the Last Commit

Changing your most recent commit is probably the most common rewriting of history that you’ll do.
You’ll often want to do two basic things to your last commit: simply change the commit message, or change the actual content of the commit by adding, removing and modifying files.

If you simply want to modify your last commit message, that’s easy:

The command above loads the previous commit message into an editor session, where you can make changes to the message, save those changes and exit.
When you save and close the editor, the editor writes a new commit containing that updated commit message and makes it your new last commit.

If, on the other hand, you want to change the actual content of your last commit, the process works basically the same way — first make the changes you think you forgot, stage those changes, and the subsequent git commit --amend replaces that last commit with your new, improved commit.

You need to be careful with this technique because amending changes the SHA-1 of the commit.
It’s like a very small rebase — don’t amend your last commit if you’ve already pushed it.

Tip

An amended commit may (or may not) need an amended commit message

When you amend a commit, you have the opportunity to change both the commit message and the content of the commit.
If you amend the content of the commit substantially, you should almost certainly update the commit message to reflect that amended content.

On the other hand, if your amendments are suitably trivial (fixing a silly typo or adding a file you forgot to stage) such that the earlier commit message is just fine, you can simply make the changes, stage them, and avoid the unnecessary editor session entirely with:

$ git commit --amend --no-edit

Changing Multiple Commit Messages

To modify a commit that is farther back in your history, you must move to more complex tools.
Git doesn’t have a modify-history tool, but you can use the rebase tool to rebase a series of commits onto the HEAD that they were originally based on instead of moving them to another one.
With the interactive rebase tool, you can then stop after each commit you want to modify and change the message, add files, or do whatever you wish.
You can run rebase interactively by adding the -i option to git rebase.
You must indicate how far back you want to rewrite commits by telling the command which commit to rebase onto.

For example, if you want to change the last three commit messages, or any of the commit messages in that group, you supply as an argument to git rebase -i the parent of the last commit you want to edit, which is HEAD~2^ or HEAD~3.
It may be easier to remember the ~3 because you’re trying to edit the last three commits, but keep in mind that you’re actually designating four commits ago, the parent of the last commit you want to edit:

Remember again that this is a rebasing command — every commit in the range HEAD~3..HEAD with a changed message and all of its descendants will be rewritten.
Don’t include any commit you’ve already pushed to a central server — doing so will confuse other developers by providing an alternate version of the same change.

Running this command gives you a list of commits in your text editor that looks something like this:

pick f7f3f6d Change my name a bit
pick 310154e Update README formatting and add blame
pick a5f4a0d Add cat-file

# Rebase 710f0f8..a5f4a0d onto 710f0f8
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

It’s important to note that these commits are listed in the opposite order than you normally see them using the log command.
If you run a log, you see something like this:

$ git log --pretty=format:"%h %s" HEAD~3..HEAD
a5f4a0d Add cat-file
310154e Update README formatting and add blame
f7f3f6d Change my name a bit

Notice the reverse order.
The interactive rebase gives you a script that it’s going to run.
It will start at the commit you specify on the command line (HEAD~3) and replay the changes introduced in each of these commits from top to bottom.
It lists the oldest at the top, rather than the newest, because that’s the first one it will replay.

You need to edit the script so that it stops at the commit you want to edit.
To do so, change the word “pick” to the word “edit” for each of the commits you want the script to stop after.
For example, to modify only the third commit message, you change the file to look like this:

edit f7f3f6d Change my name a bit
pick 310154e Update README formatting and add blame
pick a5f4a0d Add cat-file

When you save and exit the editor, Git rewinds you back to the last commit in that list and drops you on the command line with the following message:

$ git rebase -i HEAD~3
Stopped at f7f3f6d... Change my name a bit
You can amend the commit now, with

       git commit --amend

Once you're satisfied with your changes, run

       git rebase --continue

These instructions tell you exactly what to do.
Type:

Change the commit message, and exit the editor.
Then, run:

This command will apply the other two commits automatically, and then you’re done.
If you change pick to edit on more lines, you can repeat these steps for each commit you change to edit.
Each time, Git will stop, let you amend the commit, and continue when you’re finished.

Reordering Commits

You can also use interactive rebases to reorder or remove commits entirely.
If you want to remove the “Add cat-file” commit and change the order in which the other two commits are introduced, you can change the rebase script from this:

pick f7f3f6d Change my name a bit
pick 310154e Update README formatting and add blame
pick a5f4a0d Add cat-file

to this:

pick 310154e Update README formatting and add blame
pick f7f3f6d Change my name a bit

When you save and exit the editor, Git rewinds your branch to the parent of these commits, applies 310154e and then f7f3f6d, and then stops.
You effectively change the order of those commits and remove the “Add cat-file” commit completely.

Squashing Commits

It’s also possible to take a series of commits and squash them down into a single commit with the interactive rebasing tool.
The script puts helpful instructions in the rebase message:

#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

If, instead of “pick” or “edit”, you specify “squash”, Git applies both that change and the change directly before it and makes you merge the commit messages together.
So, if you want to make a single commit from these three commits, you make the script look like this:

pick f7f3f6d Change my name a bit
squash 310154e Update README formatting and add blame
squash a5f4a0d Add cat-file

When you save and exit the editor, Git applies all three changes and then puts you back into the editor to merge the three commit messages:

# This is a combination of 3 commits.
# The first commit's message is:
Change my name a bit

# This is the 2nd commit message:

Update README formatting and add blame

# This is the 3rd commit message:

Add cat-file

When you save that, you have a single commit that introduces the changes of all three previous commits.

Splitting a Commit

Splitting a commit undoes a commit and then partially stages and commits as many times as commits you want to end up with.
For example, suppose you want to split the middle commit of your three commits.
Instead of “Update README formatting and add blame”, you want to split it into two commits: “Update README formatting” for the first, and “Add blame” for the second.
You can do that in the rebase -i script by changing the instruction on the commit you want to split to “edit”:

pick f7f3f6d Change my name a bit
edit 310154e Update README formatting and add blame
pick a5f4a0d Add cat-file

Then, when the script drops you to the command line, you reset that commit, take the changes that have been reset, and create multiple commits out of them.
When you save and exit the editor, Git rewinds to the parent of the first commit in your list, applies the first commit (f7f3f6d), applies the second (310154e), and drops you to the console.
There, you can do a mixed reset of that commit with git reset HEAD^, which effectively undoes that commit and leaves the modified files unstaged.
Now you can stage and commit files until you have several commits, and run git rebase --continue when you’re done:

$ git reset HEAD^
$ git add README
$ git commit -m 'Update README formatting'
$ git add lib/simplegit.rb
$ git commit -m 'Add blame'
$ git rebase --continue

Git applies the last commit (a5f4a0d) in the script, and your history looks like this:

$ git log -4 --pretty=format:"%h %s"
1c002dd Add cat-file
9b29157 Add blame
35cfb2b Update README formatting
f7f3f6d Change my name a bit

This changes the SHA-1s of the three most recent commits in your list, so make sure no changed commit shows up in that list that you’ve already pushed to a shared repository.
Notice that the last commit (f7f3f6d) in the list is unchanged.
Despite this commit being shown in the script, because it was marked as “pick” and was applied prior to any rebase changes, Git leaves the commit unmodified.

Deleting a commit

If you want to get rid of a commit, you can delete it using the rebase -i script.
In the list of commits, put the word “drop” before the commit you want to delete (or just delete that line from the rebase script):

pick 461cb2a This commit is OK
drop 5aecc10 This commit is broken

Because of the way Git builds commit objects, deleting or altering a commit will cause the rewriting of all the commits that follow it.
The further back in your repo’s history you go, the more commits will need to be recreated.
This can cause lots of merge conflicts if you have many commits later in the sequence that depend on the one you just deleted.

If you get partway through a rebase like this and decide it’s not a good idea, you can always stop.
Type git rebase --abort, and your repo will be returned to the state it was in before you started the rebase.

If you finish a rebase and decide it’s not what you want, you can use git reflog to recover an earlier version of your branch.
See Data Recovery for more information on the reflog command.

Note

Drew DeVault made a practical hands-on guide with exercises to learn how to use git rebase.
You can find it at: https://git-rebase.io/

The Nuclear Option: filter-branch

There is another history-rewriting option that you can use if you need to rewrite a larger number of commits in some scriptable way — for instance, changing your email address globally or removing a file from every commit.
The command is filter-branch, and it can rewrite huge swaths of your history, so you probably shouldn’t use it unless your project isn’t yet public and other people haven’t based work off the commits you’re about to rewrite.
However, it can be very useful.
You’ll learn a few of the common uses so you can get an idea of some of the things it’s capable of.

Caution

git filter-branch has many pitfalls, and is no longer the recommended way to rewrite history.
Instead, consider using git-filter-repo, which is a Python script that does a better job for most applications where you would normally turn to filter-branch.
Its documentation and source code can be found at https://github.com/newren/git-filter-repo.

Removing a File from Every Commit

This occurs fairly commonly.
Someone accidentally commits a huge binary file with a thoughtless git add ., and you want to remove it everywhere.
Perhaps you accidentally committed a file that contained a password, and you want to make your project open source.
filter-branch is the tool you probably want to use to scrub your entire history.
To remove a file named passwords.txt from your entire history, you can use the --tree-filter option to filter-branch:

$ git filter-branch --tree-filter 'rm -f passwords.txt' HEAD
Rewrite 6b9b3cf04e7c5686a9cb838c3f36a8cb6a0fc2bd (21/21)
Ref 'refs/heads/master' was rewritten

The --tree-filter option runs the specified command after each checkout of the project and then recommits the results.
In this case, you remove a file called passwords.txt from every snapshot, whether it exists or not.
If you want to remove all accidentally committed editor backup files, you can run something like git filter-branch --tree-filter 'rm -f *~' HEAD.

You’ll be able to watch Git rewriting trees and commits and then move the branch pointer at the end.
It’s generally a good idea to do this in a testing branch and then hard-reset your master branch after you’ve determined the outcome is what you really want.
To run filter-branch on all your branches, you can pass --all to the command.

Making a Subdirectory the New Root

Suppose you’ve done an import from another source control system and have subdirectories that make no sense (trunk, tags, and so on).
If you want to make the trunk subdirectory be the new project root for every commit, filter-branch can help you do that, too:

$ git filter-branch --subdirectory-filter trunk HEAD
Rewrite 856f0bf61e41a27326cdae8f09fe708d679f596f (12/12)
Ref 'refs/heads/master' was rewritten

Now your new project root is what was in the trunk subdirectory each time.
Git will also automatically remove commits that did not affect the subdirectory.

Changing Email Addresses Globally

Another common case is that you forgot to run git config to set your name and email address before you started working, or perhaps you want to open-source a project at work and change all your work email addresses to your personal address.
In any case, you can change email addresses in multiple commits in a batch with filter-branch as well.
You need to be careful to change only the email addresses that are yours, so you use --commit-filter:

$ git filter-branch --commit-filter '
        if [ "$GIT_AUTHOR_EMAIL" = "schacon@localhost" ];
        then
                GIT_AUTHOR_NAME="Scott Chacon";
                GIT_AUTHOR_EMAIL="schacon@example.com";
                git commit-tree "$@";
        else
                git commit-tree "$@";
        fi' HEAD

This goes through and rewrites every commit to have your new address.
Because commits contain the SHA-1 values of their parents, this command changes every commit SHA-1 in your history, not just those that have the matching email address.

Введение

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

Основная задача Git — гарантировать, что вы не потеряете внесенные изменений. Но эта система также предназначена для предоставления вам полного контроля над процессом разработки. В числе прочего вы сами определяете то, как выглядит история вашего проекта. Такая свобода создает и вероятность потери коммитов. Git предоставляет команды для перезаписи истории, но предупреждает, что использование таких команд может привести к потере данных.

В Git существует несколько механизмов хранения истории и сохранения изменений. Вот эти механизмы: commit --amend, git rebase и git reflog. Это мощные инструменты для настройки рабочего процесса. По окончании этого обучающего материала вы будете знать команды, которые позволят реструктурировать коммиты Git, и сможете избегать проблем, с которыми приходится сталкиваться при перезаписи истории.

Изменение последнего коммита: git commit --amend

Команда git commit --amend — это удобный способ изменить последний коммит. Она позволяет объединить проиндексированные изменения с предыдущим коммитом без создания нового коммита. Ее можно использовать для редактирования комментария к предыдущему коммиту без изменения состояния кода в нем. Но такое изменение не только редактирует последний коммит, но и полностью его заменяет. То есть измененный коммит станет новой сущностью с отдельной ссылкой. Для Git он будет выглядеть как новый коммит, который отмечен звездочкой (*) на схеме внизу. Существует несколько распространенных сценариев использования команды git commit --amend. В следующих разделах мы расскажем о примерах ее использования.

Git commit amend

Изменение комментария к последнему коммиту Git

Допустим, при выполнении коммита вы допустили ошибку в комментарии к нему. Выполнение этой команды в отсутствие проиндексированных файлов позволяет отредактировать комментарий к предыдущему коммиту без изменения состояния кода.

В процессе разработки регулярно случаются преждевременные коммиты. Очень просто забыть проиндексировать файл или использовать неправильный формат комментария к коммиту. Флаг --amend позволяет удобно исправить эти небольшие ошибки.

git commit --amend -m "an updated commit message"

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

Изменение файлов после коммита

В следующем примере показан распространенный сценарий разработки с использованием Git. Допустим, вы отредактировали несколько файлов и хотите сделать коммит за одну операцию. Но потом выясняется, что вы забыли добавить один из файлов в самом начале. Для того чтобы исправить эту ошибку, достаточно проиндексировать другой файл и выполнить коммит с флагом --amend:

# Edit hello.py and main.py
git add hello.py
git commit 
# Realize you forgot to add the changes from main.py 
git add main.py 
git commit --amend --no-edit

Флаг --no-edit позволит внести изменения в коммит без изменения комментария к нему. Итоговый коммит заменит неполный коммит. При этом все будет выглядеть так, словно изменения в файлах hello.py и main.py были сделаны за один коммит.

Не используйте amend для публичных коммитов

Измененные коммиты по сути являются новыми коммитами. При этом предыдущий коммит не останется в текущей ветке. Последствия этой операции аналогичны сбросу (reset) публичного состояния кода. Не изменяйте коммит, после которого уже начали работу другие разработчики. Эта ситуация только запутает разработчиков, и разрешить ее будет непросто.

Обзор

Если коротко, команда git commit --amend позволяет добавить новые проиндексированные изменения в последний коммит. С помощью коммита --amend можно добавлять изменения в индекс Git или удалять таковые из него. Если никаких изменений не проиндексировано, при использовании флага --amend вам все равно будет предложено изменить комментарий к последнему коммиту. Будьте осторожны при использовании флага --amend с коммитами, доступными другим членам команды. Изменение коммита, доступного другому пользователю, может привести к путанице и длительным устранениям конфликтов при слиянии.

Изменение старых или нескольких коммитов

Для изменения старых или нескольких коммитов используйте команду git rebase для объединения нескольких коммитов в новый базовый коммит. В стандартном режиме команда git rebase позволяет в буквальном смысле перезаписать историю: она автоматически применяет коммиты в текущей рабочей ветке к указателю head переданной ветки. Поскольку новые коммиты заменяют старые, команду git rebase запрещено применять к коммитам, которые стали доступны публично. Иначе история проекта исчезнет.

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

Изменение файлов после коммита

Во время операции rebase команда редактирования (e) остановит процесс на указанном коммите и позволит вам внести дополнительные изменения с помощью команды git commit --amend. Git прервет работу и выведет следующее сообщение:

Stopped at 5d025d1... formatting
You can amend the commit now, with

git commit --amend

Once you are satisfied with your changes, run

git rebase --continue

Несколько комментариев

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

  • Изменение комментария (r) приведет к остановке операции rebase и позволит вам переписать комментарий к отдельному коммиту.
  • При склеивании (s) во время rebase ко всем коммитам, отмеченным символом s, можно будет ввести один общий комментарий вместо нескольких отдельных. Подробности см. в разделе о склеивании коммитов ниже.
  • Эффект исправления (f) аналогичен склеиванию. В отличие от склеивания, исправление не прерывает rebase с открытием редактора для объединения комментариев к коммитам. В коммитах, отмеченных символом f, комментарии будут сброшены и заменены на комментарий предыдущего коммита.

Склеивайте коммиты для поддержания чистой истории

Команда склеивания (s) позволяет в полной мере понять смысл rebase. Склеивание позволяет указать коммиты, которые нужно объединить в предыдущие коммиты. Таким образом создается «чистая история». Во время перемещения Git будет исполнять указанную команду rebase для каждого коммита. В случае склеенных коммитов Git откроет выбранный текстовый редактор и предложит объединить комментарии к указанным коммитам. Этот процесс можно показать следующим образом:

Обучающие материалы по Git: пример команды git rebase -i

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

Современные решения для хостинга Git (например, Bitbucket) предлагают возможности «автосклеивания» при слиянии. Эти возможности позволяют автоматически выполнять rebase и склеивать коммиты ветки при использовании интерфейса решений для хостинга. Дополнительную информацию см. в разделе «Склеивание коммитов при слиянии ветки Git в Bitbucket».

Обзор

Команда git rebase позволяет изменять историю, а интерактивное выполнение rebase «подчищает» за вами следы. Теперь вы можете совершать и исправлять ошибки, оттачивая свою работу и сохраняя чистую, линейную историю проекта.

Страховка: git reflog

Справочные журналы (reflog) — это механизм, который используется в Git для регистрации обновлений, применяемых к концам веток и другим ссылкам на коммиты. Reflog позволяет вернуться к коммитам, даже если на них нет ссылок из какой-либо ветки или метки. После перезаписи истории reflog содержит информацию о старом состоянии веток и позволяет при необходимости вернуться к этому состоянию. Каждый раз при обновлении конца ветки любым способом (переключение веток, загрузка новых изменений, перезапись истории или просто добавление новых коммитов) в reflog добавляется новая запись. В данном разделе мы рассматриваем команду git reflog и стандартные примеры ее использования.

Использование

Отображается reflog локального репозитория.

git reflog --relative-date

Отображается reflog с относительными датами (например, 2 недели назад).

Пример

Чтобы понять команду git reflog, давайте разберем пример.

0a2e358 HEAD@{0}: reset: moving to HEAD~2
0254ea7 HEAD@{1}: checkout: moving from 2.2 to main
c10f740 HEAD@{2}: checkout: moving from main to 2.2

В приведенной выше команде reflog показан переход из главной ветки в ветку 2.2 и обратно. Отсюда можно выполнить жесткий сброс к старому коммиту. Последнее действие указано в верхней строчке с пометкой HEAD@{0}.

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

git reset --hard 0254ea7

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

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

Дополнительную информацию см. на странице git reflog.

Резюме

В данной статье мы рассмотрели несколько способов изменения истории Git и отмены изменений в Git. Мы вкратце рассмотрели процесс git rebase. Вот основные заключения.

  • Существует несколько способов переписать историю в Git.
  • Используйте команду git commit --amend для изменения последнего комментария.
  • Используйте команду git commit --amend, чтобы внести изменения в последний коммит.
  • Используйте команду git rebase для объединения коммитов и изменения истории ветки.
  • Команда git rebase -i дает более точный контроль над изменениями истории, чем обычный вариант git rebase.

Узнайте больше об описанных командах на соответствующих страницах:

  • git rebase
  • git reflog

Use git rebase. For example, to modify commit bbc643cd, run:

$ git rebase --interactive 'bbc643cd^'

Please note the caret ^ at the end of the command, because you need actually to rebase back to the commit before the one you wish to modify.

In the default editor, modify pick to edit in the line mentioning bbc643cd.

Save the file and exit. git will interpret and automatically execute the commands in the file. You will find yourself in the previous situation in which you just had created commit bbc643cd.

At this point, bbc643cd is your last commit and you can easily amend it. Make your changes and then commit them with the command:

$ git commit --all --amend --no-edit

After that, return back to the previous HEAD commit using:

$ git rebase --continue

WARNING: Note that this will change the SHA-1 of that commit as well as all children — in other words, this rewrites the history from that point forward. You can break repos doing this if you push using the command git push --force.

Mateen Ulhaq's user avatar

Mateen Ulhaq

23k16 gold badges89 silver badges130 bronze badges

answered Jul 27, 2009 at 5:28

ZelluX's user avatar

ZelluXZelluX

67k18 gold badges70 silver badges104 bronze badges

29

Use the awesome interactive rebase:

git rebase -i @~9   # Show the last 9 commits in a text editor

Find the commit you want, change pick to e (edit), and save and close the file. Git will rewind to that commit, allowing you to either:

  • use git commit --amend to make changes, or
  • use git reset @~ to discard the last commit, but not the changes to the files (i.e. take you to the point you were at when you’d edited the files, but hadn’t committed yet).

The latter is useful for doing more complex stuff like splitting into multiple commits.

Then, run git rebase --continue, and Git will replay the subsequent changes on top of your modified commit. You may be asked to fix some merge conflicts.

Note: @ is shorthand for HEAD, and ~ is the commit before the specified commit.

Read more about rewriting history in the Git docs.

Don’t be afraid to rebase

ProTip™:   Don’t be afraid to experiment with «dangerous» commands that rewrite history* — Git doesn’t delete your commits for 90 days by default; you can find them in the reflog:

$ git reset @~3   # go back 3 commits
$ git reflog
c4f708b HEAD@{0}: reset: moving to @~3
2c52489 HEAD@{1}: commit: more changes
4a5246d HEAD@{2}: commit: make important changes
e8571e4 HEAD@{3}: commit: make some changes
... earlier commits ...
$ git reset 2c52489
... and you're back where you started

* Watch out for options like --hard and --force though — they can discard data.
* Also, don’t rewrite history on any branches you’re collaborating on.


On many systems, git rebase -i will open up Vim by default. Vim doesn’t work like most modern text editors, so take a look at how to rebase using Vim. If you’d rather use a different editor, change it with git config --global core.editor your-favorite-text-editor.

Community's user avatar

answered Apr 29, 2015 at 17:50

Zaz's user avatar

ZazZaz

45.4k11 gold badges82 silver badges97 bronze badges

7

Interactive rebase with --autosquash is something I frequently use when I need to fixup previous commits deeper in the history. It essentially speeds up the process that ZelluX’s answer illustrates, and is especially handy when you have more than one commit you need to edit.

From the documentation:

--autosquash

When the commit log message begins with «squash! …​» (or «fixup! …​»), and there is a commit whose title begins with the same …​, automatically modify the todo list of rebase -i so that the commit marked for squashing comes right after the commit to be modified

Assume you have a history that looks like this:

$ git log --graph --oneline
* b42d293 Commit3
* e8adec4 Commit2
* faaf19f Commit1

and you have changes that you want to amend to Commit2 then commit your changes using

$ git commit -m "fixup! Commit2"

alternatively you can use the commit-sha instead of the commit message, so "fixup! e8adec4 or even just a prefix of the commit message.

Then initiate an interactive rebase on the commit before

$ git rebase e8adec4^ -i --autosquash

your editor will open with the commits already correctly ordered

pick e8adec4 Commit2
fixup 54e1a99 fixup! Commit2
pick b42d293 Commit3

all you need to do is save and exit

answered Sep 29, 2015 at 17:59

thrau's user avatar

thrauthrau

2,7803 gold badges24 silver badges32 bronze badges

5

Run:

$ git rebase --interactive commit_hash^

each ^ indicates how many commits back you want to edit, if it’s only one (the commit hash that you specified), then you just add one ^.

Using Vim you change the words pick to reword for the commits you want to change, save and quit(:wq). Then git will prompt you with each commit that you marked as reword so you can change the commit message.

Each commit message you have to save and quit(:wq) to go to the next commit message

If you want to exit without applying the changes, press :q!

EDIT: to navigate in vim you use j to go up, k to go down, h to go left, and l to go right( all this in NORMAL mode, press ESC to go to NORMAL mode ).
To edit a text, press i so that you enter the INSERT mode, where you insert text.
Press ESC to go back to NORMAL mode :)

UPDATE: Here’s a great link from github listing How to undo (almost) anything with git

answered Jul 2, 2015 at 19:11

betoharres's user avatar

betoharresbetoharres

1,6662 gold badges19 silver badges25 bronze badges

7

Based on Documentation

Amending the message of older or multiple commit messages

git rebase -i HEAD~3 

The above displays a list of the last 3 commits on the current branch, change 3 to something else if you want more. The list will look similar to the following:

pick e499d89 Delete CNAME
pick 0c39034 Better README
pick f7fde4a Change the commit message but push the same commit.

Replace pick with reword before each commit message you want to change. Let say you change the second commit in the list, your file will look like the following:

pick e499d89 Delete CNAME
reword 0c39034 Better README
pick f7fde4a Change the commit message but push the same commit.

Save and close the commit list file, this will pop up a new editor for you to change your commit message, change the commit message and save.

Finally, force-push the amended commits.

git push --force

sikander's user avatar

sikander

2,27616 silver badges23 bronze badges

answered May 17, 2018 at 8:38

justMe's user avatar

justMejustMe

2,10016 silver badges20 bronze badges

2

Completely non-interactive command(1)

I just thought I’d share an alias that I’m using for this. It’s based on non-interactive interactive rebase. To add it to your git, run this command (explanation given below):

git config --global alias.amend-to '!f() { SHA=`git rev-parse "$1"`; git commit --fixup "$SHA" && GIT_SEQUENCE_EDITOR=true git rebase --interactive --autosquash "$SHA^"; }; f'

Or, a version that can also handle unstaged files (by stashing and then un-stashing them):

git config --global alias.amend-to '!f() { SHA=`git rev-parse "$1"`; git stash -k && git commit --fixup "$SHA" && GIT_SEQUENCE_EDITOR=true git rebase --interactive --autosquash "$SHA^" && git stash pop; }; f'

The biggest advantage of this command is the fact that it’s no-vim.


(1)given that there are no conflicts during rebase, of course

Usage

git amend-to <REV> # e.g.
git amend-to HEAD~1
git amend-to aaaa1111

The name amend-to seems appropriate IMHO. Compare the flow with --amend:

git add . && git commit --amend --no-edit
# vs
git add . && git amend-to <REV>

Explanation

  • git config --global alias.<NAME> '!<COMMAND>' — creates a global git alias named <NAME> that will execute non-git command <COMMAND>
  • f() { <BODY> }; f — an «anonymous» bash function.
  • SHA=`git rev-parse "$1"`; — converts the argument to git revision, and assigns the result to variable SHA
  • git commit --fixup "$SHA" — fixup-commit for SHA. See git-commit docs
  • GIT_SEQUENCE_EDITOR=true git rebase --interactive --autosquash "$SHA^"
    • git rebase --interactive "$SHA^" part has been covered by other answers.
    • --autosquash is what’s used in conjunction with git commit --fixup, see git-rebase docs for more info
    • GIT_SEQUENCE_EDITOR=true is what makes the whole thing non-interactive. This hack I learned from this blog post.

answered Feb 27, 2018 at 1:47

Dethariel's user avatar

DetharielDethariel

3,2643 gold badges33 silver badges47 bronze badges

7

If for some reason you don’t like interactive editors, you can use git rebase --onto.

Say you want to modify Commit1. First, branch from before Commit1:

git checkout -b amending [commit before Commit1]

Second, grab Commit1 with cherry-pick:

git cherry-pick Commit1

Now, amend your changes, creating Commit1':

git add ...
git commit --amend -m "new message for Commit1"

And finally, after having stashed any other changes, transplant the rest of your commits up to master on top of your
new commit:

git rebase --onto amending Commit1 master

Read: «rebase, onto the branch amending, all commits between Commit1 (non-inclusive) and master (inclusive)». That is, Commit2 and Commit3, cutting the old Commit1 out entirely. You could just cherry-pick them, but this way is easier.

Remember to clean up your branches!

git branch -d amending

answered Oct 22, 2016 at 12:19

FeepingCreature's user avatar

FeepingCreatureFeepingCreature

3,5882 gold badges25 silver badges25 bronze badges

3

git stash + rebase automation

For when I need to modify an old commit a lot of times for Gerrit reviews, I’ve been doing:

git-amend-old() (
  # Stash, apply to past commit, and rebase the current branch on to of the result.
  current_branch="$(git rev-parse --abbrev-ref HEAD)"
  apply_to="$1"
  git stash
  git checkout "$apply_to"
  git stash apply
  git add -u
  git commit --amend --no-edit
  new_sha="$(git log --format="%H" -n 1)"
  git checkout "$current_branch"
  git rebase --onto "$new_sha" "$apply_to"
)

GitHub upstream.

Usage:

  • modify source file, no need to git add if already in repo
  • git-amend-old $old_sha

I like this over --autosquash as it does not squash other unrelated fixups.

answered Dec 3, 2018 at 16:02

Ciro Santilli OurBigBook.com's user avatar

2

The best option is to use «Interactive rebase command».

The git rebase command is incredibly powerful. It allows you to edit
commit messages, combine commits, reorder them …etc.

Every time you rebase a commit a new SHA will be created for each
commit regardless of the content will be changed or not! You should be
careful when to use this command cause it may have drastic
implications especially if you work in collaboration with other
developers. They may start working with your commit while you’re
rebasing some. After you force to push the commits they will be out of
sync and you may find out later in a messy situation. So be careful!

It’s recommended to create a backup branch before rebasing so
whenever you find things out of control you can return back to the
previous state.

Now how to use this command?

git rebase -i <base> 

-i stand for «interactive». Note that you can perform a rebase in non-interactive mode. ex:

#interactivly rebase the n commits from the current position, n is a given number(2,3 ...etc)
git rebase -i HEAD~n 

HEAD indicates your current location(can be also branch name or commit SHA). The ~n means «n beforeé, so HEAD~n will be the list of «n» commits before the one you are currently on.

git rebase has different command like:

  • p or pick to keep commit as it is.
  • r or reword: to keep the commit’s content but alter the commit message.
  • s or squash: to combine this commit’s changes into the previous commit(the commit above it in the list).
  • … etc.

    Note: It’s better to get Git working with your code editor to make things simpler. Like for example if you use visual code you can add like this git config --global core.editor "code --wait". Or you can search in Google how to associate you preferred your code editor with GIT.

Example of git rebase

I wanted to change the last 2 commits I did so I process like this:

  1. Display the current commits:
    #This to show all the commits on one line
    $git log --oneline
    4f3d0c8 (HEAD -> documentation) docs: Add project description and included files"
    4d95e08 docs: Add created date and project title"
    eaf7978 (origin/master , origin/HEAD, master) Inital commit
    46a5819 Create README.md
    
  2. Now I use git rebase to change the 2 last commits messages:
    $git rebase -i HEAD~2
    It opens the code editor and show this:

    pick 4d95e08 docs: Add created date and project title
    pick 4f3d0c8 docs: Add project description and included files
    
    # Rebase eaf7978..4f3d0c8 onto eaf7978 (2 commands)
    #
    # Commands:
    # p, pick <commit> = use commit
    # r, reword <commit> = use commit, but edit the commit message
    ...
    

    Since I want to change the commit message for this 2 commits. So I will type r or reword in place of pick. Then Save the file and close the tab.
    Note that rebase is executed in a multi-step process so the next step is to update the messages. Note also that the commits are displayed in reverse chronological order so the last commit is displayed in that one and the first commit in the first line and so forth.

  3. Update the messages:
    Update the first message:

    docs: Add created date and project title to the documentation "README.md"
    
    # Please enter the commit message for your changes. Lines starting
    # with '#' will be ignored, and an empty message aborts the commit.
    ...
    

    save and close
    Edit the second message

    docs: Add project description and included files to the documentation "README.md"
    
    # Please enter the commit message for your changes. Lines starting
    # with '#' will be ignored, and an empty message aborts the commit.
    ...
    

    save and close.

  4. You will get a message like this by the end of the rebase: Successfully rebased and updated refs/heads/documentation which means that you succeed. You can display the changes:

    5dff827 (HEAD -> documentation) docs: Add project description and included files to the documentation "README.md"
    4585c68 docs: Add created date and project title to the documentation "README.md"
    eaf7978 (origin/master, origin/HEAD, master) Inital commit
    46a5819 Create README.md
    

    I wish that may help the new users :).

answered Nov 15, 2019 at 17:59

DINA TAKLIT's user avatar

DINA TAKLITDINA TAKLIT

5,9129 gold badges62 silver badges72 bronze badges

Automated interactive rebase edit followed by commit revert ready for a do-over

I found myself fixing a past commit frequently enough that I wrote a script for it.

Here’s the workflow:

  1. git commit-edit <commit-hash>
    

    This will drop you at the commit you want to edit.

  2. Fix and stage the commit as you wish it had been in the first place.

    (You may want to use git stash save to keep any files you’re not committing)

  3. Redo the commit with --amend, eg:

    git commit --amend
    
  4. Complete the rebase:

    git rebase --continue
    

For the above to work, put the below script into an executable file called git-commit-edit somewhere in your $PATH:

#!/bin/bash

set -euo pipefail

script_name=${0##*/}

warn () { printf '%s: %sn' "$script_name" "$*" >&2; }
die () { warn "$@"; exit 1; }

[[ $# -ge 2 ]] && die "Expected single commit to edit. Defaults to HEAD~"

# Default to editing the parent of the most recent commit
# The most recent commit can be edited with `git commit --amend`
commit=$(git rev-parse --short "${1:-HEAD~}")
message=$(git log -1 --format='%h %s' "$commit")

if [[ $OSTYPE =~ ^darwin ]]; then
  sed_inplace=(sed -Ei "")
else
  sed_inplace=(sed -Ei)
fi

export GIT_SEQUENCE_EDITOR="${sed_inplace[*]} "' "s/^pick ('"$commit"' .*)/edit \1/"'
git rebase --quiet --interactive --autostash --autosquash "$commit"~
git reset --quiet @~ "$(git rev-parse --show-toplevel)"  # Reset the cache of the toplevel directory to the previous commit
git commit --quiet --amend --no-edit --allow-empty  #  Commit an empty commit so that that cache diffs are un-reversed

echo
echo "Editing commit: $message" >&2
echo

answered Sep 14, 2018 at 3:32

Tom Hale's user avatar

Tom HaleTom Hale

37.8k28 gold badges176 silver badges230 bronze badges

0

Changing the Last Commit:

git commit --amend
// or
git commit --amend -m "an updated commit message"

Don’t amend public commits
Amended commits are actually entirely new commits and the previous commit will no longer be on your current branch.

For example, if you want to change the last three commit messages, or any of the commit messages in that group, you supply as an argument to git rebase -i the parent of the last commit you want to edit, which is HEAD~2^ or HEAD~3. It may be easier to remember the ~3 because you’re trying to edit the last three commits, but keep in mind that you’re actually designating four commits ago, the parent of the last commit you want to edit:

$ git rebase -i HEAD~3

know more

answered Dec 5, 2021 at 21:00

Came to this approach (and it is probably exactly the same as using interactive rebase) but for me it’s kind of straightforward.

Note: I present this approach for the sake of illustration of what you can do rather than an everyday alternative. Since it has many steps (and possibly some caveats.)

Say you want to change commit 0 and you are currently on feature-branch

some-commit---0---1---2---(feature-branch)HEAD

Checkout to this commit and create a quick-branch. You can also clone your feature branch as a recovery point (before starting).

?(git checkout -b feature-branch-backup)
git checkout 0
git checkout -b quick-branch

You will now have something like this:

0(quick-branch)HEAD---1---2---(feature-branch)

Stage changes, stash everything else.

git add ./example.txt
git stash

Commit changes and checkout back to feature-branch

git commit --amend
git checkout feature-branch

You will now have something like this:

some-commit---0---1---2---(feature-branch)HEAD
           
             ---0'(quick-branch)

Rebase feature-branch onto quick-branch (resolve any conflicts along the way). Apply stash and remove quick-branch.

git rebase quick-branch
git stash pop
git branch -D quick-branch

And you end up with:

some-commit---0'---1'---2'---HEAD(feature-branch)

Git will not duplicate (although I can’t really say to what extent) the 0 commit when rebasing.

Note: all commit hashes are changed starting from the commit we originally intended to change.

answered Jun 1, 2016 at 11:57

Olga's user avatar

OlgaOlga

1,6281 gold badge22 silver badges31 bronze badges

To get a non-interactive command, put a script with this content in your PATH:

#!/bin/sh
#
# git-fixup
# Use staged changes to modify a specified commit
set -e
cmt=$(git rev-parse $1)
git commit --fixup="$cmt"
GIT_EDITOR=true git rebase -i --autosquash "$cmt~1"

Use it by staging your changes (with git add) and then run git fixup <commit-to-modify>. Of course, it will still be interactive if you get conflicts.

answered Jan 16, 2018 at 15:27

Pelle Nilsson's user avatar

1

I solved this,

1) by creating new commit with changes i want..

r8gs4r commit 0

2) i know which commit i need to merge with it. which is commit 3.

so, git rebase -i HEAD~4 # 4 represents recent 4 commit (here commit 3 is in 4th place)

3) in interactive rebase recent commit will located at bottom. it will looks alike,

pick q6ade6 commit 3
pick vr43de commit 2
pick ac123d commit 1
pick r8gs4r commit 0

4) here we need to rearrange commit if you want to merge with specific one. it should be like,

parent
|_child

pick q6ade6 commit 3
f r8gs4r commit 0
pick vr43de commit 2
pick ac123d commit 1

after rearrange you need to replace p pick with f (fixup will merge without commit message) or s (squash merge with commit message can change in run time)

and then save your tree.

now merge done with existing commit.

Note: Its not preferable method unless you’re maintain on your own. if
you have big team size its not a acceptable method to rewrite git
tree will end up in conflicts which you know other wont. if you want
to maintain you tree clean with less commits can try this and if its
small team otherwise its not preferable…..

answered Jan 5, 2018 at 18:35

Mohideen bin Mohammed's user avatar

1

For me it was for removing some credentials from a repo.
I tried rebasing and ran into a ton of seemingly unrelated conflicts along the way when trying to rebase —continue.
Don’t bother attempting to rebase yourself, use the tool called BFG (brew install bfg) on mac.

answered Nov 7, 2017 at 7:24

Pellet's user avatar

PelletPellet

2,1521 gold badge27 silver badges20 bronze badges

I do the following, including changing the date and time of local commits:

git rebase -i HEAD~6

~6 is the amount of commit history to display.

  • Change from pick to edit the commits to be edited.
  • Then I save and exit (In ubuntu: Ctrl+O to save and Ctrl+X to exit)
    1. Then I run: git commit --amend --date="2022-09-02T19:10:04" -m "NEW_MSG"
    1. If the edit is opened, just save and exit.
    1. Then to confirm and go to the next commit or finish if it is the last one, I execute: git rebase --continue

If there are more commits to edit, it is repeated from point 1

Finally I validate the changes and if everything is ok, I do the push

answered Sep 3, 2022 at 0:53

JxDarkAngel's user avatar

JxDarkAngelJxDarkAngel

8419 silver badges4 bronze badges

If you haven’t already pushed the commits then you can go back to a previous commit using git reset HEAD^[1,2,3,4...]

For example

git commit <file1> -m "Updated files 1 and 2"
git commit <file3> -m "Updated file 3"

Oops, forgot to add file2 to the first commit…

git reset HEAD^1 // because I only need to go back 1 commit

git add <file2>

This will add file2 to the first commit.

answered May 8, 2019 at 13:30

rharvey's user avatar

rharveyrharvey

1,9771 gold badge28 silver badges23 bronze badges

Well, this solution might sound very silly, but can save you in certain conditions.

A friend of mine just ran into accidentally committing very some huge files (four auto-generated files ranging between 3GB to 5GB each) and then made some additional code commits on top of that before realizing the problem that git push wasn’t working any longer!

The files had been listed in .gitignore but after renaming the container folder, they got exposed and committed! And now there were a few more commits of the code on top of that, but push was running forever (trying to upload GB of data!) and finally would fail due to Github’s file size limits.

The problem with interactive rebase or anything similar was that they would deal with poking around these huge files and would take forever to do anything. Nevertheless, after spending almost an hour in the CLI, we weren’t sure if the files (and deltas) are actually removed from the history or simply not included in the current commits. The push wasn’t working either and my friend was really stuck.

So, the solution I came up with was:

  1. Rename current git folder to ~/Project-old.
  2. Clone the git folder again from github (to ~/Project).
  3. Checkout to the same branch.
  4. Manually cp -r the files from ~/Project-old folder to ~/Project.
  5. Make sure the massive files, that are not needed to be checked in are mved, and included in .gitignore properly.
  6. Also make sure you don’t overwrite .git folder in the recently-cloned ~/Project by the old one. That’s where the logs of the problematic history lives!
  7. Now review the changes. It should be the union of all the recent commits, excluding the problematic files.
  8. Finally commit the changes, and it’s good to be push‘ed.

The biggest problem with this solution is, it deals with manual copying some files, and also it merges all the recent commits into one (obviously with a new commit-hash.) B

The big benefits are that, it is very clear in every step, it works great for huge files (as well as sensitive ones), and it doesn’t leave any trace in history behind!

answered Jun 12, 2019 at 9:47

Aidin's user avatar

AidinAidin

21.5k7 gold badges68 silver badges61 bronze badges

I had same problem and this this:

  1. First duplicated the branch as x,
  2. Then hard rest to where you want to go back to
  3. Then amend new changes
  4. After that cherry pick all other changes from the original branch
  5. Checkout the original branch
  6. Reset the original branch before the amended reversion
  7. rebase into x branch

My rider git log:

git checkout -b x 77164a510f1c17ed650b87c2ebf0f7762ac6b2a2 --
git reset --hard 0d038b5e3e3e2adef4bd6aab7653f922c3fdc63f
git add --ignore-errors -A -f -- src/Mores.Warehouse.Core.Api.ClientSdk/Mores.Warehouse.Core.Api.ClientSdk.csproj
git commit -F C:UsersHassanAppDataLocalTempgit-commit-msg-.txt --amend --
git cherry-pick 77164a510f1c17ed650b87c2ebf0f7762ac6b2a2
git checkout feature/MOR-2947 --
git reset --hard 0d038b5e3e3e2adef4bd6aab7653f922c3fdc63f
git rebase x

answered Nov 29, 2022 at 8:40

Hassan Faghihi's user avatar

Hassan FaghihiHassan Faghihi

1,7841 gold badge39 silver badges52 bronze badges

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

Изменение последнего сообщения о фиксации

Последнее сообщение о фиксации можно изменить с помощью команды git commit --amend.

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

Фиксация не отправлена на сайт

Если фиксация существует только в локальном репозитории и не была отправлена в GitHub.com, вы можете изменить сообщение фиксации git commit --amend с помощью команды .

  1. В командной строке перейдите к репозиторию, содержащему фиксацию, которую нужно изменить.

  2. Введите git commit --amend и нажмите клавишу ВВОД.

  3. В текстовом редакторе измените сообщение о фиксации и сохраните фиксацию.

    • Вы можете добавить соавтора, добавив к фиксации заключительную часть. Дополнительные сведения см. в разделе Создание фиксации с несколькими авторами.

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

Новая фиксация и сообщение появятся в GitHub.com при следующей отправке.

Вы можете изменить текстовый редактор по умолчанию для GIT с помощью параметра core.editor. Дополнительные сведения см. в разделе Базовая настройка клиента в руководстве по GIT.

Изменение более старых или нескольких сообщений о фиксациях

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

Мы настоятельно не рекомендуем выполнять принудительную отправку, так как это изменяет журнал репозитория. При принудительной отправке пользователям, которые уже клонировали репозиторий, придется вручную исправить свой локальный журнал. Дополнительные сведения см. в разделе Восстановление после вышестоящего перемещения изменения из одной ветви в другую в руководстве по GIT.

Изменение сообщения для последней отправленной фиксации

  1. Выполните описанные выше действия, чтобы изменить сообщение о фиксации.
  2. Используйте команду push --force-with-lease, чтобы принудительно отправить фиксацию вместо старой.
    $ git push --force-with-lease origin EXAMPLE-BRANCH

Изменение сообщения для старой фиксации или нескольких фиксаций

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

  1. В командной строке перейдите к репозиторию, содержащему фиксацию, которую нужно изменить.

  2. Используйте команду git rebase -i HEAD~n, чтобы отобразить список n последних фиксаций в текстовом редакторе по умолчанию.

    # Displays a list of the last 3 commits on the current branch
    $ git rebase -i HEAD~3

    Список будет выглядеть примерно так:

    pick e499d89 Delete CNAME
    pick 0c39034 Better README
    pick f7fde4a Change the commit message but push the same commit.
    
    # Rebase 9fdb3bd..f7fde4a onto 9fdb3bd
    #
    # Commands:
    # p, pick = use commit
    # r, reword = use commit, but edit the commit message
    # e, edit = use commit, but stop for amending
    # s, squash = use commit, but meld into previous commit
    # f, fixup = like "squash", but discard this commit's log message
    # x, exec = run command (the rest of the line) using shell
    #
    # These lines can be re-ordered; they are executed from top to bottom.
    #
    # If you remove a line here THAT COMMIT WILL BE LOST.
    #
    # However, if you remove everything, the rebase will be aborted.
    #
    # Note that empty commits are commented out
  3. Замените pick на reword перед каждым сообщением о фиксации, которое нужно изменить.

    pick e499d89 Delete CNAME
    reword 0c39034 Better README
    reword f7fde4a Change the commit message but push the same commit.
  4. Сохраните и закройте файл со списком фиксаций.

  5. В каждом полученном файле фиксации введите новое сообщение о фиксации, сохраните файл и закройте его.

  6. Когда вы будете готовы отправить изменения в GitHub, используйте команду push —force, чтобы принудительно отправить старую фиксацию.

    $ git push --force origin EXAMPLE-BRANCH

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

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

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

Дополнительные материалы

  • Подписывание фиксаций

Перевод проекта First Aid Git от команды HTML Academy.

Поговорим о решении проблем с Git.

Восстановление накопленных изменений

В том случае, если изменения, внесённые пользователем, находятся в режиме накопления, применить их к ветке можно с помощью команды git stash apply. Также можно запустить git diff — эта команда поможет выявить различия. Для того, чтобы затем избавиться от накопленных данных, нужно запустить команду:

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

git stash list затем можно применить его, воспользовавшись его индексом:

Необходимо учитывать, что отсчёт индексов ведётся от нуля.

Восстановление удалённого тега

В том случае, если необходимо восстановить случайно удалённый тег, начать можно с его поиска:

git fsck --unreachable | grep tag

После того, как нужный тег найден, его следует восстановить:

git update-ref refs/tags/название-тега

Восстановление удалённого файла

Если вы случайно удалили файл, его можно быстро восстановить:

Если требуется восстановить файл из конкретной временной точки истории коммитов, следует узнать хеш нужного коммита и запустить команду:

git checkout $commit~1 myfile.txt

Восстановление удалённой ветки

С помощью комманды git reflog можно узнать хеш (SHA1) последнего коммита в удалённой ветке. Скопируйте этот хеш и используйте в команде:

После этого восстановить удалённую ветку можно будет вот такой командой:

git checkout -b <название-ветки>

Изменение сообщения коммита перед его отправкой

Изменить сообщение коммита можно с помощью команды git commit --amend, она откроет редактор, в котором можно будет внести необходимые поправки в последнее сообщение.

Сообщение можно изменить и напрямую с помощью команды

git commit --amend -m "Новое прекрасное сообщение"

Изменение сообщения коммита после его отправки

В данном случае процесс занимает два шага. Сначала нужно изменить сообщение с помощью комманды git commit --amend, а затем перезаписать историю коммитов локальной ветки: git push <remote> <branch> --force

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

Использование алиасов команд в командной строке

Устали каждый раз печатать git status? Этой команде можно присвоить простой алиас, который проще и быстрее вбивать в git.

git config --global alias.st status

— теперь нужно писать только git st

Можно пойти дальше и присвоить алиасы более сложным командам:

git config --global alias.logme 'log -p --author=Rob'

Теперь алиас git logme будет выводить все наши коммиты.

Коммит в неправильную ветку

Нужно переключиться на новую ветку, которую вы забыли предварительно создать:

git checkout -b название-новой-ветки.

А затем переключиться к оригинальной ветке:

git checkout название-оригинальной-ветки

…и «откатиться» до последнего коммита, который нужно сохранить.

Чтобы это сделать, можно воспользоваться командой git log и сохранить хеш (SHA1) последнего коммита, который нужно оставить.. Например, это a31a45c.

Теперь его нужно сбросить: git reset --hard a31a45c и отправить получившийся результат.

Предупреждение: Убедитесь в том, что никто не отправлял коммиты в оригинальную ветку во время выполнения вышеописанных процедур, в противном случае эти изменения будут потеряны!

Обновление конкретного подмодуля

Чтобы обновить конкретный подмодуль в репозитории, нужно добавить путь к подмодулю:

git submodule update --remote --merge <path>

Откат к конкретному коммиту в истории

Если вас не очень беспокоят изменения в локальном репозитории, то можно «откатиться» к конкретному коммиту в истории с помощью команды:

Эта команда установит HEAD на конкретный коммит. Также можно воспользоваться хешем коммита.

Отмена коммита до публикации изменений

Если вы сделали коммит, который впоследствии понадобилось отредактировать или полностью стереть, поможет команда git reset.

git reset HEAD~1 # отменить последний коммит, сохранить изменения
git reset --hard HEAD~1 # отменить последний коммит, стереть изменения

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

Чтобы сохранить сообщение коммита, наберите: :

Отмена коммита после отправки его в master-репозиторий

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

Отмена только коммита, который является вторым после последнего:

Простая отмена последнего коммита:

Отмена локальных изменений файлов

Простейшим способом избавиться от нежелательных изменений для файлов и папок является восстановление состояния последнего коммита. Сделать это можно с помощью специальной команды:

Кроме того, можно восстановить конкретный путь к файлу:

git checkout -- путь-до-файла

Отображение всех коммитов одного файла

Если вы хотите просмотреть все коммиты с изменениями конкретного файла, воспользуйтесь командой git log --follow -p -- myfile

Аргумент —follow позволяет вывести все изменения над файлом, даже если в процессе работы он был переименован.

Если опустить опцию -p, то система выведет только сообщения коммитов, но не их содержимое.

Отображения числа коммитов от каждого участника

Хотите узнать, сколько коммитов сделал каждый участник команды?

Эта команда выведет список, отсортированный в порядке убывания количества коммитов: git shortlog -s -n

Отобразить коммиты, содержащие удалённые файлы

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

git log --diff-filter=D --summary

Она покажет список коммитов, в которых удалялись файлы.

Отсортировать коммиты по автору

Чтобы вывести список коммитов, отфильтрованных по автору, следует воспользоваться следующей командой:

git log --author="Имя автора"

Очистка всех скрытых состояний

Очистить все скрытые состояния можно следующей командой:

Переименование локальной и удалённой ветки

Предложим, у вас есть ветка «fix-bug25», которую вы захотели переименовать в «hotfix-users». Прежде всего, нужно будет изменить локальную ветку:

git branch -m fix-bug25 hotfix-users

А затем — удалённую ветку: переименовать её напрямую нельзя, поэтому нужно будет её удалить, и затем опубликовать заново уже с новым именем. Прежде чем приступать к этим процедурам, следует убедиться, что никто из членов команды не работает с этой веткой! Удаляем ветку: git push origin :fix-bug25

А теперь заново публикуем её с новым именем: git push origin hotfix-users

Переименование тега

Чтобы переименовать существующий тег:

git tag новое-название-тега старое-название-тега
git tag -d старое-название-тега
git push origin :refs/tags/старое-название-тега
git push --tags

Перестать отслеживать существующие файлы

Если вы хотите перестать отслеживать файлы, которые уже есть в репозитории, но при этом желаете сохранить его локально, осуществите коммит изменений и запустите команду:

Она удалит изменённые файлы из зоны подготовленных файлов (staging area). Затем нужно запустить команду:

и отправить изменения.

Подготовка удалённых файлов

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

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

Поиск конкретного сообщения во всех коммитах

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

Пометить конфликтующий файл, как разрешённый

Чтобы пометить один или несколько конфликтующих файлов, как разрешённые, чтобы их можно было нормально изменять, воспользуйтесь командой:

Затем можно запустить git commit, чтобы разрешить конфликты и опубликовать изменения.

Просмотр всех неотправленных коммитов

Чтобы просмотреть все коммиты, которые ещё не были отправлены в соответствующие ветки, воспользуйтесь следующей командой:

git log --branches --not --remotes

Кроме того, можно использовать:

git log origin/master..HEAD

Просмотр старой ревизии файла

Существует возможность просмотреть содержимое файла в конкретный момент времени в прошлом. Для этого нужно использовать команду:

git show commitHash:myfile.txt

Публикация локальной ветки для удалённого редактирования

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

git push -u origin название-моей-новой-ветки

Теперь они тоже смогут вносить изменения в эту ветку.

Сброс локальной ветки до состояния удалённой

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

Прежде всего нужно получить свежие обновления из удалённой ветки:

git fetch название-удалённой-ветки.

А затем нужно сообщить git, что локальную ветку следует «откатить» до состояния удалённой:

git reset --hard origin/название-локальной-ветки.

При наличии коммита, который нужно сохранить, перед сбросом нужно создать новую ветку и произвести коммит: git commit -m «Обновление»

git branch название-новой-ветки

Синхронизировать ветку с master-репозиторием

Чтобы синхронизировать последние изменения в репозитории master (или с любой другой веткой, с которой вы работали) необходимо «перебазировать» локальную ветку. Предположим, вы работаете над веткой foobar:

А затем осуществляете «перебазирование»:

После этого будут применены коммиты origin из master. После разрешения конфликтов процесс можно продолжить с помощью команды git rebase —continue. Теперь можно продолжать работу над своей веткой или осуществить её слияние (merge) с главным репозиторием.

Слияние локальных изменений с другой веткой

Это можно сделать прямо в процессе стандартного слияния (merge). Вам стоит сохранить историю слияний используя флаг —no-ff, что означает no fast forward.

Перейдите в ветку, в которую будут вливаться изменения, убедитесь в её актуальности и запустите процесс:

git merge <другая-ветка> --no-ff

Затем появится сообщение о коммите merge X into Y branch, после чего вы можете смело запушить ваше слияние.>

Совмещение двух и более коммитов

Здесь нам понадобится произвести интерактивное перебазирование. Если перебазирование происходит относительно master-ветки, то начать следует с команды git rebase -i master. Однако, если перебазирование происходит не относительно ветки, то нужно будет перебазироваться относительно HEAD.

Если есть необходимость в совмещении двух последних коммитов, можно использовать команду

После её ввода появятся инструкции по выбору коммитов. В том случае, если необходимо совместить все коммиты с первым старейшим коммитом, то в первой строке нужно написать pick, а для всех остальных коммитов изменить букву на f. Подробнее здесь

Совмещение коммитов по конкретной функции для добавления в ветку релиза

Если вы решите совместить и опубликовать коммиты, то возникнет новый коммит в ветке релиза, поэтому история ветки конкретной функции останется неизменной.

Ниже представлен пример того, как достичь подобного эффекта:

git fetch origin
git checkout [release-branch]
git rebase origin/[release-branch]
git merge —squash —no-commit [feature-branch]
git commit -m 'Merge X into Y'

В конечном итоге останется только один коммит в ветке релиза, а история изменений в ветке разработки конкретной функции останется нетронутой.

Подробнее о ветках функций

Создание новой ветки с изменениями текущей

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

git checkout -b название-моей-новой-ветки

Эта команда перенесёт файлы из текущей ветки в новую, которую потом уже можно «закоммитить».

Убрать файл из буфера

Чтобы убрать добавленный по ошибке файл из буфера, нужно воспользоваться простой командой:

git reset HEAD unlovedfile.txt

Удаление внешней ветки

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

git push origin --delete название-ветки

Удаление неотслеживаемых файлов и папок

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

Чтобы в принципе удалить их:

Подсказка: чтобы увидеть, какие файлы являются лишними, перед их непосредственным удалением, наберите:

Удаление старых веток, стёртых из внешнего репозитория

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

git-remote prune название-удалённой-ветки.

Она удалит старую ветку под названием название-удалённой-ветки, которая уже была стёрта из внешнего репозитория, но всё ещё доступна локально в remotes/название-удалённой-ветки.

Удаление файла из git с сохранением его локальной копии

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

git rm --cached myfile.txt

Больше статей о Git

  • Полезные команды для работы с Git
  • Как работать с GitHub в большой команде
  • Как бесплатно залить сайт на хостинг GitHub Pages

Перезапись истории

Неоднократно при работе с Git, вам может потребоваться по какой-то причине внести исправления в историю коммитов.
Одно из преимуществ Git заключается в том, что он позволяет вам отложить принятие решений на самый последний момент.
Область индексирования позволяет вам решить, какие файлы попадут в коммит непосредственно перед его выполнением; благодаря команде git stash вы можете решить, что не хотите продолжать работу над какими-то изменениями; также можете внести изменения в сделанные коммиты так, чтобы они выглядели как будто они произошли по-другому.
В частности, можно изменить порядок коммитов, сообщения или изменённые в коммитах файлы, объединить вместе или разбить на части, полностью удалить коммит — но только до того, как вы поделитесь своими наработками с другими.

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

Примечание

Не отправляйте свои наработки, пока вы ими не довольны

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

Изменение последнего коммита

Изменение вашего последнего коммита, наверное, наиболее частое исправление истории, которое вы будете выполнять.
Наиболее часто с вашим последним коммитом вам будет нужно сделать две основные операции: изменить сообщение коммита или изменить только что сделанный снимок, добавив, изменив или удалив файлы.

Если вы хотите изменить только сообщение вашего последнего коммита, это очень просто:

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

Если вы создали коммит и затем хотите изменить зафиксированный снимок, добавив или изменив файлы (возможно, вы забыли добавить вновь созданный файл, когда совершали изначальный коммит), то процесс выглядит в основном так же.
Вы добавляете в индекс необходимые изменения, редактируя файл и выполняя для него git add или git rm для отслеживаемого файла, а последующая команда git commit --amend берет вашу текущую область подготовленных изменений и делает её снимок для нового коммита.

Вы должны быть осторожными, используя этот приём, так как при этом изменяется SHA-1 коммита.
Поэтому, как и с операцией rebase — не изменяйте ваш последний коммит, если вы уже отправили его в общий репозиторий.

Подсказка

Изменённый коммит может потребовать изменения сообщения коммита

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

С другой стороны, если изменения незначительны (исправление опечаток, добавление в коммит забытого файла), то текущее сообщение вполне можно оставить; чтобы лишний раз не вызывать редактор, просто добавьте измененные файлы в индекс и выполните команду:

$ git commit --amend --no-edit

Изменение сообщений нескольких коммитов

Для изменения коммита, расположенного раньше в вашей истории, вам нужно обратиться к более сложным инструментам.
В Git отсутствуют инструменты для переписывания истории, но вы можете использовать команду rebase, чтобы перебазировать группу коммитов туда же на HEAD, где они были изначально, вместо перемещения их в другое место.
С помощью интерактивного режима команды rebase, вы можете останавливаться после каждого нужного вам коммита и изменять сообщения, добавлять файлы или делать что-то другое, что вам нужно.
Вы можете запустить rebase в интерактивном режиме, добавив опцию -i к git rebase.
Вы должны указать, какие коммиты вы хотите изменить, передав команде коммит, на который нужно выполнить перебазирование.

Например, если вы хотите изменить сообщения последних трёх коммитов, или сообщение какого-то одного коммита этой группы, то передайте как аргумент команде git rebase -i родителя последнего коммита, который вы хотите изменить — HEAD~2^ или HEAD~3.
Может быть, проще будет запомнить ~3, так как вы хотите изменить последние три коммита; но не забывайте, что вы, в действительности, указываете четвертый коммит с конца — родителя последнего коммита, который вы хотите изменить:

Напомним, что это команда перебазирования — каждый коммит, входящий в диапазон HEAD~3..HEAD, будет изменён вне зависимости от того, изменили вы сообщение или нет.
Не включайте в такой диапазон коммит, который уже был отправлен на центральный сервер: сделав это, вы можете запутать других разработчиков, предоставив вторую версию одних и тех же изменений.

Выполнение этой команды отобразит в вашем текстовом редакторе список коммитов, в нашем случае, например, следующее:

pick f7f3f6d Change my name a bit
pick 310154e Update README formatting and add blame
pick a5f4a0d Add cat-file

# Rebase 710f0f8..a5f4a0d onto 710f0f8
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

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

$ git log --pretty=format:"%h %s" HEAD~3..HEAD
a5f4a0d Add cat-file
310154e Update README formatting and add blame
f7f3f6d Change my name a bit

Обратите внимание на обратный порядок.
Команда rebase в интерактивном режиме предоставит вам скрипт, который она будет выполнять.
Она начнет с коммита, который вы указали в командной строке (HEAD~3) и повторит изменения, внесённые каждым из коммитов, сверху вниз.
Наверху отображается самый старый коммит, а не самый новый, потому что он будет повторен первым.

Вам необходимо изменить скрипт так, чтобы он остановился на коммите, который вы хотите изменить.
Для этого измените слово pick на слово edit напротив каждого из коммитов, после которых скрипт должен остановиться.
Например, для изменения сообщения только третьего коммита, измените файл следующим образом:

edit f7f3f6d Change my name a bit
pick 310154e Update README formatting and add blame
pick a5f4a0d Add cat-file

Когда вы сохраните сообщение и выйдете из редактора, Git переместит вас к самому раннему коммиту из списка и вернёт вас в командную строку со следующим сообщением:

$ git rebase -i HEAD~3
Stopped at f7f3f6d... Change my name a bit
You can amend the commit now, with

       git commit --amend

Once you're satisfied with your changes, run

       git rebase --continue

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

Измените сообщение коммита и выйдите из редактора.
Затем выполните:

Эта команда автоматически применит два оставшиеся коммита и завершится.
Если вы измените pick на edit в других строках, то можете повторить эти шаги для соответствующих коммитов.
Каждый раз Git будет останавливаться, позволяя вам исправить коммит, и продолжит, когда вы закончите.

Упорядочивание коммитов

Вы также можете использовать интерактивное перебазирование для изменения порядка или полного удаления коммитов.
Если вы хотите удалить коммит «Add cat-file» и изменить порядок, в котором были внесены два оставшихся, то вы можете изменить скрипт перебазирования с такого:

pick f7f3f6d Change my name a bit
pick 310154e Update README formatting and add blame
pick a5f4a0d Add cat-file

на такой:

pick 310154e Update README formatting and add blame
pick f7f3f6d Change my name a bit

Когда вы сохраните скрипт и выйдете из редактора, Git переместит вашу ветку на родителя этих коммитов, применит 310154e, затем f7f3f6d и после этого остановится.
Вы, фактически, изменили порядок этих коммитов и полностью удалили коммит «Add cat-file».

Объединение коммитов

С помощью интерактивного режима команды rebase также можно объединить несколько коммитов в один.
Git добавляет полезные инструкции в сообщение скрипта перебазирования:

#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

Если вместо «pick» или «edit» вы укажете «squash», Git применит изменения из текущего и предыдущего коммитов и предложит вам объединить их сообщения.
Таким образом, если вы хотите из этих трёх коммитов сделать один, вы должны изменить скрипт следующим образом:

pick f7f3f6d Change my name a bit
squash 310154e Update README formatting and add blame
squash a5f4a0d Add cat-file

Когда вы сохраните скрипт и выйдете из редактора, Git применит изменения всех трёх коммитов и затем вернёт вас обратно в редактор, чтобы вы могли объединить сообщения коммитов:

# This is a combination of 3 commits.
# The first commit's message is:
Change my name a bit

# This is the 2nd commit message:

Update README formatting and add blame

# This is the 3rd commit message:

Add cat-file

После сохранения сообщения, вы получите один коммит, содержащий изменения всех трёх коммитов, существовавших ранее.

Разбиение коммита

Разбиение коммита отменяет его и позволяет затем по частям индексировать и фиксировать изменения, создавая таким образом столько коммитов, сколько вам нужно.
Например, предположим, что вы хотите разбить средний коммит на два.
Вместо одного коммита «Update README formatting and add blame» вы хотите получить два разных: первый — «Update README formatting», и второй — «Add blame».
Вы можете добиться этого, изменив в скрипте rebase -i инструкцию для разбиваемого коммита на «edit»:

pick f7f3f6d Change my name a bit
edit 310154e Update README formatting and add blame
pick a5f4a0d Add cat-file

Затем, когда скрипт вернёт вас в командную строку, вам нужно будет отменить индексацию изменений этого коммита, и создать несколько коммитов на основе этих изменений.
Когда вы сохраните скрипт и выйдете из редактора, Git переместится на родителя первого коммита в вашем списке, применит первый коммит (f7f3f6d), применит второй (310154e), и вернёт вас в консоль.
Здесь вы можете отменить коммит с помощью команды git reset HEAD^, которая, фактически, отменит этот коммит и удалит из индекса изменённые файлы.
Теперь вы можете добавлять в индекс и фиксировать файлы, пока не создадите требуемые коммиты, а после этого выполнить команду git rebase --continue:

$ git reset HEAD^
$ git add README
$ git commit -m 'Update README formatting'
$ git add lib/simplegit.rb
$ git commit -m 'Add blame'
$ git rebase --continue

Git применит последний коммит (a5f4a0d) из скрипта, и ваша история примет следующий вид:

$ git log -4 --pretty=format:"%h %s"
1c002dd Add cat-file
9b29157 Add blame
35cfb2b Update README formatting
f7f3f6d Change my name a bit

И снова, при этом изменились SHA-1 хеши всех коммитов в вашем списке, поэтому убедитесь, что ни один коммит из этого списка ранее не был отправлен в общий репозиторий.
Обратите внимание, что последний коммит в списке (f7f3f6d) не изменился.
Несмотря на то, что коммит был в списке перебазирования, он был отмечен как «pick» и применён до применения перебазирования, поэтому Git оставил его нетронутым.

Удаление коммита

Если вы хотите избавиться от какого-либо коммита, то удалить его можно во время интерактивного перебазирования rebase -i.
Напишите слово «drop» перед коммитом, который хотите удалить, или просто удалите его из списка:

pick 461cb2a This commit is OK
drop 5aecc10 This commit is broken

Из-за того, как Git создаёт объекты коммитов, удаление или изменение коммита влечёт за собой перезапись всех последующих коммитов.
Чем дальше вы вернётесь в историю ваших коммитов, тем больше коммитов потребуется переделать.
Это может вызвать множество конфликтов слияния, особенно если у вас много последующих коммитов, которые зависят от удалённого.

Если во время подобного перебазирования вы поняли, что это была не очень хорошая идея, то всегда можно остановиться.
Просто выполните команду git rebase --abort и ваш репозиторий вернётся в то состояние, в котором он был до начала перебазирования.

Если вы завершили перебазирование, а затем решили, что полученный результат это не то, что вам нужно — воспользуйтесь командой git reflog, чтобы восстановить предыдущую версию вашей ветки.
Дополнительную информацию по команде reflog можно найти в разделе Восстановление данных главы 10.

Примечание

Дрю Дево создал практическое руководство с упражнениями по использованию git rebase.
Найти его можно здесь: https://git-rebase.io/

Продвинутый инструмент: filter-branch

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

Внимание

Команда git filter-branch таит в себе много подводных камней и более не является рекомендуемым способом изменения истории.
Вместо этого, рассмотрите возможность использования Python скрипта git-filter-repo, который лучше подходит для большинства ситуаций, в которых вы обычно используете filter-branch.
С документаций и исходным кодом скрипта можно ознакомиться здесь: https://github.com/newren/git-filter-repo.

Удаление файла из каждого коммита

Такое случается довольно часто.
Кто-нибудь случайно зафиксировал огромный бинарный файл, неосмотрительно выполнив git add ., и вы хотите отовсюду его удалить.
Возможно, вы случайно зафиксировали файл, содержащий пароль, а теперь хотите сделать ваш проект общедоступным.
В общем, утилиту filter-branch вы, вероятно, захотите использовать, чтобы привести к нужному виду всю вашу историю.
Для удаления файла passwords.txt из всей вашей истории вы можете использовать опцию --tree-filter команды filter-branch:

$ git filter-branch --tree-filter 'rm -f passwords.txt' HEAD
Rewrite 6b9b3cf04e7c5686a9cb838c3f36a8cb6a0fc2bd (21/21)
Ref 'refs/heads/master' was rewritten

Опция --tree-filter выполняет указанную команду после переключения на каждый коммит и затем повторно фиксирует результаты.
В данном примере, вы удаляете файл passwords.txt из каждого снимка вне зависимости от того, существует он или нет.
Если вы хотите удалить все случайно зафиксированные резервные копии файлов, созданные текстовым редактором, то вы можете выполнить нечто подобное git filter-branch --tree-filter 'rm -f *~' HEAD.

Вы можете посмотреть, как Git изменит деревья и коммиты, а затем уже переместить указатель ветки.
Как правило, хорошим подходом будет выполнение всех этих действий в тестовой ветке и, после проверки полученных результатов, установка на неё указателя основной ветки.
Для выполнения filter-branch на всех ваших ветках, вы можете передать команде опцию --all.

Установка подкаталога как корневого каталога проекта

Предположим, вы выполнили импорт из другой системы контроля версий и получили в результате подкаталоги, которые не имеют никакого смысла (trunk, tags и так далее).
Если вы хотите сделать подкаталог trunk корневым для каждого коммита, команда filter-branch может помочь вам в этом:

$ git filter-branch --subdirectory-filter trunk HEAD
Rewrite 856f0bf61e41a27326cdae8f09fe708d679f596f (12/12)
Ref 'refs/heads/master' was rewritten

Теперь вашим новым корневым каталогом проекта будет являться подкаталог trunk.
Git также автоматически удалит коммиты, которые не затрагивали этот подкаталог.

Глобальное изменение адреса электронной почты

Ещё один типичный случай возникает, когда вы забыли выполнить git config для настройки своего имени и адреса электронной почты перед началом работы, или, возможно, хотите открыть исходные коды вашего рабочего проекта и изменить везде адрес вашей рабочей электронной почты на персональный.
В любом случае вы можете изменить адрес электронный почты сразу в нескольких коммитах с помощью команды filter-branch.
Вы должны быть осторожны, чтобы изменить только свои адреса электронной почты, для этого используйте опцию --commit-filter:

$ git filter-branch --commit-filter '
        if [ "$GIT_AUTHOR_EMAIL" = "schacon@localhost" ];
        then
                GIT_AUTHOR_NAME="Scott Chacon";
                GIT_AUTHOR_EMAIL="schacon@example.com";
                git commit-tree "$@";
        else
                git commit-tree "$@";
        fi' HEAD

Эта команда пройдёт по всем коммитам и установит в них ваш новый адрес.
Так как коммиты содержат значения SHA-1-хешей их родителей, эта команда изменяет хеш SHA-1 каждого коммита в вашей истории, а не только тех, которые соответствовали адресам электронной почты.

Коллеги, приветствую. Сегодня мы немного поговорим о работе с коммитами в системе контроля версиями — Git.

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

Возникающие проблемы

В ходе разработки каких-либо проектов в Git, мы можем столкнуться с различными ситуациями, такими как:

  • Неправильное сообщение коммита
  • Забыли добавить некоторые файлы
  • Забыли удалить некоторые файлы
  • Необходимость удалить коммит
  • Необходимость объединить коммиты
  • и т.д

Безопасные изменения для локального репозитория

Изменить последний коммит

Давайте предположим, что мы работаем над каким-либо проектом локально.

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

git commit --amend

Перед нами откроется текстовый редактор с предложением изменить сообщение. Также мы можем увидеть список файлов которые будут зафиксированы.

Изменим сообщение коммита, выйдем из текстового редактора и проверим историю. Для этого введем команду:

git log --oneline

Но что делать, если мы забыли добавить или изменить какие-то файлы в наш последний коммит?

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

Для теста я добавлю файл forgotten file.txt в проект. После этого еще раз выполним команду:

git commit --amend

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

Проверяем историю.

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

git commit --amend

Удалить коммит

Для того, чтобы удалить один или несколько коммитов, мы можем воспользоваться командой git reset. Существует два типа выполнения reset:

  • Soft — удаляет коммит из истории и переводит файлы из него в состояние рабочего каталога.
  • Hard — удаляет коммит из истории вместе с файлами.

Сначала разберем soft reset. Возвращаемся к нашему проекту.

В один момент мы осознали, что допустили ошибку и хотели бы переделать последний коммит.

Для этого выполним команду:

git reset HEAD~1

Проверим историю коммитов. Мы можем убедится, что последний коммит был удален.

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

Подобные действия мы можем выполнять для нескольких коммитов сразу. Для примера введем команду:

git reset HEAD~3

3 последних коммита пропали из истории.

Проверим состояние файлов, которых коснулась процедура удаления коммитов.

Мы можем полностью удалить коммиты без сохранения связанных с ними файлов. Для этого выполним процедуру hard reset:

git reset --hard HEAD~3 

И проверим состояние.

Массовое изменение коммитов

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

Для этой задачи хорошо подходит выполнение команды git rebase. Ее запуск в интерактивном режиме позволит нам последовательно сообщить Git’у как бы мы хотели выполнить оптимизацию.

Мы считаем, что 4 последних коммита можно смело объединить в один.

Для этого выполним команду:

git rebase -i HEAD~4

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

После выполнения — выходим из текстового редактора.

Теперь Git попросит от нас ввести сообщения коммитов. Просто для наглядности мы немного переименуем наш объединенный коммит. Также Git сообщит о том, какие файлы подвергнуться изменениям.

Снова выходим из текстового редактора.

Похоже, что наше объединение нескольких коммитов прошло успешно. Давайте проверим это командой:

git log --oneline

Мы уже были готовы к публикации наших изменений, однако, посчитали, что необходимо объединить другие 3 коммита в 1 и изменить его сообщение. При этом, мы бы не хотели затрагивать уже объединенный.

Снова вводим команду:

git rebase HEAD~4

В этот раз в качестве начального коммита будет использоваться с коммит хэшем 4c17043. Для его переименования укажем напротив команду reword. Затем объединяем с ним два последующих коммита командой squash. Последний коммит мы никак не изменяем.

После переименования выходим из текстового редактора и проверяем историю.

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

Безопасные изменения для удаленных репозиториев

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

Однако, что делать, если мы совместно работаем над проектом и только спустя какое-то время понимаем, что один из коммитов — лишний? Если мы воспользуемся вышеописанными методами, это может спровоцировать ряд проблем в работе над проектом у наших коллег.

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

Снова вовзращаемся к исходному состоянию проекта.

Предположим, что изменения в коммите с хэшем и 5140d80 — лишние. Выполним команду:

git revert HEAD~2

Git сообщит нам об изменениях, после отката.

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

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

Итоги

В этой статье мы узнали о четырех способах изменять коммиты:

  • git commit —amend — позволяет изменять последний коммит.
  • git reset — позволяет удалять коммиты.
  • git rebase — позволяет производить массовое изменение коммитов, приводить в порядок историю.
  • git revert — позволяет откатывать изменения коммитов.

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

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

Понравилась статья? Поделить с друзьями:

Читайте также:

  • Как изменить локальные политики безопасности
  • Как изменить локальные координаты unity
  • Как изменить локальную учетную запись на учетную запись майкрософт
  • Как изменить локальную сеть на другую
  • Как изменить локальную сеть на домашнюю

  • 0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии