Skip to content

这是伯克利分校CS61B的作业与项目,其中的project2-gitlet是模仿git实现一个版本管理系统,非常值得一写

Notifications You must be signed in to change notification settings

chegangan/cs61b

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

64 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

这里的project2是CS61B课程中被学生与老师评价最高的实战项目,非常值得一写。

project2-Gitlet - 一个迷你的版本控制系统

项目简介

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 非常相似,其核心原理可以概括为以下几点:

  1. 不可变的对象图 (Immutable Object Graph)

    • 整个版本历史被构建为一个由 Commit 对象 组成的有向无环图(DAG)。
    • 每个 Commit 对象都通过其 SHA-1 哈希值(由自身内容计算得出)进行唯一标识,并且是不可变的
    • 每个 Commit 对象都包含一个或多个指向其父提交的引用(SHA-1值),从而形成了提交历史链。
  2. 内容与元数据分离

    • Blob(文件快照): 只存储文件的实际内容,不包含文件名等元数据。相同内容的文件在整个仓库中只会存储一份 Blob。
    • Commit(提交): 存储的是元数据,包括作者、时间戳、提交信息,以及一个映射表,该表记录了在这次快照中,每个文件名(路径)对应的是哪个 Blob 的哈希值。
  3. 通过指针追踪状态

    • 分支 (Branch): 本质上是一个可变的指针,它指向某个特定的 Commit 哈希值。当你创建一个新的提交时,当前分支的指针会自动向前移动到这个新的提交上。
    • HEAD: 另一个指针,它通常指向你当前所在的分支checkout 命令的核心就是移动 HEAD 指针,并根据 HEAD 指向的 Commit 来更新工作目录。
  4. 暂存区 (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即可。

About

这是伯克利分校CS61B的作业与项目,其中的project2-gitlet是模仿git实现一个版本管理系统,非常值得一写

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 8