Dwayne Mcdaniel 发布的文章

学习如何使用 Git 来压扁、变基和精选。

当我与别人谈到 Git 时,几乎每个人都对 git rebase 命令 有强烈的印象,这个命令让许多人遇到了问题,而不得不更改目录、删除仓库、然后再重新克隆一个仓库。我认为这是因为他们误解了分支是如何工作,遇到了一个非常糟糕的默认界面,还有一些合并冲突把事情搞得一团糟。

怎么找不到 git squash 命令?

如果你曾在本地的仓库提交过很多次,并希望能把这些提交都合并为一个提交,接下来,我们就来介绍能用什么 Git 命令达到这个目的。Git 称这个概念为 “ 压扁提交 squash commits ”。我在编写文档时发现了这个概念:我花了十几个提交才修改好我的 Markdown 文档,但是仓库的维护者不想看到我的所有尝试,以免扰乱了该项目的历史,所以我被告知“需要压扁你的提交”。

压扁提交听起来是一个很有用的方法。但是只有一个问题:我不知道该怎么做。作为 Git 的新手,我做了任何人会做的事情:我去查阅 git-squash 的手册,但我立即遇到了阻碍:

$ man git-squash
> No manual entry for git-squash

我发现没有一个名为 squash 的 Git 命令,而是被要求 运行一个完全独立的命令:git rebase 命令,该命令能将我的所有提交最终合并为一个提交。

我知道我碰到一个常见的情形:已经使用工具一段时间的人使用了行话或引用了一个概念,这个概念对他们来说是非常清楚的,但对新手来说就不能明白了。从概念上讲,这个情况看起来是这样的:

Image of 6 bowls of different colored spices, and an arrow pointing to the second image of all the spices blended into one bowl.

我这样说是为了鼓励你,你绝对不是第一个或最后一个 被 Git 或谈论 Git 的人 弄糊涂的人。你可以要求对方说明白他的意见,并帮助你应该使用的正确命令。仓库的维护者实际上的意思是,“使用 git rebase 命令**,将很多提交压扁成一个提交”。

现在就来学习 git rebase 命令吧

git rebase 命令会将一个提交链从其第一个父级中删除,并将其放置在另一个提交链的末尾,将两个提交链组合成一个长链,而不是两个并行链。我意识到这是一个很复杂的定义。

回想一下 Git 的提交是如何链接在一起的,你可以看到,除了初始的 main(或 master)分支外,任何分支都有一个 父提交 parent commit 作为该链的 “ 基础 base ”。“ 变基 rebase ” 能使另一个链中的最后一个提交成为指定分支的新 “ 基础提交 base commit ”。

在 Git 中整合来自不同分支的修改主要有两种方法: 合并 merge 以及 变基 rebase ,你可能更熟悉 git merge 命令。接下来,就来看看 [git-scm.com] 是如何解释 git mergegit rebase 的差异:

Image of Git merge versus git rebase shown as numbered bubbles.

在合并示例中,它会把两个分支的最新快照(C3C4)以及二者最近的共同祖先(C2)进行三方合并,合并的结果是生成一个新的快照(C5)。experiment 的分支指针仍然存在,仍然指向 C4

在变基示例中,它提取在 C4 中引入的补丁和修改,然后在 C3 的基础上应用一次,使 C3 成为 C4 的新父级,并产生了一个名为 C4' 的新提交。

(LCTT 译注:具体的命令如下:

$ git checkout experiment
$ git rebase main
First, rewinding head to replay your work on top of it...
Applying: added staged command

它的原理是首先找到这两个分支 —— 即当前分支 experiment、变基操作的目标基底分支 main —— 的最近共同祖先 C2,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件,然后将当前分支指向目标基底 C3,最后以此将之前另存为临时文件的修改依序应用。)

值得注意的是,分支指针 main 没有移动。要让 Git 将指针移动到链的末尾(由experiment 指向),你还需要执行合并。

(LCTT 译注:具体的命令如下:

$ git checkout main
$ git merge experiment

master 分支的快进合并

此时,C4' 指向的快照就和上面使用 merge 命令的例子中 C5 指向的快照一模一样了。)

git rebase 并不能替代 git mergegit rebase 是一种用于制作更清晰的历史记录,以与 git merge 结合使用的工具。

(LCTT 译注:使用 git rebase 命令将提交到某一分支上的所有修改都移至另一分支上,就好像“重新播放”一样。)

交互式变基能给你一个更友好的界面!

从命令行执行 git rebase 命令,最可怕的地方在于它糟糕的默认界面。运行命令 git rebase <target-refr> 要么有效,要么会变得一团糟,因为它没有太多的反馈或方法来确保它做你想做的事情。幸运的是,git rebase 命令和许多其他 Git 命令一样,具有 交互模式 interactive mode ,你可以使用参数 -i 或者 -interactive 来使用交互模式。

Image of the Git lens interactive Rebase tool in VS Code.

在使用交互式模式时,git rebase 会从一个糟糕的黑框界面转换为一个选项菜单,允许你选择对正在变基的提交链所做的事。对于每个提交,你可以选择

  • 选用 pick :按原样包含
  • 重写 reword :重写提交消息
  • 编写 edit :在变基完成之前对提交中的文件进行进一步更改
  • 压扁 squash :将多个提交压缩成一个提交,保留所有提交消息
  • 修理 fixup :将多个提交压缩成一个提交,但只保留最后一个提交消息
  • 丢弃 drop :丢弃此提交

就我个人而言,我更喜欢 VS Code 的开源 GitLens 扩展 使用下拉选择列表布局选项的方式,但 Git 允许你使用任何编辑器选择这些选项。对于 Emacs 或 Vim 等纯文本工具,你需要键入选择,而不是从菜单中选择,但最终结果仍然是相同的。

何时做变基

知道 何时 做变基与知道 如何 做变基同样重要。事实上,如果你不在乎你的仓库历史提交消息有点混乱的话,那么你可以永远都不使用 git rebase 命令。但是,如果你想要更干净的历史提交消息,并且想要更少扰乱你的图形视图的提交,那么当你使用 git rebase 命令时,有一个重要的经验法则需要时刻记住:

“不要变基你存储库以外的的提交,那些提交可能是别人工作的基础。”

如果你遵循该准则,不会发生什么大问题的。

简而言之,如果你让一个本地分支来完成你的工作,变基是没有问题的。但一旦该分支被 推送 push 了,就不要再变基该分支了。当然,你想要怎么做完全取决于你自己。

希望你会认为上述内容有助于你理解 git rebase 命令的工作原理,并能让你更有信心地使用它。与任何 Git 命令一样,练习是学习和理解怎么做的唯一方法。我鼓励你勇敢地尝试 交互式变基 interactive rebase git rebase -i <branch name>

接下来学习 Git cherry-pick 命令吧

大多数开发人员将修改提交到某一分支上,但是之后发现他们一直提交到了错误的分支上。理想情况下,他们可以拿走那个提交,然后把它移到正确的分支,这正是 git cherry-pick 命令的作用。

git cherry-pick 命令利用了变基单个提交的方法。这一用法非常常见,以至于有了它自己的命令。

Image of a woman picking a cherry from one tree and putting on another tree.

要使用 git cherry-pick,你只需告诉 Git 你要移动到“那个分支”的提交 ID(由 HEAD 指向):

$ git cherry-pick <target-ref>

如果出现问题,你可以根据 Git 提供的错误消息,来进行恢复:

$ git cherry-pick -i 2bc01cd
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
error: could not apply 2bc01cd… added EOF lines
hint: After resolving the conflicts, mark them with
hint: "git add/rm ", then run
hint: "git cherry-pick --continue".
hint: You can instead skip this commit with "git cherry-pick --skip".
hint: To abort and get back to the state before "git cherry-pick",
hint: run "git cherry-pick --abort".
$ git cherry-pick --abort

让 Git 更强大

git rebase 命令是 Git 实用程序强大的地方之一。你最好在测试仓库中先练习一下怎么使用,一旦你熟悉了它的概念和工作流程,你就可以给仓库一个清晰历史消息记录了。


via: https://opensource.com/article/22/11/advanced-git-commands

作者:Dwayne McDaniel 选题:lkxed 译者:chai001125 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Git 的 bisect 工具通过快速识别坏的提交,节省了时间和精力。

你是不是有过这样的经历:发现代码中有 错误 bug ,但不知道这个错误是什么时候引入的。这有可能是因为,某个人提交了一份有错误的代码,但没有在他的 Git 提交 commit 消息中声明它。这个错误可能已经存在了几周、几个月甚至几年,这意味着你需要搜索数百或数千个提交,才能找到问题何时出现的。而 git bisect 命令能够完美地解决这个问题!

git bisect 命令是一个强大的工具。你可以给 git bisect 命令一个范围,一端是一个已知的好状态,另一端是一个已知的坏状态。它会自动地确认当前范围的中点,在这个中点上进行测试,然后要求你确定那次提交是一个 好提交 good commit 还是一个 坏提交 bad commit ,然后它会重复这一“二分查找”的过程,直到你找到首次引入错误的那一次提交。

Image of Zeno's paradox of Achilles.

这个“数学”工具是利用“二分查找”来找到错误之处的。git bisect 命令通过查看中点,然后由你来决定它是提交列表的新起点(即 “坏提交” )还是新终点(即 “好提交”),进而来缩小查找范围,如此在几次查找中你可以就能定位到有错误的提交。即使你有 10,000 个提交要检查,最多只需要 13 次查找,就能很快地定位到首次引入错误的提交。

  1. 提交 1 坏 <> 提交 10,000 好 => 提交 5,000 是坏的
  2. 提交 5,000 坏 <> 提交 10,000 好 => 提交 7,500 是好的
  3. 提交 5,000 坏 <> 提交 7,500 好 => 提交 6,250 是好的
  4. 提交 5,000 坏 <> 提交 6,250 好 => 提交 5,625 是坏的
  5. 提交 5,625 坏 <> 提交 6,250 好 => 提交 5,938 是坏的
  6. 提交 5,938 坏 <> 提交 6,250 好 => 提交 6,094 是好的
  7. 提交 5,938 坏 <> 提交 6,094 好 => 提交 6,016 是坏的
  8. 提交 6,016 坏 <> 提交 6,094 好 => 提交 6,055 是好的
  9. 提交 6,016 坏 <> 提交 6,055 好 => 提交 6,036 是坏的
  10. 提交 6,036 坏 <> 提交 6,055 好 => 提交 6,046 是坏的
  11. 提交 6,046 坏 <> 提交 6,055 好 => 提交 6,050 是坏的
  12. 提交 6,050 坏 <> 提交 6,055 好 => 提交 6,053 是好的
  13. 提交 6,053 坏 <> 提交 6,055 好 => 提交 6,054 是好的

对于上面这个例子,我们能知道 10,000 个提交中的第一个错误提交是第 6053 次提交。对于 git bisect 命令,最多需要几分钟就能完成检索。但是如果要一个一个查找每个提交是否错误,我甚至无法想象需要多长时间。

使用 Git bisect 命令

git bisect 命令使用起来非常简单:

(LCTT 译注:使用 git bisect start 命令来进入 bisect 模式,并且该命令指定了一个检查范围。它会告诉我们一共有多少次提交,大概需要几步就可以定位到具体的提交。)

$ git bisect start
$ git bisect bad        # Git assumes you mean HEAD by default
$ git bisect good <ref> # specify a tag or commit ID for <ref>

Git 检查中间的提交,并等待你声明这次提交是一个好提交还是一个坏提交:

(LCTT 译注:如果某一提交是可以通过的,则使用 git bisect good 命令标记;同样地,如果某一提交不能通过,则使用 git bisect bad 命令标记。)

$ git bisect good

$ git bisect bad

然后,git bisect 工具重复检查好提交和坏提交中间的那次提交,直到你告诉它:

$ git bisect reset

一些高级用户甚至可以自己编写脚本,来确定提交的好坏状态、并在找到特定提交时采取某一补救措施。你可能不会每天都使用 git bisect 命令,但当你需要它来定位首次引入错误的提交时,它会是一个很有用的救星。


via: https://opensource.com/article/22/11/git-bisect

作者:Dwayne McDaniel 选题:lkxed 译者:chai001125 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出