为什么需要版本控制
想象你正在写一份重要的项目报告。随着内容不断修改,你的桌面逐渐变成这样:
report_v1.doc
report_v2.doc
report_v2_final.doc
report_v2_final_REALLY_FINAL.doc
report_v2_final_boss_review.doc
report_v3_after_meeting.doc
这就是没有版本控制时的典型困境。问题显而易见:
- 你无法清晰地知道每个版本改了什么
- 哪个才是"真正最终"版本?
- 如果多人同时修改同一份文档,如何合并?
- 想回到三天前的某个状态,几乎不可能
- 磁盘空间被大量重复文件浪费
软件开发中这个问题更加严峻。一个现代项目可能有数十万行代码、数百个文件、数十名开发者同时工作——没有版本控制系统(VCS),这一切将是一场灾难。
版本控制系统(VCS)能做什么?记录每一次文件变更、追踪是谁在何时做了什么修改、支持多人并行开发、随时回退到任意历史版本、对比任意两个版本的差异。
版本控制系统的演进
第一代:本地版本控制
最早的解决方案是在本地磁盘上管理版本。RCS(Revision Control System)是这一时代的代表,它通过存储文件的补丁集(patch set)来节省空间,可以重放这些补丁来重建任意历史版本。
问题是显而易见的:数据只在一台机器上,一旦硬盘损坏,所有历史都消失了;多人协作基本不可能实现。
第二代:集中式版本控制(CVCS)
CVS、Subversion(SVN)、Perforce 代表了这一阶段。核心思想是:所有版本历史存储在一台中央服务器上,团队成员从服务器检出(checkout)文件。
│ ────── checkout ──────▶ 开发者 A
│ ────── checkout ──────▶ 开发者 B
└ ────── checkout ──────▶ 开发者 C
这解决了多人协作的问题,但引入了新问题:
- 单点故障:中央服务器宕机,所有人无法工作,甚至历史永久丢失
- 必须联网:没有网络就无法提交、查看历史
- 提交速度慢:每次操作都需要与服务器通信
第三代:分布式版本控制(DVCS)
Git、Mercurial、Bazaar 代表了这一阶段的突破。核心思想是:每个开发者本地都有一份完整的仓库副本(包括全部历史)。
优势显著:
- 离线工作:无需网络也能提交、查看历史、创建分支
- 没有单点故障:任意一个副本都可以恢复整个仓库
- 操作极快:大多数操作在本地完成,无网络延迟
- 灵活的工作流:可以有多个"中心",支持复杂的协作模式
Git 的诞生
2005 年之前,Linux 内核开发团队使用 BitKeeper(一个商业 DVCS)。这一年,由于许可协议纠纷,BitKeeper 撤回了对 Linux 社区的免费授权。
Linux 创始人 Linus Torvalds 决定亲自动手,写一个新的版本控制系统。他的目标明确:
- 速度极快
- 简单的设计
- 对非线性开发(数千个并行分支)的强力支持
- 完全分布式
- 能够高效管理超大型项目(如 Linux 内核)
令人震惊的是,Linus 在 2005 年 4 月用大约两周时间写出了 Git 的第一个版本,并在同年 6 月将 Linux 内核的版本管理迁移到了 Git 上。
Git 这个名字据说来自 Linus 的自嘲——英国俚语中 "git" 意为"令人讨厌的人"。Linus 说:"我用自己的名字命名了第一个项目(Linux),现在用一个骂人的词命名第二个。"
Git 的设计哲学
分布式优先
Git 从设计之初就假设没有"唯一权威"的中心服务器。所谓的远程仓库(如 GitHub)只是另一个 Git 仓库,地位与本地仓库完全平等。
数据完整性(SHA 哈希)
Git 中的每个对象(文件、目录、提交)都通过其内容计算出一个 SHA-1 哈希(40位十六进制字符串,现代 Git 也支持 SHA-256)。这个哈希就是该对象的唯一标识符。
e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 # 这是一个空文件的 SHA-1 哈希
a94a8fe5ccb19ba61c4c0873d391e987982fbbd3 # "test" 的 SHA-1 哈希
只要内容不变,哈希就不会变。任何数据损坏都会导致哈希不匹配,Git 立即能发现。这被称为内容寻址存储(Content-Addressable Storage)。
性能优先
Git 中绝大多数操作(提交、分支切换、查看历史)都在本地完成,不需要网络请求,速度极快。分支的创建只是写一个 41 字节的文件,近乎零成本。
Git 的四个区域(核心概念!)
理解 Git 的四个区域是掌握 Git 的关键。很多初学者混乱的根源就是不清楚文件处于哪个区域。
(Working Dir) (Staging/Index) (Local Repo) (Remote Repo)
远程仓库 ──git pull──▶ 工作区
远程仓库 ──git fetch──▶ 本地仓库(不更新工作区)
- 工作区 Working Directory:你实际看到和编辑的文件所在的目录。就是你的项目文件夹。对文件做任何修改,首先发生在工作区。
-
暂存区
Staging Area / Index:一个准备区域。你使用
git add将工作区的变更"登记"到这里,告诉 Git "我要提交这些变更"。暂存区让你可以精细控制下一次提交包含哪些内容。 -
本地仓库
Local Repository:存储在
.git/目录中的完整版本数据库。git commit将暂存区的快照永久保存到这里,生成一个提交记录(commit)。 -
远程仓库
Remote Repository:托管在网络上的 Git 仓库,如 GitHub、GitLab、Gitee。通过
git push同步本地提交,通过git pull/git fetch获取他人的提交。
初学者常见误区:git add 不是"上传",只是"登记到暂存区"。git commit 不是"保存到 GitHub",只是"保存到本地仓库"。只有 git push 才会与远程交互。
安装 Git
macOS
推荐使用 Homebrew 安装,可获得最新版本:
# 安装 Homebrew(如果没有)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# 安装 Git
brew install git
# 验证安装
git --version
Windows
访问 git-scm.com 下载安装包。安装时推荐选择:
- 默认编辑器:选择你熟悉的编辑器(如 VS Code)
- PATH 环境:选择 "Git from the command line and also from 3rd-party software"
- 行尾符(line endings):Windows 选 "Checkout Windows-style, commit Unix-style"
Linux
# Ubuntu / Debian
sudo apt update && sudo apt install git
# CentOS / RHEL / Fedora
sudo yum install git
# 或
sudo dnf install git
初始化配置
安装 Git 后,第一件事是告诉 Git 你是谁。每次提交都会记录这些信息。
# 设置用户名(显示在提交记录中)
git config --global user.name "张三"
# 设置邮箱
git config --global user.email "zhangsan@example.com"
# 设置默认分支名为 main(现代惯例)
git config --global init.defaultBranch main
# 设置默认编辑器为 VS Code
git config --global core.editor "code --wait"
配置的三个级别
Git 配置有三个层级,优先级从高到低:
| 级别 | 参数 | 配置文件位置 | 作用范围 |
|---|---|---|---|
| 仓库级 | --local | .git/config | 只对当前仓库生效 |
| 用户级 | --global | ~/.gitconfig | 对当前用户的所有仓库生效 |
| 系统级 | --system | /etc/gitconfig | 对系统所有用户生效 |
高优先级的配置会覆盖低优先级的同名配置。例如,你可以在 --global 中设置个人邮箱,在某个工作仓库的 --local 中设置公司邮箱。
查看配置
# 查看所有配置及来源
git config --list --show-origin
# 查看某个具体配置
git config user.email
# 查看全局配置文件内容
cat ~/.gitconfig
配置 SSH 密钥(简述)
与 GitHub 等远程仓库交互时,推荐使用 SSH 密钥认证而非密码:
# 生成 ED25519 密钥对(推荐)
ssh-keygen -t ed25519 -C "your@email.com"
# 查看公钥内容,复制到 GitHub Settings → SSH Keys
cat ~/.ssh/id_ed25519.pub
# 测试连接
ssh -T git@github.com
HTTPS vs SSH:HTTPS 使用用户名+密码(或 Personal Access Token),适合偶尔贡献的场景;SSH 配置一次后无需每次输入凭据,更适合日常开发。