Skip to content
This page has been auto-translated and may contain errors.View in English

什么是 Git? ​

docs.scrimba.com

你几乎肯定手动做过版本控制。一个文件夹里放着 report.docx、report_final.docx、report_final_v2.docx、report_final_ACTUAL.docx,这就是版本控制,而且是最糟糕的那种。你没法看出这几个文件之间到底改了什么,没法把两个人同时做的修改合并起来,也绝对没法找回你三个版本之前删掉的那段写得很好的文字。

Git 就是来彻底解决这个问题的。它是一个能随时间记录项目快照的工具,让你的整段历史都保存在一个地方,而不是散落成一堆副本。这个工具本身是安装在你自己电脑上的一个小程序:你在终端里输入以 git 开头的命令,它会用文字回应你。

Git 能为你做什么 ​

从核心上说,Git 会为你的项目维护一条时间线。每当你走到一个值得保存的节点,你就记录一次**commit(提交)**:它是每个文件的一份快照,外加一句简短的说明,描述改了什么、为什么改。之后你可以回顾这条时间线,清楚地看到项目是如何一步步走到今天这个样子的。下面是 git log 打印出的一段真实历史记录,这个命令用来列出项目的所有提交(--oneline 参数会把每条记录压缩成一行):

bash
$ git log --oneline
a1b2c3d 添加密码强度提示
9f8e7d6 修复注册表单验证
5c4b3a2 搭建项目结构

这就是项目历史中的三个保存点,最新的排在最上面,每条记录都有一个简短的 ID 和作者写下的说明。(在所有这类示例中,以 $ 开头的那一行是你在终端里输入的命令,不包含 $ 本身,下面几行则是 Git 的回复。)在接下来的几章里,你会学到每一个能生成、能查看这段历史的命令。眼下你只需要记住一点:Git 把你的工作记录成了一系列你随时可以回溯的快照。

一旦项目有了这样的历史记录,很多事情就都变得可能了。你可以看到改了什么、是谁改的。你可以在旁支上试一个有风险的想法,如果不成功就干净地丢掉它。你可以把整个项目连同完整的历史一起交给别人。而当两个人修改同一段代码时,Git 能把他们的工作合并起来,并标出双方有冲突的地方,交给人来解决。这四种能力——记录历史、安全试验、共享、合并工作——正是几乎所有专业代码库都离不开 Git 的原因。

在实际工作中,团队会一直依赖这条时间线。任何大小的改动都会从一个独立的分支开始,那是一条独立的历史线,你可以在上面工作而不影响主代码。改动准备好之后,会有人来做代码审查,审查通过后再合并回主分支。审查、开分支、合并,这就是用 Git 工作的日常节奏,而它们都建立在前面说的快照时间线之上。本手册接下来的内容会按你实际会遇到的顺序,逐一讲解这套节奏中的每一个环节。

有一个细节对后面所有内容都很重要:每次提交都会保存你所跟踪文件的完整状态,是那一刻的完整快照。很多人以为 Git 只保存版本之间的差异,但实际上每次提交都保存着整个项目。Git 之所以能把这做得很省资源,是因为它对每一份不重复的文件内容只存一次——一次提交如果重用了没有变化的文件,就只会指向那份已经保存过的内容,所以一千个文件里只改了一个的快照,并不会把其余 999 个再复制一遍。每次提交还会记录它前一个提交的 ID,也就是它的父提交,Git 正是靠这个把各个快照串联成一条可以往回追溯的历史。这个父提交的链接是后面几乎所有功能的根基:分支、合并、历史查看,全都建立在跟随这些指针之上。完整的对象模型你会在“历史记录”和“分支”两章里看到。

JunoGit 能为你做什么 Git 把你的项目记录成一系列快照,叫做 commit(提交),每次提交都带一句说明,讲清楚改了什么。有了这段历史之后,你就能回顾它、放心地做实验、分享它,还能和别人一起合并工作成果。那一堆 final_v2_ACTUAL 副本正是 Git 生来要解决的问题,我敢保证,第一次删掉一个这样的文件时,你会觉得特别爽。
JunoGit 能为你做什么 一次提交就是一份快照加一句说明,历史记录就是这些提交连成的时间线。日常工作中,你会从这条时间线上开出一个分支去做改动,让人审查,再合并回去。脑子里记住这个“不断增长的快照时间线”的模型,你学到的每个命令,无非是往上面添加内容,或者从上面读取内容。
JunoGit 能为你做什么 每次提交都保存整个项目的状态,并指向它的父提交,Git 正是靠这个来遍历历史。相同的文件内容只存一份,在各个提交之间共享,所以快照的开销很小。记住这个“父指针”的思路,因为分支和合并说到底就是同一个概念换了个说法。

Git 和 GitHub 是两回事 ​

几乎所有人一开始都会在这里绊一下,所以我们直接说清楚。Git 是那个版本控制工具。它运行在你自己的电脑上,负责记录你的提交,不需要联网、也不需要账号就能用。GitHub 是一个网站,它把 Git 仓库存到网上,并在此基础上加了一堆功能:一个能分享代码、互相审查改动、报告问题、协作开发的地方。

感受这个区别最直接的方式是:你完全可以每天用 Git,在自己电脑上提交、开分支,却从来不需要注册 GitHub 账号。只有当你想把仓库放到一个共享的地方,让别人或者别的机器能访问到它时,GitHub 才会登场。Git 是引擎,GitHub 只是存放它产出的成果的一个地方。类似的地方还有其他,比如 GitLab 和 Bitbucket,它们底层托管的都是同一种 Git 仓库。

这两者会混在一起,是因为大多数人第一次接触 Git 和 GitHub 是在同一个时刻——第一天就从 GitHub 上克隆一个项目。很多人由此以为 git push 的意思是“发送到 GitHub”,其实它真正的意思是“发送到你配置的那个远程仓库”,只是大多数时候那个远程仓库恰好是 GitHub。在脑子里把这两者分开,等你第一次用 Git 配合别的托管平台,或者根本不用任何托管平台时,就不会觉得奇怪了。

JunoGit 和 GitHub 是两回事 Git 是装在你电脑上、负责记录提交的工具。GitHub 是一个把 Git 项目存到网上的网站,方便大家分享和审查代码。你完全可以在没有任何 GitHub 账号的情况下使用 Git。如果这一页你只记住一件事,就记住这一点,因为把两者混为一谈会造成很多初学时的困惑。
JunoGit 和 GitHub 是两回事 Git 是你机器上的版本控制引擎;GitHub 是托管它产出结果的一个常用地方,和 GitLab、Bitbucket 是一类东西。git push 发送到的是你设置的那个远程仓库,出于习惯,大多数人设置的都是 GitHub。把这个区分记在心里,遇到项目放在别处时你就不会不知所措。
JunoGit 和 GitHub 是两回事 Git 只懂提交、分支和引用;GitHub 是在托管的副本上叠加了 pull request、issue 和合并按钮这些功能。GitHub 加的这些东西都不是 Git 命令,这就是为什么你要在网站上发起 pull request,而不是用 git 命令。把两者在脑子里分开,你会发现大部分看起来像是 GitHub“魔法”的东西,其实都还原成了普通的引用。

Git 从何而来 ​

Git 的历史能解释很多它为什么会是这样设计的原因。它是 2005 年由 Linus Torvalds 编写的,他也是 Linux 的创始人。Linux 内核由分布在世界各地的数千名志愿者共同开发,之前几年,他们一直用一款叫 BitKeeper 的商业工具来协调这些工作,那家公司允许开源项目免费使用它。

2005 年,这个免费使用的安排突然告吹,内核社区一下子失去了管理这个庞大且快速变化的代码库的工具。当时市面上没有别的工具能顶上。于是 Torvalds 暂时放下内核的工作,用了大约两周时间,自己写了一个版本控制工具,并针对当时面临的问题定下了几个明确的目标。

他希望这个工具是**分布式的,这样每个贡献者的机器上都保存着完整的项目历史,不用连着中央服务器才能工作。他希望这个工具足够快**,快到能应付内核这种规模的项目,不让人干等着。他还希望这个工具能保证完整性,让任何人都无法悄悄篡改历史,也不会因为一次磁盘错误就悄无声息地损坏项目。

正是这些目标,决定了 Git 今天的行为方式。当你 clone 一个项目时,你得到的是它的整段历史,这正是“分布式”这个目标的体现:你可以在没有 wifi 的飞机上提交、开分支、查看历史。而每个提交都会被打上一个根据其确切内容算出的指纹,一旦哪怕一个字节发生变化,这个指纹就对不上了,Git 就会察觉到。Torvalds 在 2005 年匆忙做出的这些设计决定,正是你今天每次使用 Git 时都在依赖的东西。

完整性这个保证,驱动了整套设计。每个提交都由一个哈希值来标识,这是一个固定长度的指纹,根据提交的全部内容(包括其父提交的 ID)计算而来。因为父提交的 ID 会被计算进子提交的哈希值里,所有的哈希值就连成了一条链:只要改动某个旧提交里的任何内容,它的哈希值就会变,这又会改变它之后每一个提交的哈希值。这就是 Git 历史记录能够“防篡改可察觉”的原理。这也意味着一个提交的 ID 本身就是其确切内容的指纹,这就是为什么你看到的是像 a1b2c3d 这样的十六进制字符串,而不是一个整整齐齐的第 42 号提交。

JunoGit 从何而来 Linus Torvalds 在 2005 年为 Linux 内核编写了 Git,当时项目一直在用的那个工具突然不再免费了。他把 Git 设计成分布式、快速,并且能防止数据损坏。这就是为什么 clone 能让你拿到完整的历史记录在离线状态下工作,也是为什么每次提交都会有一个指纹。了解这段背景,对你以后要用很多年的这个工具会很有帮助。
JunoGit 从何而来 Git 诞生于 2005 年的 Linux 内核项目,为的是应对数千名贡献者、又没有中央服务器的场景,这就是为什么它整套模型都是分布式的。每一次克隆都带着完整的历史,所以你可以在本地随时开分支、提交,等你自己决定好了再同步。每当 Git 的某个默认行为让你觉得奇怪,通常“它是为内核而设计的”就能解释清楚。
JunoGit 从何而来 2005 年,内核项目失去了 BitKeeper 的使用权,Torvalds 用两周时间冲刺开发出了 Git,定下了三个目标:分布式、快速、可校验完整性。每个提交的哈希值都是根据其内容加上父提交的哈希值算出来的,所以这些 ID 连成了一条防篡改的链。当你想不通 Git 为什么会这样设计时,“它是为 Linux 内核而生的”通常就是答案。

这本手册接下来讲什么 ​

有了这套心理模型之后,本系列剩下的内容就是动手实操了。你的第一个仓库会从零开始:打开终端,如果你的电脑还没装 Git 就先安装它,然后把一个文件夹变成一个可以提交的仓库。从那之后,你会学到保存改动的日常流程,接着是分支、合并,以及在 GitHub 上协作的完整流程。如果哪个术语让你摸不着头脑,术语表里对本文档中出现的每一个 Git 词汇都有一句简短的解释。