Skip to content

ashin9/Gitlet

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

项目背景

Git 是目前最先进的分布式版本管理控制系统(VCS),但命令概念比较反直觉和抽象。

如果只学习上层操作使用,不理解底层实现原理,非常容易陷入死记硬背命令的状态,把几个命令当做魔法,因而非常容易遗忘。

虽然现在很多 GUI 工具辅助操作,但出现各种冲突和报错时,理解底层实现处理起来会更得心应手,否则有时候非常棘手,非常消磨心智,非常浪费时间。

项目意义

重点理解:快照存储、哈希索引、引用指针、分支合并策略等底层原理

可以练习:模块化设计、面向对象编程、状态管理、序列化、文件系统操作

Gitlet

Git 的 Java 简易实现。主要实现了以下操作命令

基础命令

  • init,初始化本地仓库,init commit,HEAD 指向默认 master 分支
  • add,Stage.addtions.add,Blob
  • rm,Stage.removals.add
  • commit,继承父 Commit,遍历 Stage 的 add 和 remove 表,更新 Commit 的 Map,创建新 Commit,更新分支头

状态命令

  • log,按父指针,遍历 commit,打印信息
  • global-log,遍历 Commits 文件夹,打印信息
  • show,根据 BlobID 打印文件内容
  • find,加载 Commit 对象文件夹所有 Commit 对象,遍历,根据 Commit message 找 Commit
  • status
    • 分支状态,遍历分支文件夹 refs/heads,HEAD 指向当前分支,第一个打印,并前面加*
    • 暂存状态,打印 Stage 对象的 add 作为 Staged Files ,remove 作为 Removed Files
    • 工作区修改状态,遍历文件集合,每个文件在三种状态区(工作目录、Stage、Commit)有不同的存在与否情况。
      • 意外删除:工作目录没有,Stage remove 没有,但 Stage add 或 Commit 有,说明文件被意外删除
      • 修改未 add:工作目录有,Stage add 没有,Commit 有,但 BlobID 不同,说明被修改但没 add
      • add 后被修改:工作目录有,Stage add 有,但 BlobID 不同,说明 add 后被修改
    • 未追踪状态,不在 Commit ,不在 Stage add,说明未追踪

分支命令

  • branch,创建分支文件 refs/heads/branchname,内容是指向的 CommitID 文本
  • rm-branch,删除分支文件
  • checkout
    • 1,恢复指定 Commit 中的文件(默认头 Commit),由文件名 filename 从 Commit.blobs 拿到 blobID,根据 blobID 加载 blob,再持久化到文件名
    • 2,切换分支,清空工作目录,恢复目标分支 headCommit 的所有 blobs 到工作目录,设置当前分支,清空暂存区。
  • reset,复用 checkout 分支,只不过是 checkout 任意 Commit,而非分支头 Commit。
  • merge
    • 1,DAG 上用 BFS 找最近公共祖先

    • 2,比较:split point, current commit, 给定分支的 head commit

      • split point == 给定分支头,给定分支已经包含在当前分支中,无需合并
      • split point == 当前分支头,可以直接快进,相当于 checkout
    • 3,处理文件变动,遍历文件集合

    • 4,创建新合并 Commit,两个父指针,发展为 DAG

情况 条件判断 操作
未修改 S == C && S == G 不处理
只给定分支修改 S == C && S != G 使用 G 的内容,checkoutstage
只当前分支修改 S != C && S == G 保留当前内容
两分支同改且相同 S != C && S != G && C == G 不处理
两分支不同改 S != C && S != G && C != G 冲突,写冲突标记
新文件:仅给定分支有 S == null && C == null && G != null 使用 G 内容,checkoutstage
新文件:两边都有不同内容 S == null && C != null && G != null && C != G 冲突
删除:当前删除,目标未删 S != null && C == null && G != null 冲突(或保留?)

远程命令

  • add-remote,创建远程分支文件 refs/remotes/branchname,内容指向远程地址(未实现网络,指向本地其他文件目录)
  • rm-remote,删除远程分支文件
  • push,读取远程分支 HeadCommit 和本地分支 HeadCommit,检查 fast-forward 合法性(远程 commit 是本地 commit 的祖先),所有远程没有的 Commit 写到远程 commit 目录,更新远程分支指针
  • fetch,读取远程分支 HeadCommit,DAG DFS 向回遍历,把所有 commit、blob 拉取到本地,创建[remoteName]/[branch] 分支
  • pull,fetch + merge

未支持但常用命令

  • git stash:暂存

  • git commit --amend:撤销 commit

  • git rebase:在当前分支重放一遍目标分支的操作,能精简分支,但会丢失一些现有提交,有一定风险

Gitlet 工作模式

数据的状态在四种区域流转

工作区(Working Directory)

本地工作目录下的新建文件或修改文件

暂存区(Staging Area)(可看做文件索引)

保存下次提交的文件列表信息,可看做是文件操作索引

本地仓库(Local Repository)

本地保存持久化的数据对象:Blob、Commit

远端仓库(Remot Repository)

远端本地保存持久化的数据对象:Blob、Commit

image-20250519224117835

Gitlet 架构设计

Git 本质实现文件状态集合的备份系统

Git 实现的难点是 0-1 的设计。如何设计程序来组织数据,实现目的?

如何持久化状态?使用文件

Git 初始化后会在本地目录下生成 .git 隐藏文件,里面存储持久化的数据

  • 1,使用文本文件存储状态:存储字符串标识状态

  • 2,使用对象文件存储状态:序列化后存储为文件

Gitlet 数据建模

Git 表面上的复杂性源于各种抽象概念的反直觉,但核心模型极致简洁和强大。

image-20250511165022308

Blob,文件建模为树叶 Blob

Tree,目录建模为数枝 Tree

此项目没有引入目录处理

Commit,快照建模为提交

存储文件索引,指向对应的 Blob。使用 Map 存储:<文件名, Blob ID>

历史记录建模为关联快照:快照由链表连接,当多分支时 Commit 可有多个父节点,变成有向无环图(DAG)

Commit 是不可变的

Branch,分支建模为指针

分支本质是指向 Commit 的指针,因此创建销毁极快,因此 Git 推荐使用多分支管理。

HEAD 指向当前分支,分支指向 Commit

Stage,下次提交增删的文件索引

add 文件,Map 表存储: 文件名 -> blobID

rm 文件,Set 存储:文件名

Repository,仓库建模为对象+指针

用 SHA-1 哈希生成对象地址作为指针

Wrok Dir,工作目录

程序

异常处理

条件分支

算法

List 遍历

有向无环图 DFS

设计模式优化代码思考

暂时没有用设计模式

Stage 对象单例模式?

单个本地仓库时,Stage 类只有一个实例,可以用单例模式提供一个全局访问点。

问题:生命周期难以管理,在每次 commit 后 stage 被清空/重置,所以 Stage 状态必须与磁盘保持一致性,可以 Stage.getInstance().reload(); // 从磁盘强制刷新

Blob 和 Commit 对象工厂模式?

将对象创建的复杂细节隐藏起来,使业务代码更简洁,也更符合单一职责原则

集中管理这些核心对象的创建逻辑,提高代码的灵活性和可维护性

每种命令操作,使用模版方法?

抽象类中定义算法的骨架(模板),将步骤的实现延迟到子类中完成。

特征:每种命令的操作基本遵循 3 个步骤,首先加载磁盘中持久化的状态到内存中,其次对其进行操作,最后操作完将状态持久化到磁盘中。

优点:

  • 复用代码:抽象类统一封装流程,避免每个子类都写一遍流程逻辑。
  • 开放封闭原则:子类可以灵活扩展步骤,而不用动流程。
  • 提升可读性和维护性:流程结构统一,行为局部差异。

命令处理大量条件判断,使用策略模式?

特征:很多命令有多种不同情况判断,比如 merge 合并冲突处理有10 来种情况判断,导致 if-else 炼狱,可读性差,扩展性差。if-else 大于 5,且后续可能扩展判断,推荐使用策略模式。

优点:

  • 解耦复杂逻辑,每种合并情况分成独立类,清晰单一职责

  • 易扩展,新增策略不需修改已有逻辑,只需添加新类

About

Git 的 Java 简易实现

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published