修改最后一次提交

仅修改提交信息

假设修改前的git日志如下:

1
2
3
4
$ git log --oneline
5e5996c (HEAD -> master) 修改b.java 6
cce2144 修改b.java 5
...

如果我们只是希望修改最后一次提交的提交信息,我们可以通过如下的方式进行修改:

1
2
3
4
$ git commit --amend -m '修改B.java文件:删除无用的import语句'
[master 055cda3] 修改B.java文件:删除无用的import语句
Date: Mon Apr 1 10:39:09 2024 +0800
1 file changed, 1 insertion(+)

此时再次查看git日志:

1
2
3
4
$ git log --oneline
055cda3 (HEAD -> master) 修改B.java文件:删除无用的import语句
cce2144 修改b.java 5
...

可以发现,之前旧的提交对象5e5996c被新的提交对象055cda3覆盖,并没有增加新的提交对象。

修改提交的文件快照

如果你已经完成提交,又因为之前提交时忘记添加一个新创建的文件,想通过添加或修改文件来更改提交的快照,也可以通过类似的操作来完成。 通过修改文件然后运行 git addgit rm 一个已追踪的文件,随后运行 git commit --amend 拿走当前的暂存区域并使其做为新提交的快照。

使用这个技巧的时候需要小心,因为修正会改变提交的 SHA-1 校验和。 它类似于一个小的变基 - 如果已经推送了最后一次提交就不要修正它

比如修改前的git日志如下:

1
2
3
4
5
6
7
8
$ git log --oneline
055cda3 (HEAD -> master) 修改B.java文件:删除无用的import语句
cce2144 修改b.java 5
...

$ git status
On branch master
nothing to commit, working tree clean

此时如果我们希望追加一个新的文件D.java,并删除B.java中的某一行:

1
2
3
$ git status -s
M B.java # 修改
?? D.java # 新增

但是我们希望将这些改动也加入最后一次改动中,可以通过如下的方式实现:

1
2
3
4
5
6
7
8
9
10
11
$ git add *

$ git status -s
M B.java
A D.java

$ git commit --amend # 弹出的文本编辑器中可以根据需要选择是否要修改提交信息
[master 38063bd] 修改B.java文件:删除无用的import语句
Date: Mon Apr 1 10:39:09 2024 +0800
2 files changed, 2 insertions(+)
create mode 100644 D.java

此时再次查看git日志:

1
2
3
4
$ git log --oneline
38063bd (HEAD -> master) 修改B.java文件:删除无用的import语句
cce2144 修改b.java 5
...

修改多个提交信息

为了修改在提交历史中较远的提交,必须使用更复杂的工具。 Git 没有一个改变历史工具,但是可以使用变基工具来变基一系列提交,基于它们原来的 HEAD 而不是将其移动到另一个新的上面。

通过交互式变基工具,可以在任何想要修改的提交后停止,然后修改信息、添加文件或做任何想做的事情。 可以通过给 git rebase 增加 -i 选项来交互式地运行变基。 必须指定想要重写多久远的历史,这可以通过告诉命令将要变基到的提交来做到。

详情阅读:修改多个提交信息

重新排序提交

详情阅读:修改多个提交信息

压缩提交

详情阅读:修改多个提交信息

拆分提交

详情阅读:修改多个提交信息

彻底移除文件

简单情况:仅涉及最近一次提交

参考:remove-sensitive-files-and-their-commits-from-git-history

如果改动需要移除的文件仅在本地的最后一次提交中存在,而且没有被Push到远端仓库,可以直接使用给git commit --amend进行修复,比如错误的提交了

secret.txt文件:

1
2
3
4
5
6
7
8
$ git log --all --oneline --graph
* f1a16dc (HEAD -> master) 提交 secret.txt
* d945ca3 app.properties中新增env变量

$ ll
total 7
...
-rw-r--r-- 1 86182 197609 0 4月 2 10:32 secret.txt

我们可以通过如下的方式覆盖本地提交:

1
2
3
4
5
6
7
8
9
$ git rm secret.txt
rm 'secret.txt'

$ git commit --amend -m '删除误提交的文件'
You asked to amend the most recent commit, but doing so would make
it empty. You can repeat your command with --allow-empty, or you can
remove the commit entirely with "git reset HEAD^".
On branch master
No changes

因为原先该次提交只提交了文件secret.txt,而此次amend删除了该文件,会导致原先的那次commit内容为空,因此需要加上--allow-empty参数,也可以根据提示直接删除这次提交:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ git commit --amend --allow-empty -m '删除误提交的文件'
[master 84d04fe] 删除误提交的文件
Date: Tue Apr 2 10:32:28 2024 +0800

$ ll # 结果中已经没有secret.txt文件
total 7
-rw-r--r-- 1 86182 197609 649 4月 1 10:38 A.java
...
-rw-r--r-- 1 86182 197609 0 4月 1 13:43 D.java

$ git log --all --oneline --graph
* 84d04fe (HEAD -> master) 删除误提交的文件
* d945ca3 app.properties中新增env变量
...

$ git show 84d04fe
commit 84d04fe11239c940a1828dcdb446bc2151be7a88 (HEAD -> master)
Author: slimterry <slimterry@qq.com>
Date: Tue Apr 2 10:32:28 2024 +0800

删除误提交的文件

此次提交变为一次空提交。

如下的情况则不需要添加--allow-empty参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ touch secret.txt bar.txt
$ git add *
$ git commit -m '添加2个文件'

$ git rm secret.txt
rm 'secret.txt'

$ git commit --amend -m'剔除误提交的文件'
[master ce592c7] 剔除误提交的文件
Date: Tue Apr 2 10:41:47 2024 +0800
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 bar.txt

复杂情况:涉及历史提交

参考:remove-sensitive-files-and-their-commits-from-git-history

不推荐使用filter-branch

有另一个历史改写的选项,如果想要通过脚本的方式改写大量提交的话可以使用它 - 例如,全局修改你的邮箱地址从每一个提交中移除一个文件。 这个命令是 filter-branch,它可以改写历史中大量的提交

除非你的项目还没有公开并且其他人没有基于要改写的工作的提交做的工作,你不应当使用它。 然而,它可以很有用。 你将会学习到几个常用的用途,这样就得到了它适合使用地方的想法。

注意:git-filter-branch(1) Manual Page中都不推荐使用filter-branch,推荐使用的是 git filter-repo

git filter-branch has a plethora of pitfalls that can produce non-obvious manglings of the intended history rewrite (and can leave you with little time to investigate such problems since it has such abysmal performance). These safety and performance issues cannot be backward compatibly fixed and as such, its use is not recommended. Please use an alternative history filtering tool such as git filter-repo.

安装git filter-repo

1
2
3
4
5
6
7
$ pip install git-filter-repo
Looking in indexes: https://mirrors.aliyun.com/pypi/simple
Collecting git-filter-repo
Downloading https://mirrors.aliyun.com/pypi/packages/8d/0b/49d4d620327b717fdc072b2efdfcb3588eb3cf780e60ed5ba98ebedb5637/git_filter_repo-2.38.0-py2.py3-none-any.whl (99 kB)
---------------------------------------- 99.1/99.1 kB 1.4 MB/s eta 0:00:00
Installing collected packages: git-filter-repo
Successfully installed git-filter-repo-2.38.0

从每一个提交移除敏感文件、大文件

命令格式:

1
git filter-repo --path path/to/remove1 --path path/to/remove2 --invert-paths

这经常发生。 有人粗心地提交了一个巨大的二进制文件largefile.iso,你希望从git仓库中彻底删除该文件

①:误提交了大文件

1
2
3
4
5
6
7
8
9
10
$ ll -h  largefile.iso
-rw-r--r-- 1 86182 197609 1000M 4月 1 14:33 largefile.iso

$ git status -s
A largefile.iso # 误提交

$ git commit -m '提交iso文件'
[master 98cca66] 提交iso文件
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 largefile.iso

此时你可能想通过git rm删除该文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
 git rm largefile.iso
rm 'largefile.iso'

$ git commit -m '删除错误提交的iso文件'
[master a45b192] 删除错误提交的iso文件
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 largefile.iso


$ git log --oneline
a45b192 (HEAD -> master) 删除错误提交的iso文件
98cca66 提交iso文件
...

此时工作区中,确实已经没有该文件,但是git仓库中仍然存在该文件,我们只需要检出到提交该大文件的版本,就可以找到该文件:

1
2
3
4
5
6
7
8
$ git checkout 98cca66
Note: switching to '98cca66'.

$ ll -h
total 1001M
-rw-r--r-- 1 86182 197609 649 4月 1 10:38 A.java
...
-rw-r--r-- 1 86182 197609 1000M 4月 1 14:41 largefile.iso

注意:

  • 无法通过将largefile.iso加入.gitignore的方法实现从历史提交中彻底删除文件的效果,.gitignore仅针对未提交的文件。

如果我们想彻底从git仓库中删除该文件,可以通过如下的命令实现:

1
2
3
4
5
6
7
8
9
10
11
12
$ git filter-repo --force --path largefile.iso --invert-paths
Parsed 21 commitsHEAD is now at 2592cd4 删除错误提交的iso文件
Enumerating objects: 49, done.
Counting objects: 100% (49/49), done.
Delta compression using up to 16 threads
Compressing objects: 100% (44/44), done.
Writing objects: 100% (49/49), done.
Total 49 (delta 20), reused 0 (delta 0), pack-reused 0

New history written in 0.21 seconds; now repacking/cleaning...
Repacking your repo and cleaning out old unneeded objects
Completely finished after 0.82 seconds.

此时我们再次切换到当初提交大文件的版本:

1
2
3
4
5
MINGW64 /d/coding/git-playground (master)
$ git log --all --oneline --graph
* 2592cd4 (HEAD -> master) 删除错误提交的iso文件
* d6aea11 提交iso文件
...

注意:由于重写了提交历史,因此git log中的commit id会发生改变

1
2
3
4
5
6
7
8
9
10
$ git checkout d6aea11
Note: switching to 'd6aea11'.

MINGW64 /d/coding/git-playground ((d6aea11...))
$ ll
total 6
-rw-r--r-- 1 86182 197609 649 4月 1 10:38 A.java
-rw-r--r-- 1 86182 197609 205 4月 1 13:43 B.java
-rw-r--r-- 1 86182 197609 5 3月 29 15:57 C.java
-rw-r--r-- 1 86182 197609 0 4月 1 13:43 D.java

此时在历史版本d6aea11中的大文件largefile.iso已经被彻底删除。

如果你的仓库已经推送到远程目录,还需要执行:

1
$ git push -f

替换git历史提交中某个字符串

参考how-to-replace-a-string-in-whole-git-history

假设我们在某次提交中新增了一个app.properties文件:

1
2
3
4
5
6
7
8
9
10
11
$ cat app.properties
passwd=12345

86182@yawen MINGW64 /d/coding/git-playground (master)
$ git add app.properties

86182@yawen MINGW64 /d/coding/git-playground (master)
$ git commit -m '提交app.properties文件'
[master 1ebee67] 提交app.properties文件
1 file changed, 1 insertion(+)
create mode 100644 app.properties

然后再后续的提交中又陆续修该了该文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ git commit -m 'app.properties中新增env变量'
[master a067387] app.properties中新增env变量
1 file changed, 1 insertion(+)

$ git log --all --oneline --graph
* a067387 (HEAD -> master) app.properties中新增env变量
* 1ebee67 提交app.properties文件


MINGW64 /d/coding/git-playground (master)
$ cat app.properties
passwd=12345 # 旧密码
env_name=dev

如果我们将各个提交历史中的app.properties中的12345替换成abc123,可以通过如下的命令实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ echo 'literal:12345==>abc123' > expressions.txt # 将替换规则写入到一个文件中,替换完成后可以删除

$ git-filter-repo --replace-text expressions.txt # 执行替换命令
Parsed 21 commitsHEAD is now at d945ca3 app.properties中新增env变量
Enumerating objects: 55, done.
Counting objects: 100% (55/55), done.
Delta compression using up to 16 threads
Compressing objects: 100% (28/28), done.
Writing objects: 100% (55/55), done.
Total 55 (delta 24), reused 46 (delta 20), pack-reused 0

New history written in 0.21 seconds; now repacking/cleaning...
Repacking your repo and cleaning out old unneeded objects
Completely finished after 0.77 seconds.

MINGW64 /d/coding/git-playground (master)
$ git log --all --oneline --graph
* d945ca3 (HEAD -> master) app.properties中新增env变量 # 由于重写了提交,因此commit id发生了改变
* 0ca4f99 提交app.properties文件
...

–replace-text <expressions_file>::
A file with expressions that, if found, will be replaced. By
default, each expression is treated as literal text, but
regex: and glob: prefixes are supported. You can end the
line with ==> and some replacement text to choose a
replacement choice other than the default of ***REMOVED***.

此时我们分别查看工作区和历史版本中的密码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ cat app.properties
passwd=abc123
env_name=dev

MINGW64 /d/coding/git-playground (master)
$ git show 0ca4f99
commit 0ca4f99f453b9211a19c08de968e05579775cbc3
Author: slimterry <slimterry@qq.com>
Date: Tue Apr 2 10:02:20 2024 +0800

提交app.properties文件

diff --git a/app.properties b/app.properties
new file mode 100644
index 0000000..ed9939e
--- /dev/null
+++ b/app.properties
@@ -0,0 +1 @@
+passwd=abc123 #此处也被重写为新的密码

注意: