Git是一个强大的工具,用于管理和跟踪代码的更改,并广泛用于软件开发。无论您是刚开始使用Git还是想提高自己的技能,本指南都将带您了解使用Git的基础知识,包括创建存储库、进行提交、分支、合并等。通过清晰的解释和逐步说明,您将学习如何充分利用Git,帮助您更有效地工作并与他人协作完成编码项目。
若对本文中的部分术语不甚了解,请阅读 GIT 文档 学习
GIT OBJECT
Git 对象是构成 Git 仓库的底层数据结构。它们是 Git 的构建模块,用于存储和管理不同版本的文件。有三种类型的 Git 对象:Blobs、Trees、Commits。Blobs 代表文件的内容,而 Trees 代表仓库的目录结构。Commits是仓库在某一特定时间点的快照。理解这些对象对于有效地使用 Git 是至关重要的,它将使你能够最大限度地利用这个强大的版本控制体系。
BLOB
在 git 中,文件的内容存储在一些被称为 blob (二进制大对象)的对象中。blob 与文件的不同在于,文件还会包含元数据(meta-data)。例如一个文件会“记住”它的创建时间,如果你把它移动到另一个目录,它的创建时间是不会改变的。
相反,blob 只是内容——数据的二进制流。除了内容以外,blob 不会记录它的创建时间、名字或任何其它东西。git 中的 blob 通过 SHA-1 哈希值 唯一标识。SHA-1 哈希值由 20 个字节(byte)组成,通常表示成 40 个十六进制形式的字符。在这篇文章中,我们有时只会展示这个哈希值的前几个字符。
TREE
在 git 中,树对象(tree) 相当于目录。一个 树对象 基本上就是一个目录列表,它引用着 blob 和其它的 树对象。
树对象 也用 SHA-1 哈希值唯一标识,它通过其它对象(blob 或 树对象)的 SHA-1 哈希值引用它们。
注意 CAFE7 这个 树对象 指向了 blob F92A0,在另一个 树对象 中,同一个 blob 可能会有不同的名字。
上面这张图相当于一个文件系统,这个文件系统有一个根目录,根目录下有一个位于 /test.js 的文件和一个名为 /docs 的目录,/docs 目录下有两个文件:/docs/pic.png 和 /docs/1.txt。
现在是时候捕获该文件系统的一个快照了,把那个时刻存在的所有文件连同它们的内容保存下来。
COMMIT
在 git 中,一个快照就是一个 提交(commit) 。一个 提交 对象包括一个指向主要 树对象(根目录)的指针和一些像 提交者、提交信息 和 提交时间 这样的元数据。
在大多数情况下,一个 提交 还会有一个或多个父 提交——之前的快照。当然,提交 对象也通过它们的 SHA-1 哈希值唯一标识。这些哈希值就是我们使用 git log 命令时看到的那些哈希值。
每个 提交 都持有 完整的快照,并不只是与之前 提交 之前的差异。那么它是怎么工作的呢?难道它不代表我们每次提交都必须保存很多数据吗?
让我们来看看改变一个文件的内容会发生什么。我们编辑 1.txt,加一个感叹号——也就是把文件的内容由 HELLO WORLD 变为 HELLO WORLD!。
这个改变意味着我们会有一个新的 blob,它有新的 SHA-1 哈希值。这是有意义的,因为 sha1("HELLO WORLD") 与 sha1("HELLO WORLD!") 并不相同。
由于我们得到了一个新的哈希值,所以对应 树对象 的目录也会改变。毕竟,我们的 树对象 不再指向 blob 73D8A 了,而是指向了 blob 62E7A。当我们改变 树对象 的内容时,我们也改变了它的哈希值。
现在,由于原来那个 树对象 的哈希值已经不同了,我们也需要改变它的 父树对象——后者不再指向 tree CAFE7了,而是指向了 tree 246001。最终,父树对象 也会有一个新的哈希值。
几乎做好创建一个新 提交 对象的准备了,我们好像会再一次保存很多的数据——整个文件系统。但是真的有必要这么做吗?
实际上,一些对象(尤其是 blob 对象)相比起之前的提交来说没有任何改变——blob F92A0仍然原封不动,blob F00D1 也一样。
这就是其中的秘诀——只有对象改变了,我们才再次保存它。在这个例子中,我们不需要再次保存 blob F92A0 和 blob F00b1。我们只需要通过它们的哈希值引用它们,然后我们可以创建 提交 对象。
由于这次 提交 不是第一次 提交,所以它有一个父节点——commit A1337。
回顾一下,我们介绍了三种 git 对象
让我们思考一下这些对象的哈希值吧。如果我写了 git is awesome! 并从它创建了一个 blob。你也在自己的系统上这么做,我们会有相同的哈希值吗?
答案是肯定的。因为这两个 blob 有相同的内容,自然也会有相同的 SHA-1 哈希值。
如果我创建了一个引用 git is awesome! 这个 blob 的 树对象 ,赋给它一个特定的名字和元数据,你也在自己的系统上重复我的操作。我们会有相同的哈希值吗?
答案还是肯定的。因为这两个 树对象 是相同的,它们会有同样的哈希值。
如果我创建了一个指向那个 树对象 的 提交对象,提交信息为 Hello,你也在自己的系统上重复了一遍这个操作,结果会怎样呢?我们的哈希值还会相同吗?
这个时候的答案是否定的。即使我们的 提交对象 指向了相同的 树对象,它们也会有不同的 提交详情——时间、提交者,等等。
BRANCH
分支(branch)只不过是提交对象的命名引用。
我们可以一直用 SHA-1 哈希值引用一个 提交,但是人们通常喜欢以其他形式命名对象。分支 恰好是引用 提交 的一种方式,实际上也只是这样。
在大多数仓库中,主线开发都是在一个叫做 master 的分支上完成的。master 只是一个名字,它是在我们使用 git init 命令的时候被创建的。正因为如此,它被广泛使用。然而,它并不特别,我们可以用任何我们喜欢的名字代替它。
通常,分支指向的是当前开发线上的最近一次 提交。
我们通常使用 git branch 命令创建一个新分支,而我们实际创建的却是另一个指针(pointer)。假设我们使用 git branch test 命令创建了一个名为 test 的分支,我们实际上是创建了另一个指针,它指向当前分支上的同一 提交。
git 是怎么知道我们当前所在的分支呢?答案是它维护了一个名为 HEAD 的特殊指针。通常情况下,HEAD 会指向一个分支,这个分支指向一个 提交。有时候,HEAD 也能直接指向一个 提交,不过这不是我们的重点。
译者注:**活动分支(active branch)**指的是我们当前所在的分支,也就是 HEAD 指向的分支。
要将活动分支切换到 test,我们可以使用命令 git checkout test。现在我们已经能猜到这条命令真正做的事情了——它只不过是把 HEAD 指向的分支改成了 test。
在创建 test 分支之前,我们也可以使用 git checkout -b test,这条命令等价于先运行 git branch test 创建分支,再运行 git checkout test 使 HEAD 指向新的分支。
如果我们做了一些改动并使用 git commit 创建了一个新 提交 呢?这个新 提交 会被添加到哪个分支上呢?
答案是 test 分支,因为它是当前的活动分支(因为 HEAD 指向了它)。之后,test 指针会移动至新添加的 提交 上。注意 HEAD 仍然指向 test。
因此,如果我们使用 git checkout master 回到 master 分支,我们就让 HEAD 的再次指向 master 了。
如果我们现在创建一个新的 提交,它就会被添加到 master 分支,commit B2424 会成为新提交的父节点。
CHANGE
通常,我们在 工作目录(working dir) 中编写源代码。工作目录 (或 工作树(working tree) )可以是文件系统上的任何一个目录,它关联着一个 仓库(repository) 。目录内不仅包含工程的文件夹和文件,还包含一个名为 .git 的目录。稍后我们会再讨论 git 这个目录。
在做了一些改动之后,我们想把这些改动记录到我们的 仓库 中。一个 仓库 (缩写:repo)就是一系列 提交 的集合,每个 提交 都是工程 工作树 的归档。除了我们自己机器上的提交外,仓库也会包含他人机器上的提交。
仓库 也包含除代码文件以外的其它东西,例如 HEAD 指针、分支等等。
你可能使用过的其它和 git 类似工具,但是 git 并不会像其它工具那样直接将变化从 工作树 提交到 仓库。相反,它会先把这些变化注册到一个被称为 索引(index) 或 暂存区(staging area) 的地方。
这两个术语指的都是同一个东西,它们也经常被 git 的文档使用,我们将会在这篇文章中交替使用它们。
当我们 checkout 到一个分支时,git 会将上一次检出到工作目录中的所有文件填充到 索引,它们看起来就像最初被检出时的样子。之后执行 git commit 时, 提交 会在当前 索引 的基础上创建。
索引 允许我们精心准备每次 提交。举个例子,自上一次 提交 以来,我们的 工作目录 中可能有两个文件发生了变化,但是我们可能只想将其中的一个添加到 索引(使用 git add),然后使用 git commit 记录这一个文件的变化。
工作目录 下文件的状态不外乎有两种:已跟踪(tracked) 或 未跟踪(untracked) 。
已跟踪文件 是指那些 git 已经知道的文件。它们要么已经在上一次快照(提交)中,要么已经被 暂存(staged) (换句话说,它们已经在 暂存区 中)。
工作目录 中除已跟踪文件以外的所有其它文件都属于 未跟踪文件(untracked) ,它们既没有在上次快照(提交)中,也没有在 暂存区 中。