这里的project2是CS61B课程中被学生与老师评价最高的实战项目,非常值得一写。
Gitlet 是一个模仿 Git 轻量级命令行版本控制系统,完全由 Java 实现。本项目是加州大学伯克利分校 CS61B 课程的经典项目之一。它旨在模拟 Git 的核心功能,包括初始化仓库、暂存文件、提交更改、查看历史、创建分支与合并等
项目的核心目标是通过从零开始构建一个分布式版本控制系统(DVCS),来深入理解其内部的工作原理。所有的数据(如提交、文件快照等)都存储在项目根目录下的 .gitlet
文件夹中,这与 Git 的 .git
目录非常相似。
不过gitlet中关于文件存储的部分与git略有不同,gitlet将所有提交的文件blog都储存在了一个文件夹中,这样去寻找文件的时候需要遍历这一整个文件夹,而git是有一个索引文件来记录文件位置的,创建了多个层次的文件夹。
init
: 在当前目录下创建一个全新的 Gitlet 版本控制系统。clone
: (附加功能) 克隆一个远程仓库。
add <文件名>
: 将文件的当前版本添加至暂存区。commit <提交信息>
: 将暂存区的文件快照永久保存。每次提交都包含一条提交信息、一个时间戳以及一个指向父提交的引用。rm <文件名>
: 如果文件已在暂存区,则将其取消暂存。如果文件在当前提交中被追踪,则将其暂存为“待删除”状态,并从工作目录中删除该文件。
log
: 从当前HEAD
提交开始,展示提交历史。每次提交都显示其唯一的ID、时间戳和提交信息。global-log
: 功能与log
类似,但会展示所有分支的所有提交记录,无论当前在哪个分支。find <提交信息>
: 查找并打印出所有包含指定提交信息的提交ID。status
: 显示所有分支的列表并标记出当前所在分支。同时,也会显示已暂存(待添加)和已标记为待删除的文件。
branch <分支名>
: 基于当前的HEAD
提交创建一个新的分支。checkout -- <文件名>
: 将工作目录中的指定文件恢复到HEAD
提交中的版本。checkout <分支名>
: 切换到指定分支,并将工作目录中的文件更新为该分支最新提交的快照。rm-branch <分支名>
: 删除指定名称的分支。
Gitlet 的工作流与 Git 非常相似,其核心原理可以概括为以下几点:
-
不可变的对象图 (Immutable Object Graph):
- 整个版本历史被构建为一个由 Commit 对象 组成的有向无环图(DAG)。
- 每个 Commit 对象都通过其 SHA-1 哈希值(由自身内容计算得出)进行唯一标识,并且是不可变的。
- 每个 Commit 对象都包含一个或多个指向其父提交的引用(SHA-1值),从而形成了提交历史链。
-
内容与元数据分离:
- Blob(文件快照): 只存储文件的实际内容,不包含文件名等元数据。相同内容的文件在整个仓库中只会存储一份 Blob。
- Commit(提交): 存储的是元数据,包括作者、时间戳、提交信息,以及一个映射表,该表记录了在这次快照中,每个文件名(路径)对应的是哪个 Blob 的哈希值。
-
通过指针追踪状态:
- 分支 (Branch): 本质上是一个可变的指针,它指向某个特定的 Commit 哈希值。当你创建一个新的提交时,当前分支的指针会自动向前移动到这个新的提交上。
- HEAD: 另一个指针,它通常指向你当前所在的分支。
checkout
命令的核心就是移动HEAD
指针,并根据HEAD
指向的 Commit 来更新工作目录。
-
暂存区 (Staging Area / Index):
- 暂存区是介于工作目录 (Working Directory) 和 本地仓库 (Repository) 之间的一个中间层。
- 当执行
gitlet add
时,你实际上是将工作目录中文件的当前快照信息(文件名和Blob哈希值)记录到暂存区。 gitlet commit
命令则会清空暂存区,他会先复制一个最新的commit,然后将暂存区中的所有文件与最新的commit中对应的文件进行对比,新增与减少的文件直接提交,在commit中原来就有的文件,会将新的文件与旧的文件进行对比,如果有不同,则更新新的commit对象中文件名与blob哈希值的映射关系。最后储存新的commit,将head指向最新的commit。
🌱 学习与收获 完成这个项目让我受益匪浅,主要有以下几点收获:
-
真正理解了 Git 的工作原理: 在自己实现
add
,commit
,branch
,checkout
等命令后,我不再把 Git 当作一个“黑盒子”。我深刻理解了提交链、分支指针、暂存区和工作目录之间的关系,这让我在日常使用 Git 时更加得心应手,尤其是在处理复杂的合并冲突或版本回退时。 -
数据结构与算法的实际应用: 这个项目是数据结构知识的一次绝佳实践。例如,使用哈希图(HashMap)来构建提交对象中的文件映射和暂存区;将提交历史抽象为图(Graph)结构
-
系统设计与工程实践能力: 如何规划项目结构、如何将复杂系统拆分为独立的模块(例如将数据模型、序列化工具、命令处理器分离),以及如何处理大量的边界情况和错误逻辑,这些都极大地锻炼了我的系统设计和工程思维。
-
持久化与序列化: 通过将 Java 对象(如 Commit、Blob)序列化到本地文件,我掌握了在没有数据库的情况下,如何实现数据的持久化存储,并理解了其中的挑战,例如版本兼容性和性能问题。
-
调试复杂系统的能力: 面对一个状态复杂且环环相扣的系统,定位 Bug 变得极具挑战。我学会了如何通过编写单元测试、打印关键状态日志、以及在脑海中模拟数据流动来逐步缩小问题范围,最终找到并修复错误。这培养了我解决复杂问题的耐心和逻辑分析能力。
运行直接运行project2中的makefile即可。