Git 已成为 DevOps 时代存储和传输代码的默认方式。超过 93% 的开发者报告称 Git 是他们的主要版本控制系统。几乎所有使用过版本控制的人都熟悉 git add
、git commit
和 git push
。对于大多数用户来说,这就是他们计划用 Git 做的全部,他们对此感到满意。它只需满足他们的需求即可。
然而,几乎每个人都会时不时地遇到需要做一些更高级操作的情况,例如 git rebase
或 git cherry-pick
或在分离头指针状态下工作。这就是许多开发者开始感到有点紧张的地方。
[ 接下来阅读 如何在 Git 中重置、还原和返回到之前的状态 ]
我在这里告诉你,这没关系!每个已经或将要使用 Git 的人都可能会经历同样的恐慌。
Git 非常棒,但学习起来也让人望而生畏,有时会让人感到非常困惑。Git 与计算机科学中的几乎任何其他事物都不同。你通常是零零散散地学习它,特别是在其他编码工作的上下文中。我遇到的大多数开发者都没有正式学习过 Git,可能只是看过一个 快速教程。
Git 是开源的,这意味着你可以自由地检查代码并了解它的工作原理。它主要用 C 语言编写,对于许多开发者和学习计算机科学的人来说,这可能使其难以理解。与此同时,文档中使用了诸如 massage parameters 和 commit-ish 之类的术语。这可能会让人感到有点困惑。你可能会觉得 Git 是为高级 Linux 专业人士编写的。因为它最初就是如此。
Git 简史
Git 最初是 Linus Torvalds 用来管理补丁的特定脚本集。
以下是他如何向 Linux 内核邮件列表介绍后来成为 Git 的内容
所以我正在编写一些脚本,试图更快地跟踪事情。初步迹象表明,我应该能够像应用补丁一样快地完成它,但坦率地说,我最多只完成了一半,如果我遇到障碍,也许情况就并非如此了。无论如何,我可以快速完成它的原因是我的脚本_不会_是一个 SCM,它们将是一个非常具体的“记录 Linus 的状态”之类的东西。这将使线性补丁合并更加省时高效,因此成为可能。
当我对于 Git 的工作原理感到困惑时,我做的第一件事之一就是想象 Linus 为什么以及如何将其应用于管理补丁。它已经发展到可以处理更多的事情,并且确实是一个完整的 SCM,但记住最初的用例有时有助于理解“为什么”。
Git commit
Git 中核心的概念工作单元是 commit。这些是项目文件夹(.git
文件夹所在的位置)中正在跟踪的文件的快照。

(Git-scm.com,CC BY-NC-SA 3.0)
重要的是要记住,Git 存储的是文件系统的压缩快照,而不是差异。每当你更改文件时,都会创建一个该文件的全新压缩版本并存储在该 commit 中。它通过从文件中创建一个超压缩的二进制大对象 (blob),然后通过生成使用 SHA 哈希算法创建的校验和来跟踪它。你的 Git 历史记录的持久性是永远不要在你的 Git 项目中存储或硬编码敏感数据的原因之一。任何可以 克隆仓库 的人都可以完全访问所有版本的文件。
Git 真的非常高效。如果文件在 commit 之间没有更改,Git 不会创建一个全新的压缩版本进行存储。相反,它只是指回之前的 commit。所有 commit 都知道哪个 commit 直接在其之前,称为其父 commit。当你运行 git log
时,你可以轻松地看到这个 commit 链。

(Git-scm.com,CC BY-NC-SA 3.0)
你可以完全控制这些 commit 链,并且可以使用它们做一些非常酷的事情。你可以根据需要创建、删除、合并和重新排序它们。你甚至可以有效地穿越时间,探索甚至编写你的 commit 历史记录。但这都依赖于理解 Git 如何看待 commit 链,这些链通常被称为分支。
Git 分支
分支 允许你在一个项目中处理多个 commit 链。处理多个分支(特别是当你使用 rebase
时)是许多用户开始感到紧张的地方。大多数人对分支甚至是什么的常见心智模型增加了困惑。
在考虑分支时,大多数人会联想到泳道、分叉和相交点的图像。虽然这些模型在理解特定的分支策略和工作流程时可能很有帮助,但在尝试思考 Git 如何工作时,将 Git 视为图表上的一系列编号点可能会混淆视听。
我发现一个有用的替代心智模型是将分支视为存在于一个大的电子表格中。第一列是父 commit ID,第二列是新的 commit ID,然后是元数据列,包括所有指针。

(Dwayne McDaniel,CC BY-SA 4.0)
指针跟踪你所在的位置以及哪个分支是哪个分支。指针是方便的人类可读的对特定 commit 的引用。任何指向特定 commit 的引用都称为 commit-ish。
用于命名分支的特殊指针始终指向链上最新的 commit。你可以使用 git tag
将指针任意分配给任何 commit,它不会移动。当你在分支之间 git checkout
或 git switch
时,你实际上是在告诉 Git 你想要更改 Git 的参考点,并在每个 .git
文件夹中移动一个非常特殊的指针,称为 HEAD。
.git 文件夹
理解 Git 正在发生什么的最佳方法之一是深入研究 .git
文件夹。如果你以前从未打开过此文件,我强烈建议你打开它并查看其中的内容。如果你非常担心会破坏某些东西,可以克隆一个任意的开源项目来尝试,直到你感觉有信心查看你自己的项目仓库。

(Dwayne McDaniel,CC BY-SA 4.0)
你首先注意到的事情之一是文件有多小。
事物以字节或千字节为单位衡量,最大也是如此。Git 非常高效!
HEAD
在 .git
文件夹中,你会找到特殊文件 HEAD。这是一个非常小的文件,只有几个字节大小。如果你打开它,你会看到它只有一行长。
git > HEAD
ref:refs/heads/main
在阅读关于 Git 的文章时,你经常会遇到的一个短语是“一切都是本地的”。从 Git 的角度来看,HEAD 指向的任何地方都是“这里”。HEAD 是 Git 与其他分支、其他引用和自身其他副本交互的参考点。
在此示例中,ref:
指向另一个指针,即分支名称指针。沿着该路径,你可以找到一个看起来很像之前电子表格的文件。Git 只是从文件中获取最新的 commit ID,并知道那是 HEAD 引用的 commit。
如果 HEAD 指向没有附加其他指针的特定 commit,则 HEAD 被称为“分离的”。在分离的 HEAD 状态下工作是完全安全的,但会限制你可以执行的操作,例如从那里创建新的 commit。要退出分离的 HEAD 状态,只需检出另一个指针,例如分支名称,例如 git checkout main
。
Config
另一个帮助 Git 跟踪事物的重要文件是 .git/config
文件。这只是 Git 读取和存储配置的位置之一。你可能已经熟悉 Git 配置的 --global
级别,它存储在你主目录的 .gitconfig
文件中。实际上 Git 加载配置有五个位置,每个位置都会覆盖之前的配置。Git 加载配置的顺序是
-
--system
这会加载特定于你的操作系统的配置 -
--global
影响你作为用户,user.name
和user.email
存储在此处 -
--local
这会设置仓库特定的信息,例如远程仓库和 hooksPath -
--worktree
worktree 是压缩到单个 commit 中的内容 -
--blob
单个压缩文件可以有自己的设置
你可以通过运行 git config --list --show-origin
来查看仓库的所有配置
你可以利用你的本地配置来使用多个 Git 角色。在 .gitconfig
中覆盖 user.name
和 user.email
。当你在工作项目、个人仓库和任何开源贡献之间分配时间时,利用本地配置特别有用。
Git 钩子
Git 有一个内置的强大自动化平台,称为 Git 钩子。Git 钩子允许你执行在 Git 中发生某些事件时运行的脚本。你可以用你喜欢的任何脚本语言编写脚本,只要你的环境可用即可。有 17 个可用的钩子。
如果你查看任何仓库的 .git/hooks
文件夹,你会看到一系列 .sample
文件。这些是预先编写的示例,旨在帮助你入门。其中一些包含一些看起来很奇怪的代码。或许很奇怪,直到你记住这些主要是为了服务于 Linux 内核工作的原始用例而添加的,并且是由生活在 Perl 和 Bash 脚本海洋中的人们编写的。你可以让脚本做任何你想做的事情。
这是一个我在个人仓库中使用的钩子的示例
#!/sur/bin/env bash
curl https://icanhazdadjoke.com
echo “”
在此示例中,每次我运行 git commit
时,但在 commit 消息提交到我的 Git 历史记录之前,Git 都会执行该脚本。(感谢 Edward Thomson 的 git-dad 提供的灵感。)
当然,你也可以做一些实际的事情,例如在 进行 commit 之前检查硬编码的密钥。要了解更多关于 Git 钩子的信息并找到许多示例脚本,请查看 Matthew Hudson 的精彩网站 GitHooks.com。
高级 Git
现在你更好地理解了 Git 如何看待世界以及幕后工作原理,并且你已经了解了如何使用脚本使其听从你的命令。在我的下一篇文章中,我将介绍 Git 中的一些高级工具和命令。
1 条评论