git 修改 commit 总结

在使用 Git 作为版本控制的时候,我们可能提交了很多不尽如人意的 commit。如果这些 commit 太多会显得混乱,甚至会影响到正常的工作流。如何对这些 commit 进行修正呢?

下面以 github.com/jjvvv/autoComplete 项目为示例进行演示。

1
$ git log

可以看到一共有 7 个 commit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
commit 75931a058b8c6810048cd9b8fb6ad7dd53a32140 (HEAD -> master, origin/master, origin/HEAD)
Author: JJVvV <drowning524@gmail.com>
Date: Fri Dec 18 08:11:35 2015 +0800

add demo

commit 9f480fba3c5c973082c30d4751e6d8d802c8e1bc
Author: JJVvV <drowning524@gmail.com>
Date: Fri Dec 18 06:13:32 2015 +0800

change README.md

commit 064af197e7674b7357da6606655f13e4aebbcaab
Author: JJVvV <drowning524@gmail.com>
Date: Fri Dec 18 06:10:52 2015 +0800

change README.md

commit 251af8b01add69e49c64308c9b328670bcad0aae
Author: JJVvV <drowning524@gmail.com>
Date: Fri Dec 18 06:08:57 2015 +0800

change readme.md

commit fdc128e488457e18887d85362dde87f1a2420146
Author: JJVvV <drowning524@gmail.com>
Date: Fri Dec 18 06:07:23 2015 +0800

remove module

commit 666ba561a6ef687abb61be44ce410128b2faf645
Author: JJVvV <drowning524@gmail.com>
Date: Fri Dec 18 06:02:05 2015 +0800

autoComplete

commit c1c0414ad3dce011802d75347d6191c801bd8f5c
Author: JJVvV <drowning524@gmail.com>
Date: Fri Dec 18 05:58:16 2015 +0800

first commit
~

修改最新 commit

如果只想修改最新的 commit,可以使用:

1
$ git commit --amend

场景

  • 添加最近提交时漏掉的档案
  • 修改最近提交的注解

示例

我们把最新的那个 commit add demo 信息改成 docs(readme): add demo

1
$ git commit --amend

进入到编辑模式,修改 commit 信息为docs(readme): add demo 然后保存。修改后的 commit 信息如下,但是要注意,commit 的 id 已经和之前不同了:

1
2
3
4
5
commit 0e49c219ce76065f6825941b3cecf30b3439412d (HEAD -> master)
Author: JJVvV <drowning524@gmail.com>
Date: Fri Dec 18 08:11:35 2015 +0800

docs(readme): add demo

修改多个 commit

修改更早提交的 commit ,可以使用git rebase,通过给 git rebase 增加-i 选项来以交互方式地运行 rebase。

示例

上例中,有三个 commit 是重复的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
commit 9f480fba3c5c973082c30d4751e6d8d802c8e1bc
Author: JJVvV <drowning524@gmail.com>
Date: Fri Dec 18 06:13:32 2015 +0800

change README.md

commit 064af197e7674b7357da6606655f13e4aebbcaab
Author: JJVvV <drowning524@gmail.com>
Date: Fri Dec 18 06:10:52 2015 +0800

change README.md

commit 251af8b01add69e49c64308c9b328670bcad0aae
Author: JJVvV <drowning524@gmail.com>
Date: Fri Dec 18 06:08:57 2015 +0800

change readme.md

我们利用 git rebase 给它们合并进行合并。

合并有两个方法:

  1. 从 HEAD 往过去数 4 个版本
1
$ git rebase -i HEAD~4
  1. 指出要合并的版本的前一个版本号,这里注意,不包括这个指明的 commit
1
$ git rebase -i 666ba56

其实按我的理解,git rebase -i HEAD~4 的意思是:嘿,我挑出来最近四个 commit,让我做一些修改吧。

这里我们用第一种方法执行完 rebase 之后,会进入交互模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pick 251af8b change readme.md
pick 064af19 change README.md
pick 9f480fb change README.md
pick 0e49c21 docs(readme): add demo

# Rebase fdc128e..0e49c21 onto fdc128e (4 commands)
#
# 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>]

我们把 中间两个 pick 改成 squash,意思是说把这两个 commit 融入到前一个 commit 中:

1
2
3
4
pick 251af8b change readme.md
squash 064af19 change README.md
squash 9f480fb change README.md
pick 0e49c21 docs(readme): add demo

保存完之后会再次进入编辑状态,提示你可以修改三个合并好之后的 commit 的信息,再次保存之后,我们看下 git log

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
commit 09ad45377c2c7a616545f7f01ddb740964a2eddb (HEAD -> master)
Author: JJVvV <drowning524@gmail.com>
Date: Fri Dec 18 08:11:35 2015 +0800

docs(readme): add demo

commit 0c5c282227c9e8d336b1e3633f6962e621880662
Author: JJVvV <drowning524@gmail.com>
Date: Fri Dec 18 06:08:57 2015 +0800

change readme.md

change README.md

change README.md

commit fdc128e488457e18887d85362dde87f1a2420146
Author: JJVvV <drowning524@gmail.com>
Date: Fri Dec 18 06:07:23 2015 +0800

remove module

commit 666ba561a6ef687abb61be44ce410128b2faf645
Author: JJVvV <drowning524@gmail.com>
Date: Fri Dec 18 06:02:05 2015 +0800

autoComplete

commit c1c0414ad3dce011802d75347d6191c801bd8f5c
Author: JJVvV <drowning524@gmail.com>
Date: Fri Dec 18 05:58:16 2015 +0800

first commit

可以看到,三个 commit 已经成功合并到一个了。

如果 rebase 的过程有冲突,解决好冲突之后,运行:

1
2
$ git add .
$ git rebase --continue

如果想放弃 rebase 的话,运行:

1
$ git rebase --abort

删除 commit

可以利用 git rebase 删除 commit,同样,将 pick 改为 drop 即可:

1
# d, drop <commit> = remove commit

回滚 commit

git reset

1
$ git reset

示例

比如,我们想回滚到倒数第二个 commit(add demo 的前一个 commit),先记录下这个 commit ID 0c5c282227c9e8d336b1e3633f6962e621880662

1
2
# 相当于 git reset HEAD~1 --soft
$ git reset 0c5c282 --soft

然后再使用 git log 查看历史记录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
commit 0c5c282227c9e8d336b1e3633f6962e621880662 (HEAD -> master)
Author: JJVvV <drowning524@gmail.com>
Date: Fri Dec 18 06:08:57 2015 +0800

change readme.md

change README.md

change README.md

commit fdc128e488457e18887d85362dde87f1a2420146
Author: JJVvV <drowning524@gmail.com>
Date: Fri Dec 18 06:07:23 2015 +0800

remove module

commit 666ba561a6ef687abb61be44ce410128b2faf645
Author: JJVvV <drowning524@gmail.com>
Date: Fri Dec 18 06:02:05 2015 +0800

autoComplete

commit c1c0414ad3dce011802d75347d6191c801bd8f5c
Author: JJVvV <drowning524@gmail.com>
Date: Fri Dec 18 05:58:16 2015 +0800

first commit

会发现 add demo 的那个 commit 已经没有了,版本库已经回滚到 0c5c282 提交的状态了。但是 add demo 提交的内容并没有丢失,只是回到了未提交状态:

1
2
3
4
5
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

modified: .idea/workspace.xml
modified: README.md

git reset –mixed 和 git reset –soft 差不多: soft 执行完之后文件是已提交状态,mixed 执行完之后文件是未提交状态。

这些标记往往和 HEAD 作为参数一起使用。比如,git reset –mixed HEAD 将你当前的改动从缓存区中移除,但是这些改动还留在工作目录中。另一方面,如果你想完全舍弃你没有提交的改动,你可以使用 git reset –hard HEAD。这是 git reset 最常用的两种用法。

我们把刚才未提交的文件再次提交为add demo,然后执行 git reset 0c5c282 --hard。会发现版本库也回到了这个 commit ID,但是之前提交的 commit 文件已经丢失了。

git revert

1
2
# commitID为需要撤销的commitID
$ git revert commitID

有些时候既不想更改 commmit 也不想删除 commit,只是想撤销掉某个 commit,此时可以使用 revert。revert 在撤销一个 commit 的同时会新增一个 commit(相当于综合掉了这个 commit 的影响)。

示例

我们在刚才的实例上连续添加三个 commit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
commit 93a702791a9102dfd14aa12063eb4a71d8df6ceb
Author: Alex Liu <drowning524@gmail.com>
Date: Sun Apr 28 17:25:08 2019 +0800

format html

commit a04e1083fe57b26bd0359e39c1f5e3057485bb9e
Author: Alex Liu <drowning524@gmail.com>
Date: Sun Apr 28 17:24:12 2019 +0800

add console

commit 15618e77fc4c8de131b5c8f655d360d2e7221c3a
Author: Alex Liu <drowning524@gmail.com>
Date: Sun Apr 28 17:23:16 2019 +0800

change version

如果我们想撤销change version这个 commit 的话,复制它的 ID,运行:

1
$ git revert 15618e7

此时会增加一个新的 commit:

1
2
3
4
5
Date:   Sun Apr 28 17:25:26 2019 +0800

Revert "change version"

This reverts commit 15618e77fc4c8de131b5c8f655d360d2e7221c3a.

revert 的操作,只会撤销掉change version这个 commit 的影响。 它前边的两个 commit 仍会保留。

总结

以上总结了一些修改 commit 的方法,当然还有更多的方式,这里只列举了一些常用的。

  • git commit –amend 修改最新 commit
  • git rebase -i commitID 修改 commitID 之前的多个 commit
  • git reset commitID 重置到某个 commit
  • git revert commitID 撤销掉 commitID(通过新增一个 commit 来撤销掉 commitID,相对安全)