第十一章 Git 操作技巧与诀窍
本章相关主题:
- 活用
git stash
(上) ✔️ - 保存并应用
stash
(上) ✔️ - 用
git bisect
进行调试 - 使用
git blame
命令 - 设置彩色命令行界面
- 自动补全
- 让 Bash 自带状态信息
- 更多别名
- 交互式新增提交
- 用 Git 的图形界面交互式新增
- 忽略文件
- 展示与清理忽略文件
简介
本章将重点梳理 Git 的使用技巧,以便用于日常工作。例如在执行重要任务时被临时打断需要寄存工作区变更内容,或者使用 bisect
与 blame
命令高效定位 bug
,抑或是设置命令行的颜色与状态信息等。此外还将进一步梳理实用别名、根据指定的行级变更创建更清爽的 commit
提交、以及忽略文件的相关操作。
11.1 活用 git stash
本节演示如何使用 git stash
命令来快速寄存未提交的更改并再次读取这些内容。该操作在某项重要任务被临时打断(如紧急修复线上 bug
)时非常实用,可以快速保存本地更改,恢复当前工作区;待临时任务结束后再还原之前的工作状态。利用 git stash
命令,还可以在寄存本地更改时设置是否保存暂存区(staging area
)中的内容。
相关命令:
git stash
:添加到寄存区(入栈)git stash list
:查看寄存区的存放列表git stash pop
:取回寄存区的内容(出栈)
实操示例如下:
# repo init
$ git clone https://github.com/PacktPublishing/Git-Version-Control-Cookbook-Second-Edition_tips_and_tricks.git tiptrick
$ cd tiptrick
$ git checkout master
# do some changes
$ echo "Just another unfinished line" >> foo
$ git add foo
$ echo "Another line" >> bar
$ echo "Some content" > new_file
$ git status -s
可见,git stash
只对暂存区及加入版本管理的文件生效(new_file
未被寄存)。
# using git stash
$ git stash
Saved working directory and index state WIP on master: b6dabd7 Update foo and bar
$ git status -s
?? new_file
查看 gitk
:
$ gitk master stash
结果如下:
图 11-1 放入寄存区后的分支状态
对 foo
文件作如下修改:
# on Linux (BSD sed)
$ sed -i 's/First line/This is the very first line of the foo file/' foo
$ git add foo
$ git commit -m "Update foo"
[master 3646ef9] Update foo
1 file changed, 1 insertion(+), 1 deletion(-)
# check stashed contents
$ git stash list
stash@{0}: WIP on master: b6dabd7 Update foo and bar
实测发现,Ubuntu
环境下 sed
命令为 sed -i 's/.../.../' foo
。
此时再查看分支状态:
$ gitk --reflog
结果如下:
图 11-2 完成临时任务并提交后的分支状态
完成临时任务后,取回 stash
命令寄存的内容如下:
$ git status -s
?? new_file
$ git stash pop
Auto-merging foo
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
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: bar
modified: foo
Untracked files:
(use "git add <file>..." to include in what will be committed)
new_file
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (c008d2f149d0827cbc06879b3097bd690128e260)
值得注意的是,虽然原来暂存区的变更也一并存入了寄存区,但还原时 git
默认只把工作区内的变更还原了。
原理分析
上述演示中,foo
文件的一处修改先放到了寄存区;然后在处理临时任务时,对该文件的另一处内容作了修改并提交;之后再将寄存的变更重新还原到 foo
上,由于没有版本冲突,Git
作了自动合并。
根据 图 11-1 可知,加入寄存区后,Git
分别为索引区(index
)及工作区(work area
)创建了 commit
节点;这在临时任务提交后也可以佐证,如 图 11-2 所示。
stash
创建的 commit
对象存放在 refs/stash
命名空间下。其中,包含原暂存区信息的 commit
节点,称为 index on master
(主分支上的索引信息);另一个包含工作区信息的节点,叫 WIP on master
(主分支上正在进行中的工作信息,WIP
是 W
ork I
n P
rogress 的缩写)。这些临时创建的节点信息,可以通过 git
的常规操作将更改重新还原到工作目录下。也就是说,一旦还原过程中发生版本冲突,也可以通过 git
处理冲突的常规流程进行处理。
知识拓展
通过刚才的演示得知,默认情况下,git
寄存或还原当前工作状态时,存在两个问题:
- 未加入版本控制的文件不会自动
stash
到寄存区; - 还原只对工作区生效,尽管寄存了原暂存区的变更;
这两个问题都可以通过手动指定参数来解决。前者使用 git stash --include-untracked
解决;后者通过 git stash pop --index
解决:
$ git clone https://github.com/PacktPublishing/Git-Version-Control-Cookbook-Second-Edition_tips_and_tricks.git tiptrick
$ cd .\tiptrick\
$ echo "Just another unfinished line" >> foo; git add foo; echo "Another line" >> bar;echo "Some content" > new_file;
$ git stash --include-untracked
Saved working directory and index state WIP on master: b6dabd7 Update foo and bar
# Check untracked file (not existed):
$ git status -s
# Check gitk with 'stash' branch
$ gitk stash
查看结果如下:(Git
在 stash
上又新增一个节点来单独存放 不在版本控制内的 内容变更)
继续后面的操作,处理并提交临时任务,只是在还原寄存区的变更时,将原暂存区的变更也还原:
$ sed -i 's/First line/This is the very first line of the foo file/' foo
$ git add foo; git commit -m 'Update foo';
$ gitk --reflog
再次查看还原前的分支状态:
这时,需要将原暂存区的变更内容一并还原到之前的状态:
$ git stash pop --index
$ git status -s
M bar
M foo
?? new_file
$
注意对比之前没加 --index
的输出内容。再次查看 gitk --reflog
予以验证,可以看到还原后的状态中,暂存区不同了:
此外,还可以在放入寄存区时,手动指定是否需要保留暂存区的变更:在执行 git stash
时通过标记 --keep-index
实现(也可以加 --include-untracked
包含不受版本控制的变更):
$ git clone https://github.com/PacktPublishing/Git-Version-Control-Cookbook-Second-Edition_tips_and_tricks.git tiptrick
$ cd .\tiptrick\
$ echo "Just another unfinished line" >> foo; git add foo; echo "Another line" >> bar;echo "Some content" > new_file
# keep changes in staging area
# while stashing away untracked changes, too:
$ git stash --keep-index --include-untracked
Saved working directory and index state WIP on master: b6dabd7 Update foo and bar
# Check result (as expected)
$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: foo
11.2 保存并应用 stash
寄存当前工作状态时,寄存区可能会有出现寄存状态,而默认的名称并不能区分要恢复的版本具体是哪个。本节将对这种情况进行处理,演示怎样给寄存区的存放记录命名,并恢复指定的版本:
# init repo
$ git clone https://github.com/PacktPublishing/Git-Version-Control-Cookbook-Second-Edition_tips_and_tricks.git tiptrick
$ cd .\tiptrick\
$ echo "Just another unfinished line" >> foo; git add foo; echo "Another line" >> bar;echo "Some content" > new_file
$ git stash --keep-index --include-untracked
Saved working directory and index state WIP on master: b6dabd7 Update foo and bar
$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: foo
$ git stash list
stash@{0}: WIP on master: b6dabd7 Update foo and bar
# create stash and name the stash item
$ git stash save 'Updates to foo'
Saved working directory and index state On master: Updates to foo
$ git stash list
stash@{0}: On master: Updates to foo
stash@{1}: WIP on master: b6dabd7 Update foo and bar
# Create a new stash by changing bar:
$ echo "Another change" >> bar
$ git stash save 'Made another change to bar'
Saved working directory and index state On master: Made another change to bar
# check stash list
$ git stash list
stash@{0}: On master: Made another change to bar
stash@{1}: On master: Updates to foo
stash@{2}: WIP on master: b6dabd7 Update foo and bar
解决了寄存记录的命名问题,就可以在应用寄存信息的同时保留该记录:
# apply stash info
$ git stash apply 'stash@{1}'
# use --quiet to suppresses the status output
$ git stash apply --quiet 'stash@{0}'
$ git stash list
stash@{0}: On master: Made another change to bar
stash@{1}: On master: Updates to foo
stash@{2}: WIP on master: b6dabd7 Update foo and bar
可以看到,寄存列表在成功还原后仍在存在。应用某个寄存区的记录,语法为:git stash apply stash@{stash-no}
。
发散
寄存列表如何删除呢?执行命令 git stash drop stash@{stash-no}
即可:
$ git stash list
stash@{0}: On master: Made another change to bar
stash@{1}: On master: Updates to foo
stash@{2}: WIP on master: b6dabd7 Update foo and bar
$ git stash drop 'stash@{1}'
Dropped stash@{1} (ef0c0e7de00803e61f7a2a02df581bedb143778b)
$ git stash list
stash@{0}: On master: Made another change to bar
stash@{1}: WIP on master: b6dabd7 Update foo and bar
对比之前的默认版本,手动应用或删除寄存区版本提供了更多的灵活性,可以避免一个操作失误。默认情况下,成功 stash pop
一个版本后,该版本会从寄存区自动删除;但若还原过程受阻(如发生冲突)则会被保留下来,即便用户处理了这个冲突,寄存区的版本仍不会自动删除。这就为后续的版本还原埋下了隐患。而手动控制应用或删除,就可以很好地解决这个问题。
小试牛刀
git stash
命令也能把调式信息应用到项目中。比如在调式代码过程中,为了定位 bug
而在项目中加入了不少调式用的语句,调试结束后不必手动删除所有的调试语句。相反,可以将这些语句通过 git stash save "Debug info stash"
寄存到 git
中并命名。这样在今后再次调试时,可以应用该寄存版快速初始化上一次的调试环境。