本文不包括 Git 命令的介绍与使用,只分享 Git 的关键概念与 Github 项目的基本工作流程。作者相信先了解它们对后续的学习和工作大有裨益。(如有错误和建议请大家评论告知)
版本控制系统
VCS → Version Control System,版本控制系统 → 一个跟踪文件变化、记录修订情况、协助开发项目的系统。
分为 本地 VCS、集中式 VCS 和分布式 VCS:
- 本地 VCS——早期的版本控制系统,所有的版本信息都存储在本地计算机上。
- 有名的示例——RCS——在硬盘上保存补丁集(补丁是指文件修订前后的变化);通过应用所有的补丁,可以重新计算出各个版本的文件内容。
- 缺点:无法支持多人协作,数据容易丢失。
- 集中式 VCS(Centralized Version Control System, CVCS):所有版本信息集中存储在一个中央服务器上,开发者通过客户端访问。
- 示例:SVN(Subversion)、CVS(Concurrent Versions System)、Perforce
- 缺点:
- 单点故障问题:如果中央服务器宕机或数据丢失,整个系统可能瘫痪,因为客户端只保存快照,而不记录版本信息。
- 开发者需要联网才能提交或创建分支。(没有网络几乎做不了任何事情)
- 分布式 VCS(Distributed Version Control System, DVCS):每个开发者都有一个完整的本地仓库,包含所有版本历史。
- 示例:Git、Mercurial
- 优点:
- 更高的容错性:即使中央服务器出现问题,开发者仍然可以从其他人的本地仓库恢复数据。
- 无需联网依赖中央服务器即可提交、创建分支。
Git 特点
了解 Git 的具体特点是十分必要的:
- 直接记录快照(snapshot),而非差异(delta)比较:其他 VCS(如 SVN、CVS、Perforce)保存的每个版本记录的是文件修改前后的差异,没修改就不记录。而 Git 每次保存版本都直接记录整个仓库的快照,当然没有修改的文件只是保留指向其存储的链接。
- 几乎所有操作都是本地的:因为 Git 在本地保留了版本历史,所以不需要像其他 VCS 一样连接服务器获取。
- Git 保证完整性:Git 中所有的数据在保存前会进行 SHA-1 校验和(40 位十六进制 hash 值)计算,校验和也是对应数据的引用。
- Git 通常只添加数据:Git 常规操作都只是往数据库添加数据,只要存在一次快照提交,就很难导致文件不可恢复。
- 三种状态:已修改(modified)、已暂存(staged)、已提交(commited):
- 已修改表示文件被修改但没被标记;
- 已暂存表示已修改文件被标记,下一次提交时会被保存到 Git 数据库;
- 已提交就是已暂存文件已经保存到 Git 数据库了。
工作区、暂存区和 Git 目录
工作区(Working Directory,叫 Working Tree 更好)是位于磁盘上的从 Git 数据库中提取出来的某个版本的内容,我们看见的、修改的文件就是在工作区。
暂存区(Staging Area)是 Git 目录下的一个叫做 index 的文件。
Git 仓库目录(.git Directory)(Repository)是一个隐藏的名为 .git 的文件夹,它保存了项目的元数据和对象数据库。克隆项目时克隆的就是这部分。
Git 底层对象
Git 的底层由以下四种对象构成,所有对象均通过 SHA-1 哈希值唯一标识:
- Blob 对象(二进制大对象):存储文件内容(不包含文件名、权限等元数据)。每个文件对应一个 Blob,内容相同则哈希值相同。
- Tree(目录结构):记录目录中的文件列表和子目录结构。包含文件名、权限、对应的 Blob 或子 Tree 的哈希值。
- Commit(提交):指向一个 Tree(项目快照)、父提交的哈希值、作者、提交信息等。形成链式结构,构成版本历史。
- Tag(标签):标记某个特定提交(如版本号 v1.0.0)。
一次提交的生成过程:修改文件 → 生成新 Blob → 更新 Tree → 创建 Commit 指向该 Tree 和父提交
Git 仓库结构
git init 时会创建一个.git 隐藏文件夹(即 Git 仓库,Git Repository),主要有以下内容:
- objects:所有对象存储在 .git/objects 目录中,按哈希值前两位分目录存储。例如:哈希 a1b2c3... 的对象路径为 .git/objects/a1/b2c3...。对象内容经过压缩(使用 zlib 的 DEFLATE 算法)。
- refs:存储指向分支、标签和远程仓库等数据的提交对象的指针。.git/refs/heads/
文件存储指向的提交哈希值。 - HEAD:.git/HEAD 文件存储当前分支的引用路径(如 ref: refs/heads/main)。(该分支会被提取到工作区供开发者修改)
- config:项目的配置项。
- hooks:客户端或服务端的钩子脚本。
分支与 HEAD
分支是一个指向某个提交的可移动指针(如 main 分支指向最新提交)。
HEAD 是一个特殊指针,指向当前所在的分支(或直接指向某个提交,即“分离头指针”状态)。当提交时,HEAD 指向的分支会自动向前移动。
分支就像是是贴在提交上的“便签纸”,HEAD 就是“你当前正在使用的便签纸”。
Git 提交信息约定(语义化提交)
当我们提交暂存区到 git 仓库时,需要写一个信息,是对这次提交的简要说明。
为什么需要约定化的提交信息?约定化的提交信息有利于提高历史可读性从而方便项目协作和管理、方便生成 CHANGELOG 和自动化版本号等。
较流行的提交信息约定有:Angular 和 Conventional Commits 的约定。
这里只介绍 Angular 提交信息约定,它被业界广泛使用。
格式:
<type 类型>(<scope 作用域>): <summary 摘要> --组成--> <header 头部>
<空行>
<body 正文>
<空行>
<footer 脚注>
规则:
- type(必填项):必须是下列值之一:
- feat:新增功能(对应语义化版本中的 MINOR)
- fix:修复 Bug(对应语义化版本中的 PATCH)
- docs:文档更新(如 API 文档、README)
- refactor:代码重构(既不新增功能,也不修复 Bug)
- test:测试相关(添加或修改测试代码)
- perf:性能优化
- ci:CI 配置修改(如 SauceLabs、GitHub Actions)
- build:构建系统或外部依赖的改动(如 npm、gulp)
- scope(可选项):一般是受影响的 npm 包的名称,也有例外情况:
- packaging:影响所有包的 npm 布局(如 package.json 全局修改)。
- changelog:更新 CHANGELOG.md。
- devtools:浏览器扩展变更。
- 空作用域:跨包变更(如全项目测试修复)或无明确范围(如文档拼写错误)。
- summary(必填项):
- 动词开头,使用现在时态的祈使句(如 add、fix、remove)。
- 首字母小写,结尾不加句号。
- 长度简洁。
- body(除 docs 类型外必填):
- 解释为什么要改动,对比旧行为与新行为。
- 使用现在时态的祈使句。
- footer(可选项):用于标注破坏性变更、弃用声明,以及关联相关的 Issue 或 PR。格式自行查阅 Angular 提交信息约定。
- revert commits:
revert: <原提交的 Header>
<空行>
This reverts commit <原提交 SHA>.
<回退原因>
Git 分布式工作模式
现在是时候加入 Git 中心服务器的角色进行讨论了。这会涉及到使用它与其他 developer 进行协作开发的流程或工作模式。
- 集中式工作流:
- 逻辑:所有开发者围绕一个中央仓库同步代码,类似传统集中式版本控制(如 SVN),但保留了 Git 的本地操作能力。
- 流程:
- 开发者从中央仓库获取最新代码。
- 在本地完成修改后,直接同步到中央仓库。
- 多人协作时需频繁同步中央仓库的变更,避免代码冲突。
- 功能分支协作模式:
- 逻辑:每个新功能或修复在独立分支中开发,完成后通过合并请求(Pull Request/Merge Request)将代码集成到主分支。
- 流程:
- 开发者从主分支创建独立分支进行开发。
- 开发完成后,将分支推送到远程仓库并申请合并。
- 团队成员通过代码评审讨论修改,确认后合并到主分支。
- 标准化分支模式(Git Flow):
- 逻辑:通过严格定义分支角色和生命周期,管理复杂项目的开发、测试和发布流程。
- 分支角色:
- 主分支:仅存放稳定版本(如正式环境代码)。
- 开发分支:集成所有新功能的日常开发主线。
- 临时分支:包括功能分支(开发新功能)、发布分支(测试版本)、热修复分支(紧急修复生产问题)。
- 流程:
- 新功能分支基于开发分支创建,完成后合并回开发分支。
- 发布时从开发分支创建测试分支,验证后合并到主分支并标记版本。
- 生产问题通过热修复分支快速处理。
- 开源协作模式(Forking):
- 逻辑:贡献者无权直接修改主仓库,需先复制(Fork)一份个人副本,修改后通过合并请求向主仓库提交代码。
- 流程:
- 贡献者复制主仓库到个人账号下。
- 在个人副本中开发完成后,向主仓库发起合并请求。
- 维护者审核代码后决定是否合并。
- 持续交付模式(GitHub Flow):
- 逻辑:强调主分支随时可发布,通过短周期功能分支快速迭代。
- 规则:
- 主分支始终保持可部署状态。
- 新功能通过小颗粒度分支开发,快速合并。
- 自动化测试和代码审查是合并的必要条件。
Git 裸仓库
远程仓库通常是裸仓库(bare repository)。
(1)什么是裸仓库?什么又是普通仓库?
- 裸仓库(Bare Repository):一个没有工作目录的 Git 仓库,仅包含 Git 的版本控制数据(即 .git 目录的内容)。通常以 .git 结尾。
- 普通仓库(Non-Bare Repository):包含工作目录(用户可见的文件)和 .git 目录(版本控制数据)。
(2) 为什么远程仓库通常是裸仓库?
- 协作安全性:裸仓库无法直接修改文件(没有工作目录),避免多人同时操作导致冲突。开发者只能通过推送(push)或拉取(pull)同步代码,确保远程仓库的稳定性。
- 节省资源:裸仓库无需维护工作目录的文件副本,仅存储 Git 元数据(提交历史、分支、标签等),节省存储空间。
- 功能纯粹性:远程仓库的职责是作为代码的“中心枢纽”,不需要本地开发功能(如代码编辑、临时构建文件等)。
Git 使用的传输协议
Git - 协议
Git 通过多种传输协议实现仓库的远程通信,不同协议在安全性、效率和适用场景上各有特点。
以下是 Git 支持的主要传输协议及其核心特性:
- 本地协议:
- 介绍:通过本地文件系统路径直接访问远程仓库(如共享目录、NFS 挂载等),无需网络传输。远程仓库的形式通常是裸仓库(前面已介绍),存储在同一主机的另一目录中。
- 地址格式:file:///path/to/repo.git(速度较慢但数据更干净) 或直接使用路径 /path/to/repo.git(硬链接或直接复制,极快)。
- 特点:
- 最高速度:无需网络传输,适合局域网或本地开发。
- 零配置:简单快捷,无需服务器。
- 安全性低:依赖文件系统权限,不适合跨团队协作。
- 适用场景:个人开发、团队共享存储环境(如内网 NAS)。
- HTTP 协议:
- 介绍:通过 HTTP(S) 通信,支持智能 HTTP 协议(Smart HTTP,git 1.6.6 版本)和哑 HTTP 协议(Dumb HTTP)。现代 Git 服务(如 GitHub/GitLab)默认使用智能 HTTP 协议。
- 地址格式:https://github.com/user/repo.git 或 https://github.com/user/repo.git。
- 哑 HTTP 协议:
- Git 早期通过 HTTP 传输数据的一种简单实现方式。
- 只需部署静态文件,无需服务端 Git 支持,可通过普通静态 Web 服务器托管只读仓库。
- 不支持推送,无法动态优化传输。
- 数据全量下载,无压缩,耗时长、流量大。
- 智能 HTTP 协议:
- 运行在标准的 HTTP/S 端口(80/443)上并且可以使用各种 HTTP 验证机制。
- 身份验证灵活:支持用户名密码、OAuth、Token(如 GitHub Personal Access Token)。
- 数据增量压缩。
- 企业防火墙通常允许进入。
- 性能中等:比 SSH/Git 协议略慢,但可通过 HTTP/2 优化。
- 适用场景:公开/私有仓库托管(如 GitHub、GitLab)、企业级 Git 服务。
- SSH 协议:
- 介绍:基于 SSH 加密通道传输数据,使用公钥认证。
- 地址格式:git@github.com:user/repo.git。
- 特点:
- 高安全性:加密通信 + 密钥认证,适合敏感代码库。
- 免密码推送:配置 SSH 密钥后无需每次输入凭据。
- 性能优秀:压缩传输,速度接近 Git 协议。
- 权限管理:依赖服务器 SSH 账户权限。
- 适用场景:开发者个人推送权限、私有仓库托管。
- Git 协议:
- 介绍:专用轻量级协议,默认端口 9418,仅支持只读访问。
- 地址格式:git://github.com/user/repo.git。
- 特点:
- 极致速度:无加密开销,适合大规模公开仓库分发(如 Linux 内核)。
- 无身份验证:完全匿名访问,无法推送代码。
- 防火墙限制:可能被企业网络屏蔽。
- 适用场景:开源项目的公共只读镜像(如 GitHub 的 git:// 地址)。
如何对开源项目做出贡献
Git - 对项目做出贡献
(这也是前面提到的开源协作模式)