git show命令

git show使用方法如下:

1
git show [<options>] [<object>…]

该命令命令可以查看git中多种类型的对象,比如blobstreescommitstags

  • 对于提交commits:显示log信息以及文本差异(textual diff.

  • 对于tags:显示tag的信息以及其引用的对象

  • 对于tree:显示名称,相当于git ls-tree with --name-only

  • 对于普通(plain)blobs:显示其内容

选择修订版本

获取单个修订版本

通过commit id(SHA-1)

只需要提供 SHA-1 的前几个字符就可以获得对应的那次提交,当然你提供的 SHA-1 字符数量不得少于 4 个,并且没有歧义——也就是说,当前仓库中只有一个对象以这段 SHA-1 开头。

例如查看一次指定的提交,假设你执行 git log 命令来查看之前新增一个功能的那次提交:

1
2
3
4
5
6
7
8
9
10
11
12
13
MINGW64 /d/coding/git-playground (master)
$ git log -3
commit 81d81eb4595b06d26e7fbeab31fce91816bd8aa4 (HEAD -> master, origin/master)
Author: slimterry <slimterry@qq.com>
Date: Tue Mar 26 10:14:48 2024 +0800

其他人在master上的提交2

commit 144b12af62f83bbb3e85448371d67afbea999ba8
Author: slimterry <slimterry@qq.com>
Date: Tue Mar 26 10:14:14 2024 +0800

其他人在master上的提交

以下3个git show命令查看的是同一版本:

1
2
3
$ git show 81d81eb4595b06d26e7fbeab31fce91816bd8aa4
$ git show 81d81eb4595b06d26e7
$ git show 81d81eb

通常 8 到 10 个字符就已经足够在一个项目中避免 SHA-1 的歧义。

通过分支引用

指明一次提交最直接的方法是有一个指向它的分支引用。 这样你就可以在任意一个 Git 命令中使用这个分支名来代替对应的提交对象或者 SHA-1 值

假设我们有如下的git log信息:

1
2
3
$ git log --all --oneline --graph
* a1c6f33 (origin/serverfix, serverfix) 新增index.html[serverfix]
* 81d81eb (HEAD -> master, origin/master) 其他人在master上的提交2

serverfix指针指向提交对象a1c6f33

例如,你想要查看一个分支的最后一次提交的对象,那么以下的命令是等价的:

1
2
$ git show a1c6f33
$ git show serverfix

如果你想知道某个分支指向哪个特定的 SHA-1,或者想看任何一个例子中被简写的 SHA-1 ,你可以使用一个叫做 rev-parse 的 Git 探测工具。

1
2
3
4
5
$ git rev-parse serverfix # 查看分支指向哪个提交对象
a1c6f33925730cb10158c912f1b321fb688bae62

$ git rev-parse a1c6f33 # 查看该字符串是哪个提交对象id的简写
a1c6f33925730cb10158c912f1b321fb688bae62

通过引用日志(reflog)

当你在工作时, Git 会在后台保存一个引用日志(reflog),引用日志记录了最近几个月你的 HEAD 和分支引用所指向的历史。你可以使用 git reflog 来查看引用日志:

1
2
3
4
5
6
7
8
9
10
MINGW64 /d/coding/git-playground (master)
$ git checkout serverfix # 切换分支
Switched to branch 'serverfix'
Your branch is up to date with 'origin/serverfix'.

MINGW64 /d/coding/git-playground (serverfix)
$ git reflog
a1c6f33 (HEAD -> serverfix, origin/serverfix) HEAD@{0}: checkout: moving from master to serverfix # 记录从master切换到serverfix
81d81eb (origin/master, master) HEAD@{1}: checkout: moving from serverfix to master
a1c6f33 (HEAD -> serverfix, origin/serverfix) HEAD@{2}: commit: 新增index.html[serverfix]

每当你的 HEAD 所指向的位置发生了变化,Git 就会将这个信息存储到引用日志这个历史记录里。 通过这些数据,你可以很方便地获取之前的提交历史。

你可以使用 @{n} 来引用 reflog 中输出的提交记录, 如果你想查看仓库中 HEAD 在3次前的所指向的提交:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ git show HEAD@{3}
commit 81d81eb4595b06d26e7fbeab31fce91816bd8aa4 (origin/master, master)
Author: slimterry <slimterry@qq.com>
Date: Tue Mar 26 10:14:48 2024 +0800

其他人在master上的提交2

diff --git a/B.java b/B.java
index e69de29..ad1ece6 100644
--- a/B.java
+++ b/B.java
@@ -0,0 +1 @@
+// 其他人在master上的提交2

你同样可以使用这个语法来查看某个分支在一定时间前的位置。 例如,查看你的 master 分支在昨天的时候指向了哪个提交,你可以输入:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ git show master@{yesterday}
commit 81d81eb4595b06d26e7fbeab31fce91816bd8aa4 (origin/master, master)
Author: slimterry <slimterry@qq.com>
Date: Tue Mar 26 10:14:48 2024 +0800

其他人在master上的提交2

diff --git a/B.java b/B.java
index e69de29..ad1ece6 100644
--- a/B.java
+++ b/B.java
@@ -0,0 +1 @@
+// 其他人在master上的提交2

就会显示昨天该分支的顶端指向了哪个提交。 这个方法只对还在你引用日志里的数据有用,所以不能用来查好几个月之前的提交

可以运行 git log -g 来查看类似于 git log 输出格式的引用日志信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ git log -g serverfix
commit a1c6f33925730cb10158c912f1b321fb688bae62 (HEAD -> serverfix, origin/serverfix)
Reflog: serverfix@{0} (slimterry <slimterry@qq.com>)
Reflog message: commit: 新增index.html[serverfix]
Author: slimterry <slimterry@qq.com>
Date: Tue Mar 26 14:26:29 2024 +0800

新增index.html[serverfix]

commit 81d81eb4595b06d26e7fbeab31fce91816bd8aa4 (origin/master, master)
Reflog: serverfix@{1} (slimterry <slimterry@qq.com>)
Reflog message: branch: Created from HEAD
Author: slimterry <slimterry@qq.com>
Date: Tue Mar 26 10:14:48 2024 +0800

其他人在master上的提交2

值得注意的是,引用日志只存在于本地仓库,一个记录你在你自己的仓库里做过什么的日志。 其他人拷贝的仓库里的引用日志不会和你的相同;而你新克隆一个仓库的时候,引用日志是空的,因为你在仓库里还没有操作。 git show HEAD@{2.months.ago} 这条命令只有在你克隆了一个项目至少两个月时才会有用——如果你是五分钟前克隆的仓库,那么它将不会有结果返回。

通过祖先引用

祖先引用是另一种指明一个提交的方式。 如果你在引用的尾部加上一个 ^, Git 会将其解析为该引用的上一个提交。 假设你的提交历史是:

1
2
3
4
5
6
7
8
9
10
11
$ git log --oneline --graph
* 81d81eb (HEAD -> master, origin/master) 其他人在master上的提交2
* 144b12a 其他人在master上的提交
* ed9cf4a 提交.gitignore
* 152d12e Merge branch 'hotfix'
|\
| * 068a534 修复XX问题[hotfix]
* | 8507b7f 修改A.java文件[master]
|/
* 519c74f 提交git ignore
* dcd138e first commit

你可以使用 HEAD^ 来查看上一个提交,也就是 “HEAD 的父提交”:

1
2
3
4
5
$ git show HEAD # 查看HEAD所指版本
commit 81d81eb4595b06d26e7fbeab31fce91816bd8aa4 (HEAD -> master, origin/master)

$ git show HEAD^ # 查看HEAD所指的上一个版本
commit 144b12af62f83bbb3e85448371d67afbea999ba8

注意:

  • 命令git show HEAD^等价于git show HEAD^1

  • git show HEAD^2会直接报错,正确的写法应该是HEAD~2

    1
    2
    3
    4
    $ git show HEAD^2
    fatal: ambiguous argument 'HEAD^2': unknown revision or path not in the working tree.
    Use '--' to separate paths from revisions, like this:
    'git <command> [<revision>...] -- [<file>...]'

你也可以在 ^ 后面添加一个数字——例如 152d12e^2 代表 “152d12e第二父提交” 这个语法只适用于合并(merge)的提交,因为合并提交会有多个父提交。 第一父提交是你合并时所在分支,而第二父提交是你所合并的分支:

比如本例中,我们有一次合并提交:

1
2
3
4
5
6
7
8
9
$ git log --oneline --graph
...
* 152d12e Merge branch 'hotfix'
|\
| * 068a534 修复XX问题[hotfix] # 第二父提交
* | 8507b7f 修改A.java文件[master] # 第一父提交
|/
* 519c74f 提交git ignore
...

查看此处合并提交的2个父提交:

1
2
3
4
5
$ git show 152d12e^ # 第一父提交
commit 8507b7fea93ba1c6db233f8cf8e9d7b977fff4e1

$ git show 152d12e^2
commit 068a534984eea6b400ccc574fa0b528bd144ce53 # 第二父提交

这个语法只适用于合并(merge)的提交,如果不是合并提交,比如对于本例中的提交81d81eb:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ git log --oneline --graph
* 81d81eb (HEAD -> master, origin/master) 其他人在master上的提交2
* 144b12a 其他人在master上的提交
* ed9cf4a 提交.gitignore
...

$ git show 81d81eb^
commit 144b12af62f83bbb3e85448371d67afbea999ba8

$ git show 81d81eb^2
fatal: ambiguous argument '81d81eb^2': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'

尝试查看版本81d81eb^2时,会直接报错!

另一种指明祖先提交的方法是 ~。 同样是指向第一父提交,因此 HEAD~HEAD^ 是等价的。 而区别在于你在后面加数字的时候。 HEAD~2 代表 “第一父提交的第一父提交”,也就是 “祖父提交” —— Git 会根据你指定的次数获取对应的第一父提交。 例如,在之前的列出的提交历史中,HEAD~2 就是:

1
2
$ git show HEAD~2
commit ed9cf4a352ba55396c603eb9c0431653305dab5e

你也可以组合使用这两个语法 —— 你可以通过 HEAD~3^2 来取得之前引用的**第二父提交(**假设它是一个合并提交)。

获取提交区间内的所有版本、

你已经学会如何单次的提交,现在来看看如何指明一定区间的提交。 当你有很多分支时,这对管理你的分支时十分有用,你可以用提交区间来解决 “这个分支还有哪些提交尚未合并到主分支?” 的问题

双点

最常用的指明提交区间语法是双点。 这种语法可以让 Git 选出在一个分支中而不在另一个分支中的提交。假设你的提交历史如下:

image-20240329104500695

1
2
3
4
5
6
7
8
9
MINGW64 /d/coding/git-playground (master)
$ git log --oneline --graph --all
* 06a5c98 (experiment) D-experiment
* ee0b728 c-experiment
| * 1c31a26 (HEAD -> master) F-master
| * de64ba7 E-master
|/
* e611884 B-master
* 72b163e A-master

image-20240329105708676

你想要查看 experiment 分支中还有哪些提交尚未被合并入 master 分支。 你可以使用 master..experiment 来让 Git 显示这些提交。也就是 “在 experiment 分支中而不在 master 分支中的提交”。

1
2
3
4
5
6
7
8
9
10
11
12
$ git log master..experiment
commit 06a5c98b0ed10fba3c62e466bc91c5c8314107a9 (experiment)
Author: slimterry <slimterry@qq.com>
Date: Fri Mar 29 10:51:23 2024 +0800

D-experiment

commit ee0b72818537fdfd8528c27a481597cb27528add
Author: slimterry <slimterry@qq.com>
Date: Fri Mar 29 10:51:10 2024 +0800

c-experiment

反过来,如果你想查看在 master 分支中而不在 experiment 分支中的提交,你只要交换分支名即可。 experiment..master 会显示在 master 分支中而不在 experiment 分支中的提交:

1
2
3
4
5
6
7
8
9
10
11
12
$ git log experiment..master
commit 1c31a26869fb13b45b7bd2d7c4178333a39d5bad (HEAD -> master)
Author: slimterry <slimterry@qq.com>
Date: Fri Mar 29 10:48:56 2024 +0800

F-master

commit de64ba762c2b18fad604d3b0a801176c8fedb2a8
Author: slimterry <slimterry@qq.com>
Date: Fri Mar 29 10:48:28 2024 +0800

E-master

这可以让你保持 experiment 分支跟随最新的进度以及查看你即将合并的内容。

另一个常用的场景是查看你即将推送到远端的内容:

1
git log origin/master..HEAD

即在HEAD(当前分支)上,但是不在 origin/master上的提交。

这个命令会输出在你当前分支中而不在远程 origin 中的提交。 如果你执行了 git push 并且你的当前分支正在跟踪 origin/mastergit log origin/master..HEAD 所输出的提交将会被传输到远端服务器。 如果你留空了其中的一边, Git 会默认为 HEAD。 例如, git log origin/master.. 将会输出与之前例子相同的结果 —— Git 使用 HEAD代替留空的一边。

比如上述的例子中git log experiment..master等价于git log experiment..,注意前提是此时在master分支(即:HEAD -> master)。

1
2
3
4
5
6
7
8
9
10
11
12
13
MINGW64 /d/coding/git-playground (master) # 当前分支为master
$ git log experiment.. # 默认添加HEAD,而此时HEAD指向master
commit 1c31a26869fb13b45b7bd2d7c4178333a39d5bad (HEAD -> master)
Author: slimterry <slimterry@qq.com>
Date: Fri Mar 29 10:48:56 2024 +0800

F-master

commit de64ba762c2b18fad604d3b0a801176c8fedb2a8
Author: slimterry <slimterry@qq.com>
Date: Fri Mar 29 10:48:28 2024 +0800

E-master

多点

双点语法..很好用,但有时候你可能需要两个以上的分支才能确定你所需要的修订,比如查看哪些提交是被包含在某些分支中的一个,但是不在你当前的分支上。 Git 允许你在任意引用前加上 ^ 字符或者 --not 来指明你不希望提交被包含其中的分支。 因此下列3个命令是等价的:

1
2
3
$ git log refA..refB
$ git log ^refA refB
$ git log refB --not refA

这个语法很好用,因为你可以在查询中指定超过两个的引用,这是双点语法无法实现的。 比如,你想查看所有被 refArefB 包含的但是不被 refC 包含的提交,你可以输入下面中的任意一个命令

1
2
$ git log refA refB ^refC
$ git log refA refB --not refC

这就构成了一个十分强大的修订查询系统,你可以通过它来查看你的分支里包含了哪些东西。

比如:我们的仓库分支情况如下:

1
2
3
4
5
6
7
8
9
10
MINGW64 /d/coding/git-playground (client)
$ git log --oneline --graph --all
* 308a75c (HEAD -> client) G
* 1c31a26 (master) F-master
* de64ba7 E-master
| * 06a5c98 (experiment) D-experiment
| * ee0b728 c-experiment
|/
* e611884 B-master
* 72b163e A-master

image-20240329133942065

我们可以查看那些提交被clientmaster包括,但是不被experiment包括时,可以通过如下命令查看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ git log client master ^experiment
commit 308a75ca60002f29227278b42896dde24c587971 (HEAD -> client)
Author: slimterry <slimterry@qq.com>
Date: Fri Mar 29 13:36:34 2024 +0800

G

commit 1c31a26869fb13b45b7bd2d7c4178333a39d5bad (master)
Author: slimterry <slimterry@qq.com>
Date: Fri Mar 29 10:48:56 2024 +0800

F-master

commit de64ba762c2b18fad604d3b0a801176c8fedb2a8
Author: slimterry <slimterry@qq.com>
Date: Fri Mar 29 10:48:28 2024 +0800

E-master

三点

最后一种主要的区间选择语法是三点,这个语法可以选择出被两个引用中的一个包含但又不被两者同时包含的提交。 再看看之前多点例子中的提交历史:

1
2
3
4
5
6
7
8
9
$ git log --oneline --graph --all
* 308a75c (HEAD -> client) G
* 1c31a26 (master) F-master
* de64ba7 E-master
| * 06a5c98 (experiment) D-experiment
| * ee0b728 c-experiment
|/
* e611884 B-master
* 72b163e A-master

如果你想看 master 或者 experiment 中包含的但不是两者共有的提交,你可以执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ git log master...experiment
commit 06a5c98b0ed10fba3c62e466bc91c5c8314107a9 (experiment)
Author: slimterry <slimterry@qq.com>
Date: Fri Mar 29 10:51:23 2024 +0800

D-experiment

commit ee0b72818537fdfd8528c27a481597cb27528add
Author: slimterry <slimterry@qq.com>
Date: Fri Mar 29 10:51:10 2024 +0800

c-experiment

commit 1c31a26869fb13b45b7bd2d7c4178333a39d5bad (master)
Author: slimterry <slimterry@qq.com>
Date: Fri Mar 29 10:48:56 2024 +0800

F-master

commit de64ba762c2b18fad604d3b0a801176c8fedb2a8
Author: slimterry <slimterry@qq.com>
Date: Fri Mar 29 10:48:28 2024 +0800

E-master

注意:

  • 这里的 master 或者 experiment 中包含指的不是同时包含于这2个分支的
  • 三点命令的2个参数可以交换顺序,不影响结果的展示。

这和通常 log 按日期排序的输出一样,仅仅给出了4个提交的信息。

这种情形下,log 命令的一个常用参数是 --left-right,它会显示每个提交到底处于哪一侧的分支。 这会让输出数据更加清晰。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ git log experiment...master --left-right
commit < 06a5c98b0ed10fba3c62e466bc91c5c8314107a9 (experiment) # 属于左侧
Author: slimterry <slimterry@qq.com>
Date: Fri Mar 29 10:51:23 2024 +0800

D-experiment

commit < ee0b72818537fdfd8528c27a481597cb27528add # 属于左侧
Author: slimterry <slimterry@qq.com>
Date: Fri Mar 29 10:51:10 2024 +0800

c-experiment

commit > 1c31a26869fb13b45b7bd2d7c4178333a39d5bad (master) # 属于右侧
Author: slimterry <slimterry@qq.com>
Date: Fri Mar 29 10:48:56 2024 +0800

F-master

commit > de64ba762c2b18fad604d3b0a801176c8fedb2a8 # 属于右侧
Author: slimterry <slimterry@qq.com>
Date: Fri Mar 29 10:48:28 2024 +0800

E-master

注意:

  • 此处的左侧右侧指的是三点命令中,分支处于...的左侧还是右侧。

储藏与清理

有时,当你在项目的一部分上已经工作一段时间后,所有东西都进入了混乱的状态,而这时你想要切换到另一个分支做一点别的事情。 问题是,你不想仅仅因为过会儿回到这一点而为做了一半的工作创建一次提交。 针对这个问题的答案是 git stash 命令。

储藏会处理工作目录的脏的状态 - 即,修改的跟踪文件与暂存改动 - 然后将未完成的修改保存到一个栈上,而你可以在任何时候重新应用这些改动。

注意:

  • 针对已追踪文件的修改才会被stash,无论是否已经

储藏工作

为了演示,进入项目并改动几个文件,然后可能暂存其中的一个改动。 如果运行 git status,可以看到有改动的状态:

1
2
3
4
5
6
7
MINGW64 /d/coding/git-playground (master)
$ git status
On branch master
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: C.java

现在想要切换分支到分支hotfix,但是还不想要提交之前的工作;所以储藏修改。 将新的储藏推送到栈上,运行 git stashgit stash save

1
2
3
MINGW64 /d/coding/git-playground (master)
$ git stash
Saved working directory and index state WIP on master: 67e2ce0 c

储藏之后,工作目录是干净的了

1
2
3
4
MINGW64 /d/coding/git-playground (master)
$ git status
On branch master
nothing to commit, working tree clean

在这时,你能够轻易地切换分支并在其他地方工作:

1
2
3
MINGW64 /d/coding/git-playground (master)
$ git checkout hotfix
Switched to branch 'hotfix'

你的修改被存储在栈上。 要查看储藏的东西,可以使用 git stash list

1
2
3
4
MINGW64 /d/coding/git-playground (hotfix)
$ git stash list
stash@{0}: WIP on hotfix: b5e0e78 提交D.java
stash@{1}: WIP on master: 67e2ce0 c

在本例中,有一个之前做的储藏,所以你接触到了2个不同的储藏工作。

可以通过原来 stash 命令的帮助提示中的命令将你刚刚储藏的工作重新应用

1
2
3
4
5
6
7
8
9
 MINGW64 /d/coding/git-playground (hotfix)
$ git stash apply
On branch hotfix
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: B.java

no changes added to commit (use "git add" and/or "git commit -a")

注意git stash apply默认会应用最近的一次储藏stash@{0},如果想要应用其中一个更旧的储藏,可以通过名字指定它,比如:

1
2
MINGW64 /d/coding/git-playground (hotfix)
$ git stash apply stash@{1}

需要注意的是:执行git stash apply不会清除相应的储藏内容,本例中经过上面2次stash命令后,储藏内容仍然存在:

1
2
3
$ git stash list
stash@{0}: WIP on hotfix: b5e0e78 提交D.java
stash@{1}: WIP on master: 67e2ce0 c

可以看到 Git 重新修改了当你保存储藏时撤消的文件。 在本例中,当尝试应用储藏时有一个干净的工作目录,并且尝试将它应用在保存它时所在的分支;

但是有一个干净的工作目录与应用在同一分支并不是成功应用储藏的充分必要条件。 可以在一个分支上保存一个储藏,切换到另一个分支,然后尝试重新应用这些修改。

当应用储藏时工作目录中也可以有修改与未提交的文件 - 如果有任何东西不能干净地应用,Git 会产生合并冲突

应用储藏可能发生冲突

当我们通过git stash apply应用储藏内容时,可能会发生冲突,比如:假设我们已经修改了B.java,我们储藏中的内容也对该文件的内容进行了修改:

1
2
3
4
5
6
7
8
9
10
11
12
$ git stash apply
error: Your local changes to the following files would be overwritten by merge:
B.java
Please commit your changes or stash them before you merge.
Aborting
On branch hotfix
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: B.java

no changes added to commit (use "git add" and/or "git commit -a")

查看储藏内容

通过git show可以查看储藏的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
86182@yawen MINGW64 /d/coding/git-playground (hotfix)
$ git show stash@{0}
commit 35faa92dcb85683c7af1ce488efbd93014fdaf8a (refs/stash)
Merge: b5e0e78 32f769b
Author: slimterry <slimterry@qq.com>
Date: Fri Mar 29 14:34:22 2024 +0800

WIP on hotfix: b5e0e78 提交D.java

diff --cc B.java
index e69de29,e69de29..27d92bf
--- a/B.java
+++ b/B.java
@@@ -1,0 -1,0 +1,1 @@@
++###########

删除储藏内容

通过git stash drop stashname可以删除某个stash

1
2
3
4
5
6
7
8
9
10
11
12
$ git stash list
stash@{0}: WIP on hotfix: b5e0e78 提交D.java
stash@{1}: WIP on master: 67e2ce0 c

86182@yawen MINGW64 /d/coding/git-playground (hotfix)
$ git stash drop stash@{0}
Dropped stash@{0} (35faa92dcb85683c7af1ce488efbd93014fdaf8a)

86182@yawen MINGW64 /d/coding/git-playground (hotfix)
$ git stash list
stash@{0}: WIP on master: 67e2ce0 c

也可以运行 git stash pop 来应用储藏然后立即从栈上扔掉它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MINGW64 /d/coding/git-playground (hotfix)
$ git stash list
stash@{0}: WIP on master: 67e2ce0 c

MINGW64 /d/coding/git-playground (hotfix)
$ git stash pop
On branch hotfix
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: C.java

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (ac1f4723b275d86c14127b17395be9d2cd759f59)

–index选项

如果不添加--index选项,则应用(apply)储藏对象时,原本已暂存的对象不会被重新暂存,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ git status -s
M B.java # 已暂存
M C.java # 未暂存

$ git stash # 创建储藏
Saved working directory and index state WIP on testchange2: f9ae487 xxx

# 直接应用储藏
$ git stash pop
On branch testchange2
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: B.java
modified: C.java

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (071d62cc3c4bd28dcc7874c035ff272b13385ff1)

$ git status -s
M B.java # 变为未暂存状态
M C.java

同样的情况,如果我们加上--index选项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ git status -s
M B.java # 已暂存
M C.java # 未暂存

$ git stash
Saved working directory and index state WIP on testchange2: f9ae487 xxx

$ git stash apply --index
On branch testchange2
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: B.java

Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: C.java

$ git status -s
M B.java # 仍然是已暂存的状态
M C.java

–keep-index选项

有几个储藏的变种可能也很有用。 第一个非常流行的选项是 stash save 命令的 --keep-index 选项。 它告诉 Git 不要储藏任何你通过 git add 命令已暂存的东西。

当你做了几个改动并只想提交其中的一部分,过一会儿再回来处理剩余改动时,这个功能会很有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ git st
On branch hotfix
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: B.java

Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: C.java

# 若使用-s命令,显示如下:
$ git st -s
M B.java # 已暂存
M C.java # 未暂存

此时执行git stash --keep-index

1
2
3
4
5
$ git stash --keep-index
Saved working directory and index state WIP on hotfix: b5e0e78 提交D.java

$ git status -s
M B.java

可以看到:之前已经暂存过的文件B.java并没有被放到stash中,未暂存的文件C.java的改动被放到了stash中。

如果上面的stash操作不添加 --keep-index 选项,则会同时储藏如上2个更改的文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ git status
On branch hotfix
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: B.java

Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: C.java

$ git stash
Saved working directory and index state WIP on hotfix: b5e0e78 提交D.java

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

–include-untracked选项

git stash 只会储藏已经在索引中的文件。 如果指定 --include-untracked-u 标记,Git 也会储藏任何创建的未跟踪文件

1
2
3
4
$ git status -s
M B.java
M C.java
?? X.java

执行 git stash -u

1
2
3
4
5
6
$ git stash -u
Saved working directory and index state WIP on hotfix: b5e0e78 提交D.java

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

若同样的操作不添加-u选项,则结果如下:

1
2
3
4
5
Saved working directory and index state WIP on hotfix: b5e0e78 提交D.java

86182@yawen MINGW64 /d/coding/git-playground (hotfix)
$ git status -s
?? X.java # 不会暂存未跟踪的文件

–patch选项(交互式)

如果指定了 --patch 标记,Git 不会储藏所有修改过的任何东西,但是会交互式地提示哪些改动想要储藏、哪些改动需要保存在工作目录中

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
$ git status -s
M B.java
M C.java
?? X.java # 未追踪文件

$ git stash --patch
diff --git a/B.java b/B.java # B.java文件
index e69de29..190a180 100644
--- a/B.java
+++ b/B.java
@@ -0,0 +1 @@
+123
(1/1) Stash this hunk [y,n,q,a,d,e,?]? y # yes

diff --git a/C.java b/C.java # C.java
index e69de29..3bd9bce 100644
--- a/C.java
+++ b/C.java
@@ -0,0 +1 @@
+3333
(1/1) Stash this hunk [y,n,q,a,d,e,?]? n # no

Saved working directory and index state WIP on hotfix: b5e0e78 提交D.java

$ git status -s
M C.java
?? X.java

注意:

  • 因为没有添加-u选项,因此交互式stash询问的文件列表中不包含X.java,另外,这2个选项不能一起使用:

    1
    2
    $ git stash --patch -u
    Can't use --patch and --include-untracked or --all at the same time

从储创建分支

如果储藏了一些工作,将它留在那儿了一会儿,然后继续在储藏的分支上工作,在重新应用工作时可能会有问题。 如果应用尝试修改刚刚修改的文件,你会得到一个合并冲突并不得不解决它。 如果想要一个轻松的方式来再次测试储藏的改动,可以运行 git stash branch 创建一个新分支,检出储藏工作时所在的提交,重新在那应用工作,然后在应用成功后扔掉储藏:

首先创建演示用的stash:

1
2
3
4
5
6
7
8
9
10
11
12
13
MINGW64 /d/coding/git-playground (hotfix)
$ git st -s
M B.java
M C.java
?? X.java

MINGW64 /d/coding/git-playground (hotfix)
$ git stash
Saved working directory and index state WIP on hotfix: b5e0e78 提交D.java

MINGW64 /d/coding/git-playground (hotfix)
$ git stash list
stash@{0}: WIP on hotfix: b5e0e78 提交D.java

将储藏内容应用到一个新创建的分支上:

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
 MINGW64 /d/coding/git-playground (hotfix)
$ git stash branch testchanges
Switched to a new branch 'testchanges'
On branch testchanges
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: B.java
modified: C.java

Untracked files:
(use "git add <file>..." to include in what will be committed)
X.java

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (b5efc71b50efa1ddd809903925f9c6b022bb8752)

MINGW64 /d/coding/git-playground (testchanges) # 注意已经自动切换到新分支上
$ git status -s
M B.java
M C.java
?? X.java

n MINGW64 /d/coding/git-playground (testchanges)
$ git stash list

# 此时stash中内容已经被弹出

注意:

  • 如果有多个stash,该命令也只会应用最新的一个stash,即:stash@{0}

清理工作目录

对于工作目录中一些工作或文件,你想做的也许不是储藏而是移除git clean 命令会帮你做这些事。

有一些通用的原因比如说为了移除由合并或外部工具生成的东西,或是为了运行一个干净的构建而移除之前构建的残留。

你需要谨慎地使用这个命令,因为它被设计为从工作目录中移除未被追踪的文件。 如果你改变主意了,你也不一定能找回来那些文件的内容。 一个更安全的选项是运行 git stash --all 来移除每一样东西并存放在栈中。

你可以使用git clean命令去除冗余文件或者清理工作目录。 使用git clean -f -d命令来移除工作目录中所有未追踪的文件以及空的子目录。 -f 意味着 强制 或 “确定移除”。

如果只是想要看看它会做什么,可以使用 -n 选项来运行命令,这意味着 “做一次演习然后告诉你 将要 移除什么”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ git status -s
A X.java # 已暂存
?? YY.java # 未跟踪

$ git clean -d -n
Would remove YY.java # 将会删除YY.java文件


$ git clean -d
fatal: clean.requireForce defaults to true and neither -i, -n, nor -f given; refusing to clean

$ git clean -d -f
Removing YY.java

$ git status -s
A X.java

注意:

  • 直接执行git clean -d会报错,需要配置参数 -i, -n,或 -f 使用

默认情况下,git clean 命令只会移除没有忽略的未跟踪文件。 任何与 .gitiignore 或其他忽略文件中的模式匹配的文件都不会被移除。 如果你也想要移除那些文件,例如为了做一次完全干净的构建而移除所有由构建生成的 .o 文件,可以给 clean 命令增加一个 -x 选项。

-x

Don’t use the standard ignore rules (see gitignore(5)), but still use the ignore rules given with -e options from the command line. This allows removing all untracked files, including build products. This can be used (possibly in conjunction with git restore or git reset) to create a pristine working directory to test a clean build.

如果不知道 git clean 命令将会做什么,在将 -n 改为 -f 来真正做之前总是先用 -n 来运行它做双重检查。 另一个小心处理过程的方式是使用 -i 或 “interactive” 标记来运行它。

这将会以交互模式运行 clean 命令。

搜索

无论仓库里的代码量有多少,你经常需要查找一个函数是在哪里调用或者定义的,或者一个方法的变更历史。 Git 提供了两个有用的工具来快速地从它的数据库中浏览代码和提交

文件内容搜索 Git Grep

查看工作区文件

Git 提供了一个 grep 命令,你可以很方便地从提交历史或者工作目录中查找一个字符串或者正则表达式

默认情况下 Git 会查找你工作目录的文件。 你可以传入 -n 参数来输出 Git 所找到的匹配行行号

1
2
$ git grep -n 'preorderTraversal'
A.java:1: public List<Integer> preorderTraversal(TreeNode root) {

该命令相当于:

1
2
$ grep -rn 'preorderTraversal' # 注意此处是直接使用系统的grep命令
java:1: public List<Integer> preorderTraversal(TreeNode root) {

grep 命令还有一些其他有趣的选项:

  • --count:使 Git 输出概述的信息,仅仅包括哪些文件包含匹配以及每个文件包含了多少个匹配:

    1
    2
    3
    $ git grep --count 'preorderTraversal'
    A.java:1 # 匹配一次
    B.java:1 # 匹配一次

    相当于:

    1
    2
    3
    4
    5
    $ grep -c 'preorderTraversal' *
    A.java:1
    B.java:1
    C.java:0
    # 注意:不加-r选项,否则会递归搜索当前层级的目录
  • -p:等价于--show-function看匹配的行是属于哪一个方法或者函数

  • --and:也就是在同一行同时包含多个匹配,比如查看同时包含字符串isEmptyroot

    1
    2
    3
    4
    5
    6
    7
    $  git grep --break --heading -n -e isEmpty --and -e root
    A.java
    7: while (!stack.isEmpty() || null != root) {

    B.java
    7: while (!stack.isEmpty() || null != root) {

    --break

    Print an empty line between matches from different files.

    --heading

    Show the filename above the matches in that file instead of at the start of each shown line.

搜索某个tag

可以直接为git grep命令传递一个tag名称,来查找该tag中的内容(无需检出tag):

1
2
3
4
5
6
7
8
9
10
$ git tag
V1.0

$ cat B.java
import java.util.*; # 工作区中的B.java只有一行

$ git grep preorderTraversal V1.0 # 查找V1.0版本
V1.0:A.java: public List<Integer> preorderTraversal(TreeNode root) {
V1.0:B.java: public List<Integer> preorderTraversal(TreeNode root) {
# V1.0版本的B.java中版本目标字符串

注意:

不添加具体的搜索对象,比如此处的tag时,默认搜索的是工作区间

1
2
3
$ git grep preorderTraversal
A.java: public List<Integer> preorderTraversal(TreeNode root) {
# B.java中不包含目标字符串

搜索某个提交版本

比如查看某个版本中是否包含字符串MAX_CLIENT_NUM:

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
MINGW64 /d/coding/git-playground (master)
$ git log --all --oneline --graph
* edf6a15 (HEAD -> master) 修改b.java 4
* 3fd607e 修改b.java 3
* c12c285 修改b.java 2
* 8bb668c 修改b.java 1
* c5c685c 修改b.java
* f651a30 (tag: V1.0) 修改B.java
* dbc734b 修改A.java
* 7141c32 yyysdfk
| * 4e40fd0 (refs/stash) WIP on testchange2: f9ae487 xxx
| |\
| | * 9f6b560 index on testchange2: f9ae487 xxx
| |/
| * f9ae487 (testchange2) xxx
| * b5e0e78 (testchanges, hotfix) 提交D.java
|/
* 67e2ce0 c
* c9c8710 B
* 9749b1c A

MINGW64 /d/coding/git-playground (master)
$ git grep 'MAX_CLIENT_NUM' 8bb668c # 提交ID
8bb668c:B.java: private static final string MAX_CLIENT_NUM = 5; # 匹配到结果

MINGW64 /d/coding/git-playground (master)
$ git grep 'MAX_CLIENT_NUM' f651a30 # 未匹配到结果

相比grep等系统命令的优点

相比于一些常用的搜索命令比如 grepackgit grep 命令有一些的优点。 第一就是速度非常快,第二是你不仅仅可以可以搜索工作目录,还可以搜索任意的 Git 树。 在上一个例子中,我们在一个旧版本的V1.0中查找,而不是当前检出的版本

提交日志搜索

如果你想知道是什么 时候 存在或者引入的。 git log 命令有许多强大的工具可以通过提交信息甚至是 diff 的内容来找到某个特定的提交。

搜索新增或删除某个字符串的提交

注意:

  • 查找的是文件改动的内容,而不是commit message

比如查看新增或删除了字符串MAX_CLIENT_NU的提交:

1
2
3
4
5
6
7
8
9
10
11
12
$ git log -SMAX_CLIENT_NUM
commit edf6a156f91ba3682273e3488c166d009beb13fe (HEAD -> master)
Author: slimterry <slimterry@qq.com>
Date: Mon Apr 1 10:22:59 2024 +0800

修改b.java 4

commit 8bb668c784541ad5cf34f8d22685db727fb9175c
Author: slimterry <slimterry@qq.com>
Date: Mon Apr 1 10:20:49 2024 +0800

修改b.java 1

本例中:在8bb668c中引入了字符串MAX_CLIENT_NU

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ git show 8bb668c
commit 8bb668c784541ad5cf34f8d22685db727fb9175c
Author: slimterry <slimterry@qq.com>
Date: Mon Apr 1 10:20:49 2024 +0800

修改b.java 1

diff --git a/B.java b/B.java
index 017d7d3..fd03efe 100644
--- a/B.java
+++ b/B.java
@@ -1 +1,5 @@
import java.util.*;
+
+public class B{
+ private static final string MAX_CLIENT_NUM = 5; # 新增该行
+}

edf6a15中移除了该行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ git show edf6a15
commit edf6a156f91ba3682273e3488c166d009beb13fe (HEAD -> master)
Author: slimterry <slimterry@qq.com>
Date: Mon Apr 1 10:22:59 2024 +0800

修改b.java 4

diff --git a/B.java b/B.java
index 2e68846..6b97f2c 100644
--- a/B.java
+++ b/B.java
@@ -1,7 +1,6 @@
import java.util.*;

public class B{
- private static final string MAX_CLIENT_NUM = 5; # 移除该行
private static final string MAX_WAIT_SEC = 10;
private static final string MAX_POOL_SIZE = 10;
}

行日志搜索

行日志搜索是另一个相当高级并且有用的日志搜索功能。 这是一个最近新增的不太知名的功能,但却是十分有用。 在 git log 后加上 -L 选项即可调用,它可以展示代码中一行或者一个函数的历史。

-L,:

-L::

Trace the evolution of the line range given by ,, or by the function name regex , within the . You may not give any pathspec limiters. This is currently limited to a walk starting from a single revision, i.e., you may only give zero or one positive revision arguments, and and (or ) must exist in the starting revision. You can specify this option more than once. Implies --patch. Patch output can be suppressed using --no-patch, but other diff formats (namely --raw, --numstat, --shortstat, --dirstat, --summary, --name-only, --name-status, --check) are not currently implemented.

一般可以通过:

1
2
3
git log -L :funcname:file 
# 或者
git log -L start,end:file

的形式查找,但是很有可能查不到,所以可以直接换成正则表示式的形式:

1
git log -L '/匹配函数名的正则表示式/':file

比如:

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
43
44
45
46
47
48
49
$ git log -L '/test1/':B.java
commit 5e5996c6423c49855f481af7a81294d33e96cfbf (HEAD -> master)
Author: slimterry <slimterry@qq.com>
Date: Mon Apr 1 10:39:09 2024 +0800

修改b.java 6

diff --git a/B.java b/B.java
--- a/B.java
+++ b/B.java
@@ -6,5 +6,6 @@
public void test1(){
// do something 1
+ // do something 2
}

}

commit cce2144ca890e26d313d18a23f1e1015191ba2c6
Author: slimterry <slimterry@qq.com>
Date: Mon Apr 1 10:38:44 2024 +0800

修改b.java 5


86182@yawen MINGW64 /d/coding/git-playground (master)
$ git log -L '/test1\(\)/':B.java
commit 5e5996c6423c49855f481af7a81294d33e96cfbf (HEAD -> master)
Author: slimterry <slimterry@qq.com>
Date: Mon Apr 1 10:39:09 2024 +0800

修改b.java 6

diff --git a/B.java b/B.java
--- a/B.java
+++ b/B.java
@@ -6,5 +6,6 @@
public void test1(){
// do something 1
+ // do something 2
}

}

commit cce2144ca890e26d313d18a23f1e1015191ba2c6
Author: slimterry <slimterry@qq.com>
Date: Mon Apr 1 10:38:44 2024 +0800

修改b.java 5

你也可以提供单行或者一个范围的行号来获得相同的输出。