0


CS61B sp21fall Project02 Gitlet

Project02 Gitlet

一、项目简介

这个项目将会带着我们一同手写实现一个Git版本管理工具,事件性和趣味性非常强,想象一下自己可以使用自己写的代码管理工具Gitlet,想想都爽。

二、Git和Gitlet

2.1 Git简介

  1. 保存整个文件目录的内容。在git中,这被称为提交(git commit + git push),而保存的内容本身被称为提交。
  2. 还原一个或多个文件的版本或整个提交。在git中,这称为签出那些文件或提交的文件。
  3. 查看备份的历史记录。在git中,您可以在称为日志的东西中查看此历史(简称开发历史,同时起到还原的作用)。
  4. 维护相关的提交序列,称为分支(branch)。
  5. 将一个分支中的更改合并到另一个分支中(简单理解就是将不同版本的代码进行合并)。
  6. 不仅支持历史版本的修改,还能支持形式上的不同实现方案(Plan A B C D)同时推进。

2.2 Gitlet简介

  1. Gitlet支持多版本多分枝开发。
  2. Gitlet不支持删除commit,这样设计的目的是尽可能保证各个历史版本的完整性。
  3. Gitlet的结构比较像树形结构,但是Gitlet仅仅支持两个parents的树形结构。
  4. GItlet更像是一个本地Git版本管理工具,将代码推送到远程分支的功能可以自己感兴趣进行添加,在我的项目实现里面我添加了这个功能,因为是Git的忠实信徒(doge)。

三、框架设计

3.1 Blobs

将代码还有文件内容保存到一个一个blobs里面,不同版本的文件只需要通过指针指向该版本对应条件下存在的文件就可以实现版本回退和更换,CS61B的助教课中的Assistant画了一幅非常生动的图:
在这里插入图片描述
这幅图就非常直观表示出了不同分支内容和blobs之间的关系,图片中红色的就是blobs,对于项目中的每一个文件,都有一个blob保存这个文件。

3.2 Trees

因为要支持多版本的文件更新,所以使用树形结构,如果仅仅使用链表只能支持单个分支的形式。
在这里插入图片描述

3.3 Commits

将元数据、日志信息(正常commit -m后面加的东西)、还有相关指针信息
在这里插入图片描述
这张图我感觉很好地展示了commit和blobs之间的关系,可以看到blobs是真正存储数据的部分,commit通过存储对于数据存储块的引用(在C语言中就是指针)来实现版本追踪,同时很大程度上节省了空间,不用每次commit都将所有的数据单独保存成一个快照。

四、.Gitlet文件结构设计

4.1 .git文件架构

这里.Gitlet的设计可以参考.git文件夹的设计,在VSCode中打开隐藏的.git文件夹查看结构:
在这里插入图片描述
借用阿里的.git文件架构介绍:
在这里插入图片描述
如果想要一个纯净的.git文件参考一下用来写init函数,可以在powershell中找一个文件夹执行git init,于是乎得到以下文件:
在这里插入图片描述

4.1.1 重点介绍

index(VSCode中无法查看,会乱码)

保存暂存区的内容,可以简单理解成一个本地的temp文件,在推送至远端或者进行快照之前可供修改的部分

objects(VSCode中无法查看,会乱码)

这个是实现的重点,包含三个内容:

  • commits:元数据,commit指针以及整体commit的维护
  • trees:各个commit版本文件的存储结构(数据结构)
  • blobs:数据块

这个部分在GItlet中使用了Java的面向对象编程中常用的对象序列化和对象反序列化进行

4.1.2 其他文件大杂烩

  • COMMIT_EDITMSG:commit修改的最新的相关信息这里输出的commit内容刚好跟我最近一次提交的信息对应:在这里插入图片描述
  • config:设置远程仓库地址以及远程仓库分支、本地仓库分支

在这里插入图片描述

  • HEAD:保存了本地仓库的分支指针位置,在这个仓库中是master在这里插入图片描述
  • ORIG_HEAD:用哈希的方法随机生成的唯一标识码在这里插入图片描述
  • log :保存日志信息相关内容 - HEAD:保存所有的头部指针信息在这里插入图片描述 这里的matster文件保存了所有master分支提交的信息在这里插入图片描述- refs:保存本地分支和远程分支对应关系在这里插入图片描述

4.2 Gitlet文件架构设计

  • objects 文件夹:存储commit和blob对象,数据结构是tree(使用hashcode作为文件名) - commits文件夹:存储每一次commit信息- blobs文件夹:存储数据块
  • commit 文件:用sha1编码保存对commit的引用
  • blob文件:用sha1编码·1保存对blob的引用
  • remotes 存储远端分支内容,用于远程推送
  • HEAD文件: 存储当前分支的名称,默认为master分支,在init的时候生成
  • staging 存储缓存区内容(以Stage class的形式存储blob)
  • removedStage 存储被rm的文件(以Stage class的形式存储)

五、项目文件设计

在这里插入图片描述

  • Utils文件中是用于实现生成sha1标签值、对象序列化和反序列化、使用到的数据结构、常用的IO操作的辅助函数
  • Repository中定用于初始化的时候定义Gitlet项目文件夹的架构和文件初始化。
  • Commit中定义了git中比较重要的一个类commit的应用。

tips:

  1. 序列化的时间不能由于对象的内容大小而变化。

Repository类

init函数

publicvoidinit(){//create a new .gitlet directoryif(GITLET_DIR.exists()){System.out.println("A Gitlet version-control system already exists in the current directory.");return;}else{GITLET_DIR.mkdir();// set the visibility of the .gitlet directory to hiddenDosFileAttributeView fileAttributeView =Files.getFileAttributeView(GITLET_DIR.toPath(),DosFileAttributeView.class);try{
                fileAttributeView.setHidden(true);}catch(IOException e){thrownewRuntimeException(e);}//get the current timelong time =System.currentTimeMillis();//change the type of the time to a stringString timestamp =String.valueOf(time);//create the initial commitCommit initialCommit =newCommit("initial commit",null,timestamp,null);//set the branch to master by defult
            branch ="master";//create object area for commits areasFile object =join(GITLET_DIR,"object");//create subdirectories for commits areas and blobsFile commit =join(object,"commit");//create subdirectories for blobs areasFile blob =join(object,"blob");//create head area for commits areasFile head =join(GITLET_DIR,"HEAD");//init the head to masterwriteContents(head,"master");//create staging area for commitsFile staging =join(GITLET_DIR,"staging");//create the files above
            object.mkdir();
            commit.mkdir();
            blob.mkdir();
            staging.mkdir();}}
  1. 判断当前文件夹下是否存在.gitlet仓库,如果没有就新建一个,如果有就提示已经存在。
  2. 创建commit文件夹和blob文件夹,同时初始化一个initial commit对象到文件并保存到commit文件夹下
  3. 创建一个staging区域用于暂存commit的内容。

add函数

//add a copy of the files as currently exist to the stagging areapublicvoidadd(String filename){//if the current working version of the file is identical to the version in the current commit do not stage!//if the file does not exist at all,print an error messageif(!join(CWD, filename).exists()){System.out.println("File does not exist.");return;}//if the current working version of the file is identical to the version in the current commit, do not stage itif(join(GITLET_DIR,"object","commit","HEAD", filename).exists()){if(join(CWD, filename).equals(join(GITLET_DIR,"object","commit","HEAD", filename))){return;}}//if the file is not staged, add it to the staging areaif(!join(GITLET_DIR,"staging", filename).exists()){writeContents(join(GITLET_DIR,"staging", filename),readContentsAsString(join(CWD, filename)));}//if the file is already staged, overwrite the file in the staging area with the new versionelse{writeContents(join(GITLET_DIR,"staging", filename),readContentsAsString(join(CWD, filename)));}}
  1. 将指定的文件添加到暂存区中

commit函数

//commit the files in the staging areapublicvoidcommit(String message){//if no files have been staged, print an error messageif(join(GITLET_DIR,"stageing").list().length ==1){System.out.println("No changes added to the commit.");return;}//create a new commit object and set the parent commit//TODO: figure out how the blobs workCommit newCommit =newCommit(message,readObject(join(GITLET_DIR,"HEAD","HEAD"),Commit.class),String.valueOf(System.currentTimeMillis()),null);//create a new commit fileFile commitFile =join(GITLET_DIR,"object","commit", newCommit.getUID());//serialize the commit object and write it to the commit filewriteObject(commitFile, newCommit);//update the head to the new commitwriteContents(join(GITLET_DIR,"HEAD","HEAD"), newCommit.getUID());//clear the staging areafor(File file :join(GITLET_DIR,"staging").listFiles()){
            file.delete();}}
  1. 新建一个commit对象记录当前的项目状态。

rm函数

//remove the file from the staging areapublicvoidrm(String filename){//if the file is not staged, print an error messageif(!join(GITLET_DIR,"staging", filename).exists()){System.out.println("No reason to remove the file.");return;}//remove the file from the staging areajoin(GITLET_DIR,"staging", filename).delete();}
  1. 从暂存区中将已经添加的某个文件删除。

log函数

//print the commit history, but we only display the first parent commit links and ignore any second parent linkspublicvoidlog(){//get into the commit directoryFile commit =join(GITLET_DIR,"object","commit");//get the current commit and deserialize it to a commit objectFile currentCommit =join(commit,readContentsAsString(join(GITLET_DIR,"HEAD","HEAD")));Commit current =readObject(currentCommit,Commit.class);//print the commit historywhile(current !=null){System.out.println("===");System.out.println("commit "+ current.getUID());System.out.println("Date: "+ current.getTimestamp());System.out.println(current.getMessage());System.out.println();
            current = current.getParent();//implement the circular of the commit messages}}
  1. 从commit文件夹下获取当前分支的commit信息并打印在控制台上面。

global_log函数

//print the commit history of all commitspublicvoidglobal_log(){//get the global log of all commitsFile commit =join(GITLET_DIR,"object","commit");for(File file : commit.listFiles()){Commit current =readObject(file,Commit.class);System.out.println("===");System.out.println("commit "+ current.getUID());System.out.println("Date: "+ current.getTimestamp());System.out.println(current.getMessage());System.out.println();}}
  • 在全局范围内打印所有的commit信息。

find函数

//find the commit with the given messagepublicvoidfind(String message){//get the global log of all commitsFile commit =join(GITLET_DIR,"object","commit");for(File file : commit.listFiles()){Commit current =readObject(file,Commit.class);if(current.getMessage().equals(message)){System.out.println(current.getUID());//print the commit message by linereturn;}}}
  • 根据文件名找到相应的commit message

status函数

//print the status of the repositorypublicvoidstatus(){//get the current branchFile head =join(GITLET_DIR,"HEAD");String currentBranch =readContentsAsString(head);//print the current branchSystem.out.println("=== Branches ===");System.out.println("*"+ currentBranch);for(File file :join(GITLET_DIR,"object","commit").listFiles()){if(!file.getName().equals(currentBranch)){System.out.println(file.getName());}}System.out.println();//print the staged filesSystem.out.println("=== Staged Files ===");for(File file :join(GITLET_DIR,"staging").listFiles()){System.out.println(file.getName());}System.out.println();//print the removed filesSystem.out.println("=== Removed Files ===");for(File file :join(GITLET_DIR,"staging").listFiles()){System.out.println(file.getName());}System.out.println();//print the modified filesSystem.out.println("=== Modifications Not Staged For Commit ===");System.out.println();//print the untracked filesSystem.out.println("=== Untracked Files ===");System.out.println();}
  • 打印当前的暂存区的状态

checkout部分综合代码

//checkout the file from the current commitpublicvoidcheckout(String filename){//get the current commitFile commit =join(GITLET_DIR,"object","commit");File currentCommit =join(commit,readContentsAsString(join(GITLET_DIR,"HEAD")));Commit current =readObject(currentCommit,Commit.class);//get the file from the current commitFile file =join(GITLET_DIR,"object","blob", current.getUID(), filename);//copy the file to the current working directorywriteContents(join(CWD, filename),readContents(file));}//checkout the file from the commit idpublicvoidcheckout(String commitID,String filename){//get the commit from the commit idFile commit =join(GITLET_DIR,"object","commit");File currentCommit =join(commit, commitID);Commit current =readObject(currentCommit,Commit.class);//get the file from the commitFile file =join(GITLET_DIR,"object","blob", current.getUID(), filename);//copy the file to the current working directorywriteContents(join(CWD, filename),readContents(file));}//checkout the branchpublicvoidcheckoutBranch(String branchName){//get the current branchFile head =join(GITLET_DIR,"HEAD");String currentBranch =readContentsAsString(head);//if the branch does not exist, print an error messageif(!join(GITLET_DIR,"object","commit", branchName).exists()){System.out.println("No such branch exists.");return;}//if the branch is the current branch, print an error messageif(currentBranch.equals(branchName)){System.out.println("Already on the target branch, no need to change the branch");return;}//get the current commitFile commit =join(GITLET_DIR,"object","commit");File currentCommit =join(commit,readContentsAsString(join(GITLET_DIR,"HEAD")));Commit current =readObject(currentCommit,Commit.class);//get the branch commitFile branchCommit =join(commit, branchName);Commit branch =readObject(branchCommit,Commit.class);//get the files from the branch commitfor(File file :join(GITLET_DIR,"object","blob", branch.getUID()).listFiles()){//copy the file to the current working directorywriteContents(join(CWD, file.getName()),readContents(file));}//delete the files that are not in the branch commitfor(File file :join(CWD).listFiles()){if(!join(GITLET_DIR,"object","blob", branch.getUID(), file.getName()).exists()){
                file.delete();}}//update the head to the branch commitwriteContents(join(GITLET_DIR,"HEAD"), branchName);}
  • 分支切换以及状态更新

branch函数

//Description: Creates a new branch with the given name, and points it at the current head commit. A branch is nothing more than a name for a reference (a SHA-1 identifier) to a commit node. This command does NOT immediately switch to the newly created branch (just as in real Git). Before you ever call branch, your code should be running with a default branch called “master”.publicvoidbranch(String branchName){//get the current branchFile head =join(GITLET_DIR,"HEAD");String currentBranch =readContentsAsString(head);//if the branch already exists, print an error messageif(join(GITLET_DIR,"object","commit", branchName).exists()){System.out.println("A branch with that name already exists.");return;}//create a new branch commitFile commit =join(GITLET_DIR,"object","commit");File currentCommit =join(commit,readContentsAsString(join(GITLET_DIR,"HEAD")));Commit current =readObject(currentCommit,Commit.class);Commit newBranch =newCommit(current.getMessage(), current,String.valueOf(System.currentTimeMillis()),null);//create a new branch commit fileFile branchCommit =join(commit, branchName);//serialize the branch commit object and write it to the branch commit filewriteObject(branchCommit, newBranch);}
  • 创建一个新的branch

rm_branch函数

//remove the branchpublicvoidrm_branch(String branchName){//get the current branchFile head =join(GITLET_DIR,"HEAD");String currentBranch =readContentsAsString(head);//if the branch does not exist, print an error messageif(!join(GITLET_DIR,"object","commit", branchName).exists()){System.out.println("A branch with that name does not exist.");return;}//if the branch is the current branch, print an error messageif(currentBranch.equals(branchName)){System.out.println("Cannot remove the current branch.");return;}//remove the branchjoin(GITLET_DIR,"object","commit", branchName).delete();}
  • 删除一个分支

reset函数

//reset the commit header to the given commitpublicvoidreset(String commitID){//get the commit from the commit idFile commit =join(GITLET_DIR,"object","commit");File currentCommit =join(commit, commitID);Commit current =readObject(currentCommit,Commit.class);//get the files from the commitfor(File file :join(GITLET_DIR,"object","blob", current.getUID()).listFiles()){//copy the file to the current working directorywriteContents(join(CWD, file.getName()),readContents(file));}//delete the files that are not in the commitfor(File file :join(CWD).listFiles()){if(!join(GITLET_DIR,"object","blob", current.getUID(), file.getName()).exists()){
                file.delete();}}//update the head to the commitwriteContents(join(GITLET_DIR,"HEAD"), commitID);}

merge函数

//merge the branch with the current branchpublicvoidmerge(String branchName){//get the current branchFile head =join(GITLET_DIR,"HEAD");String currentBranch =readContentsAsString(head);//if the branch does not exist, print an error messageif(!join(GITLET_DIR,"object","commit", branchName).exists()){System.out.println("A branch with that name does not exist.");return;}//if the branch is the current branch, print an error messageif(currentBranch.equals(branchName)){System.out.println("Cannot merge a branch with itself.");return;}//get the current commitFile commit =join(GITLET_DIR,"object","commit");File currentCommit =join(commit,readContentsAsString(join(GITLET_DIR,"HEAD")));Commit current =readObject(currentCommit,Commit.class);//get the branch commitFile branchCommit =join(commit, branchName);Commit branch =readObject(branchCommit,Commit.class);//get the split point commitCommit splitPoint =findSplitPoint(current, branch);//get the files from the split point commitfor(File file :join(GITLET_DIR,"object","blob", splitPoint.getUID()).listFiles()){//copy the file to the current working directorywriteContents(join(CWD, file.getName()),readContents(file));}//get the files from the branch commitfor(File file :join(GITLET_DIR,"object","blob", branch.getUID()).listFiles()){//if the file is not in the split point commit, copy the file to the current working directoryif(!join(GITLET_DIR,"object","blob", splitPoint.getUID(), file.getName()).exists()){writeContents(join(CWD, file.getName()),readContents(file));}}//get the files from the current commitfor(File file :join(GITLET_DIR,"object","blob", current.getUID()).listFiles()){//if the file is not in the split point commit, copy the file to the current working directoryif(!join(GITLET_DIR,"object","blob", splitPoint.getUID(), file.getName()).exists()){writeContents(join(CWD, file.getName()),readContents(file));}}}

本文转载自: https://blog.csdn.net/weixin_73074012/article/details/135774539
版权归原作者 ^Mark_Zhang^ 所有, 如有侵权,请联系我们删除。

“CS61B sp21fall Project02 Gitlet”的评论:

还没有评论