译者注:本文翻译自 Sam Livingston-Gray 的文章Think Like (a) Git。我在学习 Git 的过程中无意看到了这篇文章,Sam 从图论的角度解释了 Git,让我明白了很多之前疑惑的地方。所以,我将这篇文章翻译成中文,希望能帮助更多学习 Git 的人。

图论(Graph Theory)

Königsberg 的七桥理论(Seven Bridges of Königsberg)

Places To Go, and Ways to Get There

Nodes and Edges

Attaching Labels to Nodes

Attaching Labels to Edges

Directed Versus Undirected Graphs

Reachability

图和 Git(Graphs and Git)

我讨论了这么多关于图论内容的原因是 Git 就是一个巨大的图。

graphs-and-git.png

Git commits(简单的视角)

大部分时候,你使用 Git 都是通过这样那样的方式进行提交(commits),从表面上看,一次 Git 提交包括两部分:(1)在某个时刻,一个指向你的代码状态的指针(pointer);(2)0 个或者多个指向父级提交的指针。

(提示:指针的含义和讨论图的时候一样)

一次 Git 的 commit 就是图里的一个节点,节点可以指向其他更早的节点。

另外:如果你希望比一个理智的人类知道的更多,我非常推荐 Scott Chacon 的PDF。或者是任何 Scott Chacon 的讲座。Scott 关于 Git 的知识实在是……惊人。

可视化你的 Git 仓库(Visualizing Your Git Repository)

我的大约一半的 Git 操作都是在命令行下进行的。但是如果我想知道正在发生什么,我会使用 Git 的可视化工具。在 Mac 上,我能找到的最好的可视化工具是GitX。其他操作系统上,可以使用gitk,或者你可以在网上找到适合你的工具。

可视化工具可以帮你 了解分支的历史 ,比如说,你可以输入命令git log --oneline--abbrev-commit --branches=* 来列出仓库中所有的 commits 的扁平化视图。

(感谢 @cflipse 指出 --pretty=online 可以缩写为--online

textual-git-log.png

或者,你可以在上面的命令里加上--graph,会得到一个稍微更有用的视图。

1
git log --oneline --abbrev-commit --branches=* --graph
textual-git-graph.png

好多了!

(感谢 @mjdominus@JRGarcia提醒我可以使用--graph

如果你想看到分支和标签,可以加上--decorate

1
git log --oneline --abbrev-commit --branches=* --graph --decorate
textual-git-graph-with-labels.png

(再次感谢 @JRGarcia提供的 --decorate 技巧)

你也可以添加 --color 来显示地更优美一点。

1
git log --oneline --abbrev-commit --branches=* --graph --decorate --color

实际上,我有一个 shell 的命令别名可以做到所有的这些

1
alias gg='git log --oneline --abbrev-commit --branches=* --graph --decorate --color'

或者,你可以使用 GitX(译者注:一种 git 的 GUI 软件)看到上面所有命令的结果,并且可以看到更加清晰的图形化展示。

gitx-visualization.png

引用(References)

你可以已经注意到了上一页中 GitX 的截图里有多重颜色的标签,这些标签是 GitX 用来展示引用的。

我不想花太多的时间讨论每一种颜色是什么意思,因为特定的颜色是 GitX 特有的,你可能使用的是另一种软件。但是无论你用的可视化工具如何展示这些标签,你都应该知道他们表示的含义是什么。

引用是指向一次 commit 的指针。

引用有几种:local branch、remote branch 和 tag。

在硬盘中,一个 local branch 的引用是完全由你的工程中 .git/refs/heads 目录中的一个文件组成的。这个文件包含对应 commit 的 40 字节的标识符,整个文件就是 40 个字节。

你可能听说过 Git 的分支是很轻便的,这就是这句话的部分来历。在 Git 中创建分支仅仅意味着在硬盘上写入 40 个字节,这就是为什么 git branch foo 是如此的快。

而真正有趣的是引用的行为,所以,让我们继续。

引用参考(The Reference Reference)

正如前面所说,引用有几种不同的种类,他们都指向你仓库中的 commits。这些引用唯一的区别在于他们是如何还有何时移动的。(当我说一个引用移动的时间,我是指它指向的 commit 的 ID 改变了。)

Local branch 引用 是针对一个仓库的:你本地的仓库。影响 local branch 引用的命令包括 commit, merge, rebase 和 reset。

Remote branch 引用 也是针对一个仓库的,但是那个先前被指定为远程的仓库。影响 remote branch 引用的命令包括 fetch 和 push。

Tag 引用 基本上可以 branch 引用一样,但是它们不会移动。一旦你创建一个 tag,它就不再改变了 (除非你明确地使用--force 命令选项更新它)。这种行为使 tag 可以用来比标记特定版本的软件包,或者标记一个特定的日期生产服务器中部署了什么。据我所知,只有一个命令影响 tag,那就是 tag。

Making Sense of the Display

这是另一张有一些注解的 GitX 的截图。

gitx-visualization-annotated.png

如果你想要一个仓库样本随便玩玩,可以clone 这个)。

关于这张图最重要的不是你看到的,而是你 没有 看到的。我看过的所有 Git 工具都有一个共同点:他们隐藏了 commits

听起来像个阴谋论,对吧?其实并不是这样。

垃圾回收(Garbage Collection)

想象这样一个场景,你写了一些代码并且 check in 了,然后你发现忘记运行测试用例了,所以你运行了测试用例,并且发现了一个语法错误。或者你认出一个拼写错误。无论因为什么原因,当你任务你做完的时候你实际并没有做完。(我经常干这种事)

回到我使用 Subvesion 的的时间,唯一能做的是在另个一个 commit 中增加新的修改。通常,在提交历史中我会有三到四个版本。第一个版本是”add feature X”,而下一个版本就是 “oops, found typo”或者”bugfix”或者”forgot to run tests”。

Git 给了你另一种选择。你可以使用 git commit —amend 命令将新的修改放到之前 commit 中。这样可以将所有相关的修改都绑定到一次 commit 上,当你以后回顾的时候也可以更快的弄明白。

另一个关于 Git commit 的事实是:一个 commit 的 ID 是一个由以下几种信息组成的 SHA-1hash 值:commit 的内容和 parent commit 的 ID。

也就是说,当你使用 git commit —amend 的时候,你实际上创建了一个完全不同的 commit,并将 local branch 引用指向了这个 commit。第一个 commit 还在硬盘上,你仍然可以找回他 (稍后介绍)。但是,为了避免混乱的展示效果,git log 和 Git 可视化工具都展示之前的 commit。

最终,Git 会决定何时启动 garbage collection#Reachability_of_an_object)。(你也可以使用git gc 自己触发这个流程)。从每一个 branch 和 tag 开始,Git 沿着相反的方向走过整张图,得到它所能到达的所有 commit 的列表。一旦它到达每一条路径的重点,就会把所有没有访问到的 commit 删除。

试试 Git(Experimenting With Git)

我在前一页说了一些非常重要的东西,这种东西是如此重要以致于我要重复一遍。

在描述 Git 的垃圾回收算法时,我说“从每一个 branch 和 tag 开始,Git 沿着相反的方向走过整张图,得到它所能到达的所有 commit 的列表。”

到此为止我写的所有的东西都是为了帮助你 理解一个特定的事情 而提供的背景知识。如果我只有十秒钟来告诉你 Git 的秘密,也是整篇文章的精髓,我把它整合成四个词:

References Make Commits Reachable

REFERENCES MAKE COMMITS REACHABLE.

让我们分解来看。

REFERENCES…

…无论他们是 local branch 引用,remote branch 引用,还是 tag 引用…

…MAKE COMMITS…

…也就是图中的节点…

…REACHABLE.

…所以你可以沿着相反的方向到达它们,而且 Git 在做垃圾回收的时候不会删除他们。

将这句话凑在一起花了我很长的时间,我写这篇文章的目的就是让你不必浪费我花费过的时间。

我拙劣的初学(My Humble Beginnings)

将分支当做存档点(Branches as Savepoints)

Use Your Targeting Computer, Luke

测试一下合并(Testing Out Merges)

The Scout Pattern

The Savepoint Pattern

Black Belt Merging

Rebase(Rebase From the Ground Up)

Cherry-Picking Explained

Using ‘git cherry-pick’ to Simulate ‘git rebase’

A Helpful Mnemonic for ‘git rebase’ Arguments

The End