2016 Git 新视界
时间:2017-04-19 12:46 来源:linux.it.net.cn 作者:IT
2016 年 Git 发生了 惊天动地 地变化,发布了五大新特性¹ (从 v2.7 到 v2.11 )和十六个补丁²。189 位作者³贡献了 3676 个提交⁴到 master 分支,比 2015 年多了 15%⁵!总计有 1545 个文件被修改,其中增加了 276799 行并移除了 100973 行⁶。
但是,通过统计提交的数量和代码行数来衡量生产力是一种十分愚蠢的方法。除了深度研究过的开发者可以做到凭直觉来判断代码质量的地步,我们普通人来作仲裁难免会因我们常人的判断有失偏颇。
谨记这一条于心,我决定整理这一年里六个我最喜爱的 Git 特性涵盖的改进,来做一次分类回顾。这篇文章作为一篇中篇推文有点太过长了,所以我不介意你们直接跳到你们特别感兴趣的特性去。
-
完成 git worktree 命令
-
更多方便的 git rebase 选项
-
git lfs 梦幻的性能加速
-
git diff 实验性的算法和更好的默认结果
-
git submodules 差强人意
-
git stash 的90 个增强
在我们开始之前,请注意在大多数操作系统上都自带有 Git 的旧版本,所以你需要检查你是否在使用最新并且最棒的版本。如果在终端运行 git --version 返回的结果小于 Git v2.11.0,请立刻跳转到 Atlassian 的快速指南 更新或安装 Git 并根据你的平台做出选择。
[所需的“引用”]
在我们进入高质量内容之前还需要做一个短暂的停顿:我觉得我需要为你展示我是如何从公开文档生成统计信息(以及开篇的封面图片)的。你也可以使用下面的命令来对你自己的仓库做一个快速的 年度回顾!
¹ 2016 年匹配 vX.Y.0 格式的里程碑
-
$ git for-each-ref --sort=-taggerdate --format \
-
'%(refname) %(taggerdate)' refs/tags | grep "v\d\.\d*\.0 .* 2016"
² 2016 年匹配 vX.Y.Z 格式的里程碑
-
$ git for-each-ref --sort=-taggerdate --format '%(refname) %(taggerdate)' refs/tags | grep "v\d\.\d*\.[^0] .* 2016"
³ 2016 年做过提交的贡献者数量
-
$ git shortlog -s -n --since=2016-01-01 --until=2017-01-01
⁴ 2016 年的提交数量
-
$ git log --oneline --since=2016-01-01 --until=2017-01-01 | wc -l
⁵ 以及 2015 年的提交数量
-
$ git log --oneline --since=2015-01-01 --until=2016-01-01 | wc -l
⁶ 2016 年添加、删除行数
-
$ git diff --shortstat `git rev-list -1 --until=2016-01-01 master` \
-
`git rev-list -1 --until=2017-01-01 master`
以上的命令是在 Git 的 master 分支运行的,所以不会显示其他出色的分支上没有合并的工作。如果你使用这些命令,请记住提交的数量和代码行数不是应该值得信赖的度量方式。请不要使用它们来衡量你的团队成员的贡献。
现在,让我们开始说好的回顾……
完成 Git 工作树
git worktree 命令首次出现于 Git v2.5 ,但是在 2016 年有了一些显著的增强。两个有价值的新特性在 v2.7 被引入:list 子命令,和为二分搜索增加了命令空间的 refs。而 lock/unlock 子命令则是在 v2.10 被引入。
什么是工作树呢?
git worktree 命令允许你同步地检出和操作处于不同路径下的同一仓库的多个分支。例如,假如你需要做一次快速的修复工作但又不想扰乱你当前的工作区,你可以使用以下命令在一个新路径下检出一个新分支:
-
$ git worktree add -b hotfix/BB-1234 ../hotfix/BB-1234
-
Preparing ../hotfix/BB-1234 (identifier BB-1234)
-
HEAD is now at 886e0ba Merged in bedwards/BB-13430-api-merge-pr (pull request #7822)
工作树不仅仅是为分支工作。你可以检出多个里程碑作为不同的工作树来并行构建或测试它们。例如,我从 Git v2.6 和 v2.7 的里程碑中创建工作树来检验不同版本 Git 的行为特征。
-
$ git worktree add ../git-v2.6.0 v2.6.0
-
Preparing ../git-v2.6.0 (identifier git-v2.6.0)
-
HEAD is now at be08dee Git 2.6
-
-
$ git worktree add ../git-v2.7.0 v2.7.0
-
Preparing ../git-v2.7.0 (identifier git-v2.7.0)
-
HEAD is now at 7548842 Git 2.7
-
-
$ git worktree list
-
/Users/kannonboy/src/git 7548842 [master]
-
/Users/kannonboy/src/git-v2.6.0 be08dee (detached HEAD)
-
/Users/kannonboy/src/git-v2.7.0 7548842 (detached HEAD)
-
-
$ cd ../git-v2.7.0 && make
你也使用同样的技术来并行构造和运行你自己应用程序的不同版本。
列出工作树
git worktree list 子命令(于 Git v2.7 引入)显示所有与当前仓库有关的工作树。
-
$ git worktree list
-
/Users/kannonboy/src/bitbucket/bitbucket 37732bd [master]
-
/Users/kannonboy/src/bitbucket/staging d5924bc [staging]
-
/Users/kannonboy/src/bitbucket/hotfix-1234 37732bd [hotfix/1234]
二分查找工作树
git bisect 是一个简洁的 Git 命令,可以让我们对提交记录执行一次二分搜索。通常用来找到哪一次提交引入了一个指定的退化。例如,如果在我的 master 分支最后的提交上有一个测试没有通过,我可以使用 git bisect 来贯穿仓库的历史来找寻第一次造成这个错误的提交。
-
$ git bisect start
-
-
# 找到已知通过测试的最后提交
-
# (例如最新的发布里程碑)
-
$ git bisect good v2.0.0
-
-
# 找到已知的出问题的提交
-
# (例如在 `master` 上的提示)
-
$ git bisect bad master
-
-
# 告诉 git bisect 要运行的脚本/命令;
-
# git bisect 会在 “good” 和 “bad”范围内
-
# 找到导致脚本以非 0 状态退出的最旧的提交
-
$ git bisect run npm test
在后台,bisect 使用 refs 来跟踪 “good” 与 “bad” 的提交来作为二分搜索范围的上下界限。不幸的是,对工作树的粉丝来说,这些 refs 都存储在寻常的 .git/refs/bisect 命名空间,意味着 git bisect 操作如果运行在不同的工作树下可能会互相干扰。
到了 v2.7 版本,bisect 的 refs 移到了 .git/worktrees/$worktree_name/refs/bisect, 所以你可以并行运行 bisect 操作于多个工作树中。
锁定工作树
当你完成了一颗工作树的工作,你可以直接删除它,然后通过运行 git worktree prune 等它被当做垃圾自动回收。但是,如果你在网络共享或者可移除媒介上存储了一颗工作树,如果工作树目录在删除期间不可访问,工作树会被完全清除——不管你喜不喜欢!Git v2.10 引入了 git worktree lock 和 unlock 子命令来防止这种情况发生。
-
# 在我的 USB 盘上锁定 git-v2.7 工作树
-
$ git worktree lock /Volumes/Flash_Gordon/git-v2.7 --reason \
-
"In case I remove my removable media"
-
# 当我完成时,解锁(并删除)该工作树
-
$ git worktree unlock /Volumes/Flash_Gordon/git-v2.7
-
$ rm -rf /Volumes/Flash_Gordon/git-v2.7
-
$ git worktree prune
--reason 标签允许为未来的你留一个记号,描述为什么当初工作树被锁定。git worktree unlock 和 lock都要求你指定工作树的路径。或者,你可以 cd 到工作树目录然后运行 git worktree lock . 来达到同样的效果。
更多 Git 变基选项
2016 年三月,Git v2.8 增加了在拉取过程中交互进行变基的命令 git pull --rebase=interactive 。对应地,六月份 Git v2.9 发布了通过 git rebase -x 命令对执行变基操作而不需要进入交互模式的支持。
Re-啥?
在我们继续深入前,我假设读者中有些并不是很熟悉或者没有完全习惯变基命令或者交互式变基。从概念上说,它很简单,但是与很多 Git 的强大特性一样,变基散发着听起来很复杂的专业术语的气息。所以,在我们深入前,先来快速的复习一下什么是变基。
变基操作意味着将一个或多个提交在一个指定分支上重写。git rebase 命令是被深度重载了,但是 rebase 这个名字的来源事实上还是它经常被用来改变一个分支的基准提交(你基于此提交创建了这个分支)。
从概念上说,rebase 通过将你的分支上的提交临时存储为一系列补丁包,接着将这些补丁包按顺序依次打在目标提交之上。

对 master 分支的一个功能分支执行变基操作 (git reabse master)是一种通过将 master 分支上最新的改变合并到功能分支的“保鲜法”。对于长期存在的功能分支,规律的变基操作能够最大程度的减少开发过程中出现冲突的可能性和严重性。
有些团队会选择在合并他们的改动到 master 前立即执行变基操作以实现一次快速合并 (git merge --ff <feature>)。对 master 分支快速合并你的提交是通过简单的将 master ref 指向你的重写分支的顶点而不需要创建一个合并提交。

变基是如此方便和功能强大以致于它已经被嵌入其他常见的 Git 命令中,例如拉取操作 git pull 。如果你在本地 master 分支有未推送的提交,运行 git pull 命令从 origin 拉取你队友的改动会造成不必要的合并提交。

这有点混乱,而且在繁忙的团队,你会获得成堆的不必要的合并提交。git pull --rebase 将你本地的提交在你队友的提交上执行变基而不产生一个合并提交。

这很整洁吧!甚至更酷,Git v2.8 引入了一个新特性,允许你在拉取时 交互地 变基。
交互式变基
交互式变基是变基操作的一种更强大的形态。和标准变基操作相似,它可以重写提交,但它也可以向你提供一个机会让你能够交互式地修改这些将被重新运用在新基准上的提交。
当你运行 git rebase --interactive (或 git pull --rebase=interactive)时,你会在你的文本编辑器中得到一个可供选择的提交列表视图。
-
$ git rebase master --interactive
-
-
pick 2fde787 ACE-1294: replaced miniamalCommit with string in test
-
pick ed93626 ACE-1294: removed pull request service from test
-
pick b02eb9a ACE-1294: moved fromHash, toHash and diffType to batch
-
pick e68f710 ACE-1294: added testing data to batch email file
-
-
# Rebase f32fa9d..0ddde5f onto f32fa9d (4 commands)
-
#
-
# Commands:
-
# p, pick = use commit
-
# r, reword = use commit, but edit the commit message
-
# e, edit = use commit, but stop for amending
-
# s, squash = use commit, but meld into previous commit
-
# f, fixup = like "squash", but discard this commit's log message
-
# x, exec = run command (the rest of the line) using shell
-
# d, drop = remove commit
-
#
-
# These lines can be re-ordered; they are executed from top to
-
# bottom.
-
#
-
# If you remove a line here THAT COMMIT WILL BE LOST.
注意到每一条提交旁都有一个 pick。这是对 rebase 而言,“照原样留下这个提交”。如果你现在就退出文本编辑器,它会执行一次如上文所述的普通变基操作。但是,如果你将 pick 改为 edit 或者其他 rebase 命令中的一个,变基操作会允许你在它被重新运用前改变它。有效的变基命令有如下几种:
-
reword:编辑提交信息。
-
edit:编辑提交了的文件。
-
squash:将提交与之前的提交(同在文件中)合并,并将提交信息拼接。
-
fixup:将本提交与上一条提交合并,并且逐字使用上一条提交的提交信息(这很方便,如果你为一个很小的改动创建了第二个提交,而它本身就应该属于上一条提交,例如,你忘记暂存了一个文件)。
-
exec: 运行一条任意的 shell 命令(我们将会在下一节看到本例一次简洁的使用场景)。
-
drop: 这将丢弃这条提交。
你也可以在文件内重新整理提交,这样会改变它们被重新应用的顺序。当你对不同的主题创建了交错的提交时这会很顺手,你可以使用 squash 或者 fixup 来将其合并成符合逻辑的原子提交。
当你设置完命令并且保存这个文件后,Git 将递归每一条提交,在每个 reword 和 edit 命令处为你暂停来执行你设计好的改变,并且自动运行 squash, fixup,exec 和 drop 命令。
非交互性式执行
当你执行变基操作时,本质上你是在通过将你每一条新提交应用于指定基址的头部来重写历史。git pull --rebase 可能会有一点危险,因为根据上游分支改动的事实,你的新建历史可能会由于特定的提交遭遇测试失败甚至编译问题。如果这些改动引起了合并冲突,变基过程将会暂停并且允许你来解决它们。但是,整洁的合并改动仍然有可能打断编译或测试过程,留下破败的提交弄乱你的提交历史。
但是,你可以指导 Git 为每一个重写的提交来运行你的项目测试套件。在 Git v2.9 之前,你可以通过绑定 git rebase --interactive 和 exec 命令来实现。例如这样:
-
$ git rebase master −−interactive −−exec=”npm test”
……这会生成一个交互式变基计划,在重写每条提交后执行 npm test ,保证你的测试仍然会通过:
-
pick 2fde787 ACE-1294: replaced miniamalCommit with string in test
-
exec npm test
-
pick ed93626 ACE-1294: removed pull request service from test
-
exec npm test
-
pick b02eb9a ACE-1294: moved fromHash, toHash and diffType to batch
-
exec npm test
-
pick e68f710 ACE-1294: added testing data to batch email file
-
exec npm test
-
-
# Rebase f32fa9d..0ddde5f onto f32fa9d (4 command(s))
如果出现了测试失败的情况,变基会暂停并让你修复这些测试(并且将你的修改应用于相应提交):
-
291 passing
-
1 failing
-
-
1) Host request "after all" hook:
-
Uncaught Error: connect ECONNRESET 127.0.0.1:3001
-
…
-
npm ERR! Test failed.
-
Execution failed: npm test
-
You can fix the problem, and then run
-
git rebase −−continue
这很方便,但是使用交互式变基有一点臃肿。到了 Git v2.9,你可以这样来实现非交互式变基:
-
$ git rebase master -x "npm test"
可以简单替换 npm test 为 make,rake,mvn clean install,或者任何你用来构建或测试你的项目的命令。
小小警告
就像电影里一样,重写历史可是一个危险的行当。任何提交被重写为变基操作的一部分都将改变它的 SHA-1 ID,这意味着 Git 会把它当作一个全新的提交对待。如果重写的历史和原来的历史混杂,你将获得重复的提交,而这可能在你的团队中引起不少的疑惑。
为了避免这个问题,你仅仅需要遵照一条简单的规则:
永远不要变基一条你已经推送的提交!
坚持这一点你会没事的。
(责任编辑:IT)
2016 年 Git 发生了 惊天动地 地变化,发布了五大新特性¹ (从 v2.7 到 v2.11 )和十六个补丁²。189 位作者³贡献了 3676 个提交⁴到 master 分支,比 2015 年多了 15%⁵!总计有 1545 个文件被修改,其中增加了 276799 行并移除了 100973 行⁶。 但是,通过统计提交的数量和代码行数来衡量生产力是一种十分愚蠢的方法。除了深度研究过的开发者可以做到凭直觉来判断代码质量的地步,我们普通人来作仲裁难免会因我们常人的判断有失偏颇。 谨记这一条于心,我决定整理这一年里六个我最喜爱的 Git 特性涵盖的改进,来做一次分类回顾。这篇文章作为一篇中篇推文有点太过长了,所以我不介意你们直接跳到你们特别感兴趣的特性去。
在我们开始之前,请注意在大多数操作系统上都自带有 Git 的旧版本,所以你需要检查你是否在使用最新并且最棒的版本。如果在终端运行 git --version 返回的结果小于 Git v2.11.0,请立刻跳转到 Atlassian 的快速指南 更新或安装 Git 并根据你的平台做出选择。 [所需的“引用”]在我们进入高质量内容之前还需要做一个短暂的停顿:我觉得我需要为你展示我是如何从公开文档生成统计信息(以及开篇的封面图片)的。你也可以使用下面的命令来对你自己的仓库做一个快速的 年度回顾!
以上的命令是在 Git 的 master 分支运行的,所以不会显示其他出色的分支上没有合并的工作。如果你使用这些命令,请记住提交的数量和代码行数不是应该值得信赖的度量方式。请不要使用它们来衡量你的团队成员的贡献。 现在,让我们开始说好的回顾…… 完成 Git 工作树git worktree 命令首次出现于 Git v2.5 ,但是在 2016 年有了一些显著的增强。两个有价值的新特性在 v2.7 被引入:list 子命令,和为二分搜索增加了命令空间的 refs。而 lock/unlock 子命令则是在 v2.10 被引入。 什么是工作树呢?git worktree 命令允许你同步地检出和操作处于不同路径下的同一仓库的多个分支。例如,假如你需要做一次快速的修复工作但又不想扰乱你当前的工作区,你可以使用以下命令在一个新路径下检出一个新分支:
工作树不仅仅是为分支工作。你可以检出多个里程碑作为不同的工作树来并行构建或测试它们。例如,我从 Git v2.6 和 v2.7 的里程碑中创建工作树来检验不同版本 Git 的行为特征。
你也使用同样的技术来并行构造和运行你自己应用程序的不同版本。 列出工作树git worktree list 子命令(于 Git v2.7 引入)显示所有与当前仓库有关的工作树。
二分查找工作树git bisect 是一个简洁的 Git 命令,可以让我们对提交记录执行一次二分搜索。通常用来找到哪一次提交引入了一个指定的退化。例如,如果在我的 master 分支最后的提交上有一个测试没有通过,我可以使用 git bisect 来贯穿仓库的历史来找寻第一次造成这个错误的提交。
在后台,bisect 使用 refs 来跟踪 “good” 与 “bad” 的提交来作为二分搜索范围的上下界限。不幸的是,对工作树的粉丝来说,这些 refs 都存储在寻常的 .git/refs/bisect 命名空间,意味着 git bisect 操作如果运行在不同的工作树下可能会互相干扰。 到了 v2.7 版本,bisect 的 refs 移到了 .git/worktrees/$worktree_name/refs/bisect, 所以你可以并行运行 bisect 操作于多个工作树中。 锁定工作树当你完成了一颗工作树的工作,你可以直接删除它,然后通过运行 git worktree prune 等它被当做垃圾自动回收。但是,如果你在网络共享或者可移除媒介上存储了一颗工作树,如果工作树目录在删除期间不可访问,工作树会被完全清除——不管你喜不喜欢!Git v2.10 引入了 git worktree lock 和 unlock 子命令来防止这种情况发生。
--reason 标签允许为未来的你留一个记号,描述为什么当初工作树被锁定。git worktree unlock 和 lock都要求你指定工作树的路径。或者,你可以 cd 到工作树目录然后运行 git worktree lock . 来达到同样的效果。 更多 Git 变基选项2016 年三月,Git v2.8 增加了在拉取过程中交互进行变基的命令 git pull --rebase=interactive 。对应地,六月份 Git v2.9 发布了通过 git rebase -x 命令对执行变基操作而不需要进入交互模式的支持。 Re-啥?在我们继续深入前,我假设读者中有些并不是很熟悉或者没有完全习惯变基命令或者交互式变基。从概念上说,它很简单,但是与很多 Git 的强大特性一样,变基散发着听起来很复杂的专业术语的气息。所以,在我们深入前,先来快速的复习一下什么是变基。 变基操作意味着将一个或多个提交在一个指定分支上重写。git rebase 命令是被深度重载了,但是 rebase 这个名字的来源事实上还是它经常被用来改变一个分支的基准提交(你基于此提交创建了这个分支)。 从概念上说,rebase 通过将你的分支上的提交临时存储为一系列补丁包,接着将这些补丁包按顺序依次打在目标提交之上。
对 master 分支的一个功能分支执行变基操作 (git reabse master)是一种通过将 master 分支上最新的改变合并到功能分支的“保鲜法”。对于长期存在的功能分支,规律的变基操作能够最大程度的减少开发过程中出现冲突的可能性和严重性。 有些团队会选择在合并他们的改动到 master 前立即执行变基操作以实现一次快速合并 (git merge --ff <feature>)。对 master 分支快速合并你的提交是通过简单的将 master ref 指向你的重写分支的顶点而不需要创建一个合并提交。
变基是如此方便和功能强大以致于它已经被嵌入其他常见的 Git 命令中,例如拉取操作 git pull 。如果你在本地 master 分支有未推送的提交,运行 git pull 命令从 origin 拉取你队友的改动会造成不必要的合并提交。
这有点混乱,而且在繁忙的团队,你会获得成堆的不必要的合并提交。git pull --rebase 将你本地的提交在你队友的提交上执行变基而不产生一个合并提交。
这很整洁吧!甚至更酷,Git v2.8 引入了一个新特性,允许你在拉取时 交互地 变基。 交互式变基交互式变基是变基操作的一种更强大的形态。和标准变基操作相似,它可以重写提交,但它也可以向你提供一个机会让你能够交互式地修改这些将被重新运用在新基准上的提交。 当你运行 git rebase --interactive (或 git pull --rebase=interactive)时,你会在你的文本编辑器中得到一个可供选择的提交列表视图。
注意到每一条提交旁都有一个 pick。这是对 rebase 而言,“照原样留下这个提交”。如果你现在就退出文本编辑器,它会执行一次如上文所述的普通变基操作。但是,如果你将 pick 改为 edit 或者其他 rebase 命令中的一个,变基操作会允许你在它被重新运用前改变它。有效的变基命令有如下几种:
你也可以在文件内重新整理提交,这样会改变它们被重新应用的顺序。当你对不同的主题创建了交错的提交时这会很顺手,你可以使用 squash 或者 fixup 来将其合并成符合逻辑的原子提交。 当你设置完命令并且保存这个文件后,Git 将递归每一条提交,在每个 reword 和 edit 命令处为你暂停来执行你设计好的改变,并且自动运行 squash, fixup,exec 和 drop 命令。 非交互性式执行当你执行变基操作时,本质上你是在通过将你每一条新提交应用于指定基址的头部来重写历史。git pull --rebase 可能会有一点危险,因为根据上游分支改动的事实,你的新建历史可能会由于特定的提交遭遇测试失败甚至编译问题。如果这些改动引起了合并冲突,变基过程将会暂停并且允许你来解决它们。但是,整洁的合并改动仍然有可能打断编译或测试过程,留下破败的提交弄乱你的提交历史。 但是,你可以指导 Git 为每一个重写的提交来运行你的项目测试套件。在 Git v2.9 之前,你可以通过绑定 git rebase --interactive 和 exec 命令来实现。例如这样:
……这会生成一个交互式变基计划,在重写每条提交后执行 npm test ,保证你的测试仍然会通过:
如果出现了测试失败的情况,变基会暂停并让你修复这些测试(并且将你的修改应用于相应提交):
这很方便,但是使用交互式变基有一点臃肿。到了 Git v2.9,你可以这样来实现非交互式变基:
可以简单替换 npm test 为 make,rake,mvn clean install,或者任何你用来构建或测试你的项目的命令。 小小警告就像电影里一样,重写历史可是一个危险的行当。任何提交被重写为变基操作的一部分都将改变它的 SHA-1 ID,这意味着 Git 会把它当作一个全新的提交对待。如果重写的历史和原来的历史混杂,你将获得重复的提交,而这可能在你的团队中引起不少的疑惑。 为了避免这个问题,你仅仅需要遵照一条简单的规则:
坚持这一点你会没事的。 |