Git 进阶——告别三板斧

前言

Git 三板斧指 git commit、git pull、git push。此篇分享开发过程中经常会使用到的 Git 进阶操作。

虽说会了三板斧就可以横行江湖,但总觉得不够潇洒飘逸。

阅读本文需要有一定 Vim 基础。

修改上个 commit 内容

场景:提交完一个 commit 之后,发现提交的message信息不够准确想要修改。

使用git commit --amend 命令。输入命令后会进入 Vim 编辑界面修改提交 message 即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
firstOne

# 请为您的变更输入提交说明。以 '#' 开始的行将被忽略,而一个空的提交
# 说明将会终止提交。
#
# 日期: Sun Jan 16 22:40:59 2022 +0800
#
# 位于分支 main
# 您的分支领先 'origin/main' 共 1 个提交。
# (使用 "git push" 来发布您的本地提交)
#
# 要提交的变更:
# 修改: console/src/main/java/basic/Demo1.java

输入 I 进入编辑模式,firstOne 修改成想要的信息,ESC键退出编辑模式 输入 :wq保存并退出 Vim。再使用 git log命令查看结果。

git commit –amend 命令必须在代码未 push到远程分支使用,不建议修改远程分支的 message 信息。

合并多个 commit 内容

场景:提交多个 commit 之后发现,前面提交的 commit 信息和现在提交的相近描述,为了分支 commit 信息可阅读性,建议把最近提交的 commit 信息合并成一个。

使用git rebase -i Head~3 合并提交。

Head~3 代表近三个 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
pick d5d8c16 firstOne
pick f768621 second
pick ebbe0a1 third

# 变基 6dee34b..ebbe0a1 到 6dee34b(3 个提交)
#
# 命令:
# p, pick <提交> = 使用提交
# r, reword <提交> = 使用提交,但编辑提交说明
# e, edit <提交> = 使用提交,但停止以便在 shell 中修补提交
# s, squash <提交> = 使用提交,但挤压到前一个提交
# f, fixup [-C | -c] <提交> = 类似于 "squash",但只保留前一个提交
# 的提交说明,除非使用了 -C 参数,此情况下则只
# 保留本提交说明。使用 -c 和 -C 类似,但会打开
# 编辑器修改提交说明
# x, exec <命令> = 使用 shell 运行命令(此行剩余部分)
# b, break = 在此处停止(使用 'git rebase --continue' 继续变基)
# d, drop <提交> = 删除提交
# l, label <label> = 为当前 HEAD 打上标记
# t, reset <label> = 重置 HEAD 到该标记
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . 创建一个合并提交,并使用原始的合并提交说明(如果没有指定
# . 原始提交,使用注释部分的 oneline 作为提交说明)。使用
# . -c <提交> 可以编辑提交说明。
"~/Project/my/hexo-blog-demo/.git/rebase-merge/git-rebase-todo" 31L, 1439B

把 third 和 second 合并到 firstOne。把对应要合并信息前的 pick 修改为 s 即可(如下所示)。

1
2
3
pick d5d8c16 firstOne
s f768621 second
s ebbe0a1 third

修改完之后输入 :wq 会进入到如下所示。

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
# 这是一个 3 个提交的组合。
# 这是第一个提交说明:

firstOne

# 这是提交说明 #2:

second

# 这是提交说明 #3:

third

# 请为您的变更输入提交说明。以 '#' 开始的行将被忽略,而一个空的提交
# 说明将会终止提交。
#
# 日期: Sun Jan 16 22:40:59 2022 +0800
#
# 交互式变基操作正在进行中;至 6dee34b
# 最后的命令已完成(3 条命令被执行):
# squash f768621 second
# squash ebbe0a1 third
# 未剩下任何命令。
# 您在执行将分支 'main' 变基到 '6dee34b' 的操作。
"~/Project/my/hexo-blog-demo/.git/COMMIT_EDITMSG" 28L, 666B

按照提示把 third和second删除掉留下firstOne即可,也可以全部删除重新编辑新的作为合并这三个commit的message信息。完成之后退出 Vim。同样可以使用 git log命令查看。

同样不建议修改已 push 到远程的 commit。

git revert

场景:代码 commit 并且 push 之后,产品经理说这个功能还是原来的好,因为已经提交到公共开发分支,为了不影响其他开发者。

使用git revert

用法相对来说比较简单,只要根据 git 的提示一步步操作就行。git revert -h看下使用说明,这里就不再做演示了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
用法:git revert [<选项>] <提交号>...
或:git revert <子命令>

--quit 终止反转或拣选操作
--continue 继续反转或拣选操作
--abort 取消反转或拣选操作
--skip 跳过当前提交并继续
--cleanup <模式> 设置如何删除提交说明里的空格和#注释
-n, --no-commit 不要自动提交
-e, --edit 编辑提交说明
-s, --signoff 添加 Signed-off-by 尾注
-m, --mainline <父编号>
选择主干父提交编号
--rerere-autoupdate 如果可能,重用冲突解决更新索引
--strategy <策略> 合并策略
-X, --strategy-option <选项>
合并策略的选项
-S, --gpg-sign[=<key-id>]
GPG 提交签名

git revert 对比 git reset

  • git revert是用一次新的commit来回滚之前的commitgit reset是直接删除指定的commit
  • 使用 git revert可以作用于已经 push 的 commit 。
  • git reset 是把HEAD向后移动了一下,而git revertHEAD继续前进,只是新的commit的内容和要revert的内容正好相反,能够抵消要被revert的内容。

git stash

场景一:此时正在A分支上开发接到一个紧急的需求需要去B分支修复一个bug,此时A分支的任务还没开发好不想 commit 。

步骤

  1. 在 A 分支使用 git stash 暂存已修改的代码,执行之后代码会回到上个commit。

    使用git stash list查看暂存清单

  2. 使用 git switch B切换到B分支修复完bug之后再切换回 A 分支。

  3. 使用 git stash pop 就回到熟悉的状态,又可以在 A 分支继续愉快的开发了。

场景二:写完最后一个分号准备 commit 的时候发现自己没有在正确的分支进行开发。

说明:可以使用 git stash把修改未提交的代码从 A 分支 移动到 B 分支。步骤上面类似。

恢复本地错误操作

场景:进行了一系列 git 命令之后想还原到最初的时刻。

步骤

  1. 使用git reflog show察看历史版本记录。

    如下所示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    ae7c1b0 (HEAD -> main) HEAD@{0}: reset: moving to ae7c1b0
    2437376 (origin/main) HEAD@{1}: reset: moving to HEAD
    2437376 (origin/main) HEAD@{2}: reset: moving to 2437376
    ae7c1b0 (HEAD -> main) HEAD@{3}: revert: Revert "firstOne"
    2437376 (origin/main) HEAD@{4}: reset: moving to Head~1
    a4bc216 HEAD@{5}: revert: Revert "firstOne"
    2437376 (origin/main) HEAD@{6}: reset: moving to Head~1
    48cdd88 HEAD@{7}: reset: moving to HEAD
    48cdd88 HEAD@{8}: commit: 111
    2437376 (origin/main) HEAD@{9}: rebase (finish): returning to refs/heads/main
    2437376 (origin/main) HEAD@{10}: rebase (squash): firstOne
    4bb0287 HEAD@{11}: rebase (squash): # 这是一个 2 个提交的组合。
    d5d8c16 HEAD@{12}: rebase (start): checkout HEAD~3
    ebbe0a1 HEAD@{13}: commit: third
    f768621 HEAD@{14}: commit: second
    d5d8c16 HEAD@{15}: commit (amend): firstOne
    a41872c HEAD@{16}: commit (amend): firstOne
    e50328a HEAD@{17}: commit: first
    6dee34b HEAD@{18}: reset: moving to HEAD
    6dee34b HEAD@{19}: commit: add ConversionService
    a0313c5 HEAD@{20}: commit: NIO
    5f611c7 HEAD@{21}: commit: SPI
  2. 记下版本号(commit id SHA)使用 git rest –hard <版本号> 恢复。

git reflog 对比 git log

  • git log是显示当前的HEAD和它的祖先的,递归是沿着当前指针的父亲,父亲的父亲。。。

  • git reflog根本不遍历HEAD的祖先。它是HEAD所指向的一个顺序的提交列表。记录下所有在本地进行的操作。

    不包括pushes fetches与远程分支有关的命令。

签出指定文件

场景:此时正在 A 分支编码,想参考 B 分支的一个类(A分支没有之歌文件)。

使用git checkout <分支> -- <文件路径>

查看本地分支是否合并

场景:年纪大了记性会不好,时常会忘记feature分支是否合并到maser分支。

使用

  1. git branch --no-merged 查看未被合并的分支
  2. git branch --merged 查看已被合并的分支

总结

此篇没有罗列 Git 各种命令的能力,而是结合开发场景介绍了使用频率较高的几个 Git 命令。希望对你有帮助尽快摆脱 Git 三板斧(git commit、git pull、git push)的囧境。