记录一下做gitlet的思路。方便后期对项目进行重构和优化~

Gitlet顶层设计

  -.gitlet 存储一切
    -objects 存储commit和blob对象(使用hashcode作为文件名)
     -commits 存储每个commit对象
     -blobs 存储每个blob对象
   -refs
     -heads 存储分支末端(文件名为分支名,内容为对应commit的hashCode)
     -remotes 远端分支(unfinished)
   -HEAD 存储当前branch的name,默认为master
   -staging 存储缓存区内容(以Stage class的形式存储blob)
   -removedStage 存储被rm的文件(以Stage class的形式存储)


在这里插入图片描述

Class定义

Commit

   实例变量
    String message;
    Date curTime;
    List<String> parentHashCodes;  // 第一个code存储的是当前分支的parent
    List<String> blobCodes; // 使用hashCode数组表示对应的blob
   method
    public String getMessage();
    public Date getCurTime();
    public List<String> getParentsHashCode();
    public List<String> getBlobCodes();
   功能

   记录用户每次commit的msg,时间信息,并对相关文件进行跟踪。每个commit实例都是commit tree上的一个节点。

Blob

   实例变量
    String refs;   // 存储的是相对路径
    byte[] content;
   method
    public String getRefs();
    public byte[] getContent();
   功能

   将文件的ref和content以blob的形式存起来,方便加入staging area和被commit跟踪。

Stage

   实例变量
    List<Blob> blobs;  // 存储stage for add or removal的blob
   method
    public List<Blob> getBlobs();
    public void add(Blob blob);
    public void add(List<Blob> addBlobs);
   功能

   将stage for add or removal的文件以stage的形式存储,并写入相应的文件中(这个可以优化,实际上只要一个文件就够了)。

Command

init 思路
   init
   创建必要的文件夹;
   初始化initial commit,并将其存入commits文件夹。
   失败的情况:
   如果有.gitlet文件存在,输出错误信息:
   A Gitlet version-control system already exists in the current directory.

commit的格式如下:
   它应该包含hashcode,date和message(merge commit比较特别)
在这里插入图片描述

add 思路
   add [file name]
   将需要stage for add的文件以blob的形式存入staging中;
   如果file和当前commit中跟踪的文件相同(blob的hashCode相同),则不将其添加到staging中;
   失败的情况:
   如果该文件不存在,输出错误信息:
   File does not exist.

commit 思路
   commit [message]
   实例化一个新的commit对象,blobCodes复制headCommit中的内容;
   更新msg和parentCodes;
   根据staging中的blobs更新/增加commit跟踪的对象;
   根据removedStage中的blobs删除commit跟踪的对象;
   将commit存入commits文件夹中;
   让当前branch指向新的commit;
   清空缓存区(删除staging和removedStage文件);
   失败的情况:
   如果缓存区中没有内容(staging和removedStage不存在),输出错误信息:
   No changes added to the commit.
   如果message为空,输出错误信息:
   Please enter a commit message.

rm 思路
   rm [file name]
   如果文件在stage for add区域,则将其中缓存区删除;
   如果文件被当前commit跟踪,则将其存入stage for removal区域。如果该文件存在于工作目录中,就将其删除;
   失败的情况:
   如果该文件既不在stage for add区域,也不被当前commit跟踪,输出错误信息:
   No reason to remove the file.

log 思路
   log
   从当前commit开始,依次回溯打印commit信息,直到initial commit;
   如果有多条支路(比如merge commit),选择first parent commit进行打印,忽略另外一条支路。体现在commit的parentHashCodes中,第一个存储的便是first parent;
   merge commit比较特别,需要多打印一行;
在这里插入图片描述

   失败的情况:
   无

global-log 思路
   global-log
   使用gitlet.Utils中的method获取commits文件夹中所有的commit,打印即可,不关心顺序;
   失败的情况:
   无

find 思路
   find [commit message]
   打印所有符合message的commit id,如果有多个结果,一个一行;
   遍历commit即可,和global-log的思路类似,不过要筛选一下;
   失败的情况:
   如果没有符合条件的commit存在,输出错误信息:
   Found no commit with that message.

status 思路
   status
在这里插入图片描述
   打印现存的分支名称,读取heads中文件的名称即可;
   在当前分支的前面加上*号;
   打印缓存区的文件;
   打印被删除的文件名称;
   最后两项是extra credit的内容,现在不用管,暑假有空再看看吧;
   失败的情况:
   无

checkout 思路
check out命令有三种使用场景:
1.checkout – [file name]
   如果文件被当前commit所跟踪,则其放入工作目录中(如果工作目录中有同名文件,则替代它);
   失败的情况:
   文件并不被当前commit跟踪,输出错误信息:
   File does not exist in that commit.

2.checkout [commit id] – [file name]
   和1很类似,不过换成对应id的commit
   失败的情况:
   commit不存在,输出错误信息:
   No commit with that id exists.
   文件不被对应commit跟踪,输出错误信息:
   File does not exist in that commit.

3.checkout [branch name]
   checked branch和当前commit所跟踪的文件可以分为三类:
      仅被当前commit跟踪(删除文件)
      被两个commit共同跟踪(用checked branch中的blobs覆写这些文件)
      仅被checked branch跟踪;
   仅被checked branch跟踪的文件又可以分为两类:
      不存在于当前工作目录(覆写)
      已经存在于当前工作目录的文件(打印错误信息)
   清空缓存区(删除对应的文件即可)
   失败的情况:
   如果checked branch不存在,输出错误信息:
   No such branch exists.
   如果checked branch就是当前分支,输出错误信息:
   No need to checkout the current branch.
   如果工作目录中存在仅被checked branch跟踪,且将要被覆写的文件,输出错误信息:
   There is an untracked file in the way; delete it, or add and commit it first.

branch 思路
   branch [branch name]
   在heads文件夹中创建新的branch,内容为当前commit的hashCode;
   失败的情况:
   无

rm-branch 思路
   rm-branch [branch name]
   删除heads文件夹中对应name的文件即可;
   失败的情况:
   如果给定的branch不存在,输出错误信息:
   A branch with that name does not exist.
   如果尝试删除的branch为当前branch,输出错误信息:
   Cannot remove the current branch.

reset 思路
   reset [commit id]
   相当于check out到对应的commit;
   复用check out中的代码;
   将当前分支存储的内容改为对应commit的id;
   清空缓存区(删除对应的文件);
   失败的情况:
   如果没有对应的commit存在,输出错误信息:
   No commit with that id exists.
   如果工作目录存在仅被reset commit跟踪,且将被覆写的文件,输出错误信息:
   There is an untracked file in the way; delete it, or add and commit it first.

merge 思路
   merge [branch name]
   这个是整个项目中最复杂的命令了,推荐观看intro视频梳理思路。
在这里插入图片描述

   失败的情况:
   如果缓存区还有blob(文件存在),输出错误信息:
   You have uncommitted changes.
   如果给定的branch不存在,输出错误信息:
   A branch with that name does not exist.
   如果给定的branch和当前branch相同,输出错误信息:
   Cannot merge a branch with itself.
   如果工作目录存在仅被merge commit跟踪,且将被覆写的文件,输出错误信息:
   There is an untracked file in the way; delete it, or add and commit it first.

总结

前后花了大概一周左右的时间,兜兜转转总算做完了基础的部分。
在这里插入图片描述

收获

   复习了cs61b前期所学的大部分知识,coding能力有了提升;
   第一个可以被写在简历上的项目(后期可能得优化一下~);
   开始的时候一片空白,无从下手,充满恐惧;完成之后则是满满的成就感。

不足

   设计和理解存在不足——在动手coding之前没有充分的准备,对项目的规划不足,有很多地方都不理解就开始动手了,导致后期经常更改前面的代码,浪费了很多时间;
   项目中使用的数据结构没有事先规划好,大量无脑使用List,浪费很多时间;
   merge寻找splitPoint的算法还存在可以改进的点,现在使用的时间复杂度太高了(可以使用BFS来做);
   在定义函数前仔细考虑,检查是否有同样功能的函数已经被定义了,不要重复造轮子;
   使用check,is,get,find等关键字细分函数的功能;
   Java泛型和数组(有空看一下)。
   严重依赖autograder,20分钟的等待时间太漫长啦~

未来

   继续刷完cs61b吧,暑假有空可以再回顾一下gitlet,挺好的。



To be a sailor of the world bound for all ports.
Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐