CS61B | gitlet
核心类
Commit.java
- 功能: 记录一次提交的元数据和状态
- 字段:
String message
: 提交信息List<String> parents
: 父提交(size <= 2)(gitlet的限制,合并的分支最多是两个)Map<String, String> pathToBlobID
: filePath->Blob对象的Hash值 的映射String id
: 当前提交Hash值
- 主要方法 :
Constructer
:Commit()
空提交Commit(String message, List<String> parents, Map<String, String> pathToBlobID)
: 普通提交
String dateToTimeStamp(Date date)
: 把Date类型的时间转换成指定表达形式的时间戳String generateID()
: 把关键信息组合起来生成唯一哈希值,确保唯一性void save()
: 把提交对象保存到文件系统Commit fromFile(String id)
: 从文件系统中加载对象Getters
:String getMessage()
String getTimeStamp()
String getParents()
Map<String, String> getPathToBlobId()
String getId()
Blob.java
- 功能 :Blob对象用于存储某个版本文件的内容快照(最大发挥SHA-1内容可寻址的优势)
- 字段 :
String id
: Blob对象的唯一表示IDbyte[] contents
: 以字节数组的形式存储文件内容,保留文件的原始二进制数据String filePath
: 对应文件在工作区的路径
- 主要方法
Constructor
:Blob(File file)
: 通过文件对象创建Blob快照
void save()
: 把Blob对象持久化存储到文件系统里面Blob fromFile(String blobId)
: 从文件中反序列化加载Blob对象Getters
:Srting getId()
String getFilePath()
byte[] getContents
StagingArea.java
- 功能 : 暂存区类,用于记录即将提交的文件变更
- 字段 :
Map<String, String> added
: 新增文件, 存储 filePath->blobId 的唯一映射Set<String> removed
: 待删除文件, 提交后这些文件将在Commit里面不存在
- 主要方法 :
Constructor
:StagingArea()
: 初始化一个空的暂存区
void save()
: 将暂存区对象序列化写入指定文件中StgingArea load()
: 从文件中反序列化提取暂存区对象void unStageFile(String filePath)
: 取消文件的所有暂存状态,从新增区和待删除区都去掉void stageAdd(String filePath, String blobId)
: 将文件添加到新增暂存区,从待删除暂存区删除void stageRemove(String filePath)
: 从待删除区中添加文件,从新增暂存区中删除文件void clear()
: 清空暂存区boolean isEmpty()
: 检查暂存区是否为空Getters
:Map<String, String> getAdded()
Set<String> getRemoved()
Repository.java
.gitlet文件结构
1 | .gitlet |
init()
- 功能 : 在当前目录创建一个新的Gitlet版本控制系统
- 实现方法 :
- 创建目录结构
- 创建初始commit
- 设置master分支,写入最初的commit Id
- 设置HEAD指向当前当前分支的路径
- 初始化暂存区
add(String fileName)
- 功能 : 将文件当前状态的副本添加到暂存区
- 实现方法 :
- 校验仓库是否初始化
- 判断文件是否存在于当前工作目录
- 根据文件生成Blob对象
- 获取当前分支的最新提交
- 尝试在当前的最新提交的快照中找到指定文件的Blob ID
- 如果两者相同,那么就取消文件的任何暂存标记
- 如果两者不同,那么就把文件添加到新增暂存区
- 保存暂存区
commit(String message)
- 功能 :保存当前提交和暂存区的文件快照,以便后续恢复,同时创建一个新的提交
- 实现方法 :
- 校验仓库是否初始化
- 校验信息是否为空
- 加载暂存区,若暂存区为空,说明没有需要提交的对象
- 获取当前分支的引用文件和提交对象
- 以当前提交为基础创建新的提交对象(包括更新父提交,继承内容快照)
- 依靠暂存区的变更,更新内容快照
- 创建新的提交并保存
- 更新分支指针的内容为新的提交
- 清空并保存暂存区
rm(String fileName)
- 功能 : 如果文件已经呗暂存添加,则取消暂存;如果文件在当前提交被跟踪,则将其标记为待删除,并且删除在工作目录上的文件
- 实现方法 :
- 校验仓库初始化
- 加载暂存区和最新的提交信息
- 如果在新增暂存区存在该文件,那就取消暂存
- 如果文件被当前提交跟踪,那么就标记为待删除,并且在工作区中移除该文件
- 如果并非以上两种情况,则命令失效,报出错误信息
log()
- 功能 : 从当前头提交开始,沿着提交树逆向显示每个提交的信息,直至初始提交,遵循第一父提交链接,忽略合并中发现的任何第二父提交
- 实现方法 :
- 校验仓库初始化
- 获取当前的最新提交
- 遍历父提交链打印,直至初始提交
global-log()
- 功能 :会显示所有的提交信息,提交的顺序无所谓
- 实现方法 :
- 校验仓库初始化
- 利用utils里的函数获取commits文件夹里面的所有提交
- 逐条打印
find(String message)
- 功能 : 打印具有给定提交信息的提交ID
- 实现方法 :
- 校验仓库初始化
- 利用utils里的函数获取commits文件夹里面的所有提交
- 遍历,寻找是否有相同的信息即可
- 若找不到,则打印错误信息
status()
- 功能 :描述当前的所有分支,用*标记当前所在的分支;同时显示暂存添加和待删除的文件,(extra:显示未修改的暂存和未跟踪的文件,推荐实现,方便后续debug)
- 实现方法 :
- 校验仓库初始化
Branches
- 获取所有分支的名称
- 获取当前分支的名称
- 按字典序打印分支列表,注意标注当前分支
stagedFile
- 加载暂存区
- 获取新增暂存区的文件并打印
rmmovedFile
- 加载暂存区
- 获取待删除暂存区的文件并打印
modificationsNotStagedForCommit
- 加载暂存区,当前的提交
- 检查所有当前提交跟踪的文件,打印满足条件的文件并添加对应标记
- 在新增暂存区里
- 工作目录的文件被删除
- 工作目录的文件内容与新增暂存区的文件内容不一致
- 未在删除暂存区里
- 工作目录的文件被删除
- 工作目录的文件内容与被提交跟踪的文件内容不一致
- 在新增暂存区里
untrackedFiles
- 加载暂存区,当前提交
- 打印存在于工作目录上,但既未背暂存添加也未被提交跟踪的文件
checkout
- 功能1 :
checkout -- [fileName]
: 将文件在头提交的版本中取出并且放入工作目录。
- 实现方法 :
- 获取最新提交
- 检查文件在最新提交里是否存在
- 获取blob id加载文件内容,写入工作区
- 功能2 :
checkout [commit id] -- [fileName]
:从给定提交中获取文件并将其写在工作目录里
- 实现方法 :
- 解析提交id(可能传入的是提交id的前缀),恢复成完整版,如果不存在那么报错退出
- 加载指定提交
- 检查文件在指定提交中是否存在
- 获取blob id加载文件内容,写入工作区
- 功能3 :
checkout [branch name]
: 将给定分支头部提交的所有文件取出放在工作目录中。并将给定分支视为当前分支。清空暂存区
- 实现方法 :
- 检查分支是否存在
- 检查是否是当前分支
- 加载目标分支的最新提交
- 检查未跟踪的文件是否会被覆盖
- 加载当前分支跟踪的文件和目标分支跟踪的文件以及工作区的文件
- 检查在工作区的文件,如果在当前分支中未被跟踪而且在目标分支中存在,则会被覆盖
- 更新工作区的文件
- 写入目标分支的所有文件
- 删除当前分支存在但是目标分支没有的文件
- 更新HEAD指向目标分支
- 清空暂存区并保存
branch(String branchName)
- 功能 :创建一个具有指定名称的分支,并且指向当前分支的头提交。该命令不会立刻切换到新建立的分支
- 实现方法 :
- 检查仓库是否存在
- 检查是否有同名branch
- 获取当前最新的提交id
- 在分支中写入当前的提交id
rmBranch(String branchName)
- 功能 :删除指定名称的分支,仅删除与分支关联的指针
- 实现方法 :
- 检查仓库是否存在
- 检查是否存在这个分支
- 检查是否属于当前分支
- 删除分支指针文件
reset(String commitId)
- 功能 : 检出给定提交的所有文件,移除该提交中不存在的已跟踪文件,同时将该分支的头部移到该提交节点,清空暂存区
- 实现方法 :
- 检查仓库
- 解析完整commit id
- 检查是否会有未被跟踪的文件被覆盖
- 更新工作区到目标提交状态
- 更新当前分支的指针到目标提交上
- 清空暂存区
- 与git里面
checkout [commit id]
的辨析:操作 分支指针 暂存区 应用场景 checkout [commit id]
HEAD脱离(Detached HEAD) 保留 临时浏览,实验旧版本 reset [commit id]
指向当前提交 清空 回退提交(硬回退), 丢弃历史和改动
merge(String branchName)
- 功能 :将给定分支的文件合并到当前分支
- 实现方法 :
- 检查是否满足合并条件
- 校验仓库初始化
- 校验给定分支是否存在
- 校验是否合并自身
- 校验是否有未提交的修改
- 校验是否会有未被跟踪的文件会被合并分支覆盖
- 获取当前分支的最新提交信息和给定分支的提交信息
- 寻找最近公共祖先
- 收集当前分支的所有祖先,不只是第一父链
- bfs查找给定分支的祖先,最先出现在当前分支祖先里面的就是最近公共祖先
- 处理特殊合并场景
- 如果最近公共祖先是给定提交,不需要进行任何操作
- 如果最近公共祖先是当前提交,直接将工作区更新为给定分支的提交,并且更新HEAD文件为给定提交
- 处理所有文件的合并
- 获取三方所有文件路径的集合,并遍历
序号 split curr given 情况 1 存在 不修改 修改 given覆盖curr 2 存在 不修改 删除 直接删除curr 3 存在 不修改 不修改 不动 4 存在 修改 不修改 不动 5 存在 修改 修改 修改得一致,不动;修改得不一致,冲突 6 存在 修改 删除 冲突 7 存在 删除 不修改 不动 8 存在 删除 修改 冲突 9 存在 删除 删除 不动 10 不存在 新增 新增 新增内容不同,冲突:内容相同, 不动 11 不存在 新增 不新增 不动 12 不存在 不新增 新增 given 覆盖curr 13 不存在 不新增 不新增 不动 - 冲突情况 (5,6, 8, 10)
- 读取给定分支的该文件与当前分支的该文件
- 判断文件内容最后是否需要增加空行(特判空文件,要注意)
- 生成冲突标记内容,在工作区重新写入该文件
- 新增暂存区添加冲突文件,保存
- 标记已产生冲突
- 读取给定分支的该文件与当前分支的该文件
- given覆盖curr情况(1, 12)
- 读取给定分支的该文件
- 直接在工作区重新写入该文件
- 新增暂存区添加该文件,保存
- 删除curr情况(2)
- 在工作区找到该文件并删除
- 在待删除暂存区中添加该文件并保存
- 获取三方所有文件路径的集合,并遍历
- 创建合并提交
- 生成合并信息
- 生成合并提交的父提交(2个)
- 生成新快照
- 把当前提交中不再删除暂存区的文件添加到新快照上(对应不动的文件和删除的文件)
- 把新增暂存区的文件也添加到新快照上(对应冲突文件和given覆盖curr的文件)
- 创建并保存合并提交
- 更新当前分支的指针指向合并提交
- 清空暂存区并保存
- 打印冲突提示
- 检查是否满足合并条件
debug的坑点和一些注意事项
- 文件操作的顺序不要写反了,比如reset里面应当是先把给提交的文件更新到工作区,然后再把HEAD文件换成给定提交的HASH值,顺序搞反了很难绷
- merge函数里面找split的时候,应该采用bfs遍历所有父提交,而不是单纯沿着第一父链去寻找
- 处理冲突文件的时候注意换行符,被删除的文件要进行特判
- 待补充……