2021年6月22日 星期二

[Git 文章收集] Git commit --amend and other methods of rewriting history

 Source From Here

Intro
This tutorial will cover various methods of rewriting and altering Git history. Git uses a few different methods to record changes. We will discuss the strengths and weaknesses of the different methods and give examples of how to work with them. This tutorial discusses some of the most common reasons for overwriting committed snapshots and shows you how to avoid the pitfalls of doing so.

Git's main job is to make sure you never lose a committed change. But it's also designed to give you total control over your development workflow. This includes letting you define exactly what your project history looks like; however, it also creates the potential of losing commits. Git provides its history-rewriting commands under the disclaimer that using them may result in lost content.

Git has several mechanisms for storing history and saving changes. These mechanisms include: Commit --amendgit rebase and git reflog. These options give you powerful work flow customization options. By the end of this tutorial, you'll be familiar with commands that will let you restructure your Git commits, and be able to avoid pitfalls that are commonly encountered when rewriting history.

Changing the Last Commit: git commit --amend
The git commit --amend command is a convenient way to modify the most recent commit. It lets you combine staged changes with the previous commit instead of creating an entirely new commit.

It can also be used to simply edit the previous commit message without changing its snapshot. But, amending does not just alter the most recent commit, it replaces it entirely, meaning the amended commit will be a new entity with its own ref. To Git, it will look like a brand new commit, which is visualized with an asterisk (*) in the diagram below. There are a few common scenarios for using git commit --amend. We'll cover usage examples in the following sections:


Change most recent Git commit message
Let's say you just committed and you made a mistake in your commit log message. Running this command when there is nothing staged lets you edit the previous commit’s message without altering its snapshot. Premature commits happen all the time in the course of your everyday development. It’s easy to forget to stage a file or to format your commit message the wrong way. The --amend flag is a convenient way to fix these minor mistakes:
$ echo "test" > test.txt
$ git add test.txt
$ git commit -m 'add test.tx' # Should be 'add test.txt'
$ git log -1 # Check our last commit
  1. commit 4137e9447f9923f42f795f8767f15214d75cc0e1 (HEAD -> main)  
  2. Author: johnklee <puremonkey2001@yahoo.com.tw>  
  3. Date:   Tue Jun 22 11:46:14 2021 +0000  
  4.   
  5.     add test.tx  

You can fix your last commit message this way:
$ git commit --amend -m 'add test.txt' # Modify the message of last commit
[main 54f9c04] add test.txt
Date: Tue Jun 22 11:46:14 2021 +0000
1 file changed, 1 insertion(+)
create mode 100644 test.txt


$ git log -2
  1. commit 54f9c047df848782b59eba59a7b60322c4c4494c (HEAD -> main)  
  2. Author: johnklee <puremonkey2001@yahoo.com.tw>  
  3. Date:   Tue Jun 22 11:46:14 2021 +0000  
  4.   
  5.     add test.txt  
  6.   
  7. commit e5c0f35087e1a58ffa57d291b7d57b97919f2200 (origin/main, origin/HEAD)  
  8. Author: John <puremonkey2001@yahoo.com.tw>  
  9. Date:   Sat Apr 3 11:07:51 2021 +0800  
  10.   
  11.     Initial commit  

Adding the -m option allows you to pass in a new message from the command line without being prompted to open an editor.

Changing committed files
The following example demonstrates a common scenario in Git-based development. Let's say we've edited a few files that we would like to commit in a single snapshot, but then we forget to add one of the files the first time around. Fixing the error is simply a matter of staging the other file and committing with the --amend flag:
$ echo 'test2' > test2.txt
$ git add test2.txt # add test2.txt into staged area
$ git commit --amend -m 'add test.txt and test2.txt' # Modify the latest commit message and add test2.txt into it
  1. [main d9363e3] add test.txt and test2.txt  
  2. Date: Tue Jun 22 11:46:14 2021 +0000  
  3. 2 files changed, 2 insertions(+)  
  4. create mode 100644 test.txt  
  5. create mode 100644 test2.txt  
  6. [11:52:46 my-git-repo]$ git log -2  
  7. commit d9363e314eab8fcd7dc56ac49f1062ad747bcbfd (HEAD -> main)  
  8. Author: johnklee <puremonkey2001@yahoo.com.tw>  
  9. Date:   Tue Jun 22 11:46:14 2021 +0000  
  10.   
  11.     add test.txt and test2.txt  
  12.   
  13. commit e5c0f35087e1a58ffa57d291b7d57b97919f2200 (origin/main, origin/HEAD)  
  14. Author: John <puremonkey2001@yahoo.com.tw>  
  15. Date:   Sat Apr 3 11:07:51 2021 +0800  
  16.   
  17.     Initial commit  

The --no-edit flag will allow you to make the amendment to your commit without changing its commit message. The resulting commit will replace the incomplete one, and it will look like we committed the changes to test1.py and test2.py in a single snapshot.

Don’t amend public commits
Amended commits are actually entirely new commits and the previous commit will no longer be on your current branch. This has the same consequences as resetting a public snapshot. Avoid amending a commit that other developers have based their work on. This is a confusing situation for developers to be in and it’s complicated to recover from.

Changing older or multiple commits
To modify older or multiple commits, you can use git rebase to combine a sequence of commits into a new base commit. In standard mode, git rebase allows you to literally rewrite history — automatically applying commits in your current working branch to the passed branch head. Since your new commits will be replacing the old, it's important to not use git rebase on commits that have been pushed public, or it will appear that your project history disappeared.

In these or similar instances where it's important to preserve a clean project history, adding the -i option to git rebase allows you to run rebase interactive. This gives you the opportunity to alter individual commits in the process, rather than moving all commits. You can learn more about interactive rebasing and additional rebase commands on the git rebase page.

Changing committed files
During a rebase, the edit or e command will pause the rebase playback on that commit and allow you to make additional changes with git commit --amend. Git will interrupt the playback and present a message (more):
  1. Stopped at 5d025d1... formatting  
  2. You can amend the commit now, with  
  3. git commit --amend  
  4.   
  5. Once you are satisfied with your changes, run  
  6.   
  7. git rebase --continue  
Multiple messages
Each regular Git commit will have a log message explaining what happened in the commit. These messages provide valuable insight into the project's history. During a rebase, you can run a few commands on commits to modify commit messages.
* Reword or 'r' will stop rebase playback and let you rewrite the individual commit message during rebase process.

* Squash or 's' during rebase playback, any commits marked s will be paused on and you will be prompted to edit the separate commit messages into a combined message. More on this in the squash commits section below.

* Fixup or 'f' has the same combining effect as squash. Unlike squash, fixup commits will not interrupt rebase playback to open an editor to combine commit messages. The commits marked 'f' will have their messages discarded in-favor of the previous commit's message.


Squash commits for a clean history
The s "squash" command is where we see the true utility of rebase. Squash allows you to specify which commits you want to merge into the previous commits. This is what enables a "clean history." During rebase playback, Git will execute the specified rebase command for each commit. In the case of squash commits, Git will open your configured text editor and prompt to combine the specified commit messages. This entire process can be visualized as follows:



Note that the commits modified with a rebase command have a different ID than either of the original commits. Commits marked with pick will have a new ID if the previous commits have been rewritten.

Modern Git hosting solutions like Bitbucket now offer "auto squashing" features upon merge. These features will automatically rebase and squash a branch's commits for you when utilizing the hosted solutions UI. For more info see "Squash commits when merging a Git branch with Bitbucket."

The safety net: git reflog
Reference logs, or "reflogs" are a mechanism Git uses to record updates applied to tips of branches and other commit references. Reflog allows you to go back to commits even though they are not referenced by any branch or tag. After rewriting history, the reflog contains information about the old state of branches and allows you to go back to that state if necessary. Every time your branch tip is updated for any reason (by switching branches, pulling in new changes, rewriting history or simply by adding new commits), a new entry will be added to the reflog. In this section we will take a high level look at the git reflog command and explore some common uses.

Usage
This displays the reflog for the local repository:
$ git reflog
c609497 (HEAD -> main) HEAD@{0}: rebase -i (finish): returning to refs/heads/main
c609497 (HEAD -> main) HEAD@{1}: rebase -i (pick): add test4
7c1f760 HEAD@{2}: commit (amend): update test3.txt
cae11ca HEAD@{3}: rebase -i: fast-forward
6593dd5 HEAD@{4}: rebase -i (start): checkout refs/remotes/origin/main
79c6509 HEAD@{5}: commit: add test4
...
6593dd5 HEAD@{15}: rebase: add test.txt and test2.txt
e5c0f35 (origin/main, origin/HEAD) HEAD@{16}: rebase: checkout refs/remotes/origin/main
ec8042e HEAD@{17}: commit: test3.txt
d9363e3 HEAD@{18}: commit (amend): add test.txt and test2.txt
54f9c04 HEAD@{19}: commit (amend): add test.txt
4137e94 HEAD@{20}: commit: add test.tx
e5c0f35 (origin/main, origin/HEAD) HEAD@{21}: clone: from https://github.com/johnklee/my-git-repo.git


Example
To understand git reflog, let's run through an example. You can use git reset to move to any commit relative to HEAD. For example, let's reverse the commit of "add test4" by:
$ git log -3
  1. commit c6094973bdaeb4f7ecafca73f1008cd60df026de (HEAD -> main)  
  2. Author: johnklee <puremonkey2001@yahoo.com.tw>  
  3. Date:   Tue Jun 22 12:12:18 2021 +0000  
  4.   
  5.     add test4  
  6.   
  7. commit 7c1f760795c7a47ad2d84a8eabdc3465dc9d385e  
  8. Author: johnklee <puremonkey2001@yahoo.com.tw>  
  9. Date:   Tue Jun 22 12:05:33 2021 +0000  
  10.   
  11.     update test3.txt  
  12.   
  13. commit 6593dd5b62a31da4c26dab2a440f4f686816df50  
  14. Author: johnklee <puremonkey2001@yahoo.com.tw>  
  15. Date:   Tue Jun 22 11:46:14 2021 +0000  
  16.   
  17.     add test.txt and test2.txt  

$ git reset "HEAD@{2}" --hard
HEAD is now at 7c1f760 update test3.txt

$ git log -3 # Check commit "add test4" is gone
  1. commit 7c1f760795c7a47ad2d84a8eabdc3465dc9d385e (HEAD -> main)  
  2. Author: johnklee <puremonkey2001@yahoo.com.tw>  
  3. Date:   Tue Jun 22 12:05:33 2021 +0000  
  4.   
  5.     update test3.txt  
  6.   
  7. commit 6593dd5b62a31da4c26dab2a440f4f686816df50  
  8. Author: johnklee <puremonkey2001@yahoo.com.tw>  
  9. Date:   Tue Jun 22 11:46:14 2021 +0000  
  10.   
  11.     add test.txt and test2.txt  
  12.   
  13. commit e5c0f35087e1a58ffa57d291b7d57b97919f2200 (origin/main, origin/HEAD)  
  14. Author: John <puremonkey2001@yahoo.com.tw>  
  15. Date:   Sat Apr 3 11:07:51 2021 +0800  
  16.   
  17.     Initial commit  
$ git reflog # All the operation will be recorded. Even the action of reset
7c1f760 (HEAD -> main) HEAD@{0}: reset: moving to HEAD@{2}
c609497 HEAD@{1}: rebase -i (finish): returning to refs/heads/main
c609497 HEAD@{2}: rebase -i (pick): add test4
7c1f760 (HEAD -> main) HEAD@{3}: commit (amend): update test3.txt
cae11ca HEAD@{4}: rebase -i: fast-forward
...

So you can revert your revert this way:
$ git reset "HEAD@{2}" --hard
HEAD is now at c609497 add test4

$ git log -n 3
  1. commit c6094973bdaeb4f7ecafca73f1008cd60df026de (HEAD -> main)  
  2. Author: johnklee <puremonkey2001@yahoo.com.tw>  
  3. Date:   Tue Jun 22 12:12:18 2021 +0000  
  4.   
  5.     add test4  
  6.   
  7. commit 7c1f760795c7a47ad2d84a8eabdc3465dc9d385e  
  8. Author: johnklee <puremonkey2001@yahoo.com.tw>  
  9. Date:   Tue Jun 22 12:05:33 2021 +0000  
  10.   
  11.     update test3.txt  
  12.   
  13. commit 6593dd5b62a31da4c26dab2a440f4f686816df50  
  14. Author: johnklee <puremonkey2001@yahoo.com.tw>  
  15. Date:   Tue Jun 22 11:46:14 2021 +0000  
  16.   
  17.     add test.txt and test2.txt  


Supplement
W3Doc - How to Change Older or Multiple Git Commit Messages
FAQ - How do I make Git use the editor of my choice for commits?
$ git config --global core.editor "vim"
$ export GIT_EDITOR=vim

30 天精通 Git 版本控管 (16):善用版本日誌 git reflog 追蹤變更軌跡




1 則留言:

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...