赞
踩
这个项目将会带着我们一同手写实现一个Git版本管理工具,事件性和趣味性非常强,想象一下自己可以使用自己写的代码管理工具Gitlet,想想都爽。
将代码还有文件内容保存到一个一个blobs里面,不同版本的文件只需要通过指针指向该版本对应条件下存在的文件就可以实现版本回退和更换,CS61B的助教课中的Assistant画了一幅非常生动的图:
这幅图就非常直观表示出了不同分支内容和blobs之间的关系,图片中红色的就是blobs,对于项目中的每一个文件,都有一个blob保存这个文件。
因为要支持多版本的文件更新,所以使用树形结构,如果仅仅使用链表只能支持单个分支的形式。
将元数据、日志信息(正常commit -m后面加的东西)、还有相关指针信息
这张图我感觉很好地展示了commit和blobs之间的关系,可以看到blobs是真正存储数据的部分,commit通过存储对于数据存储块的引用(在C语言中就是指针)来实现版本追踪,同时很大程度上节省了空间,不用每次commit都将所有的数据单独保存成一个快照。
这里.Gitlet的设计可以参考.git文件夹的设计,在VSCode中打开隐藏的.git文件夹查看结构:
借用阿里的.git文件架构介绍:
如果想要一个纯净的.git文件参考一下用来写init函数,可以在powershell中找一个文件夹执行git init,于是乎得到以下文件:
保存暂存区的内容,可以简单理解成一个本地的temp文件,在推送至远端或者进行快照之前可供修改的部分
这个是实现的重点,包含三个内容:
这个部分在GItlet中使用了Java的面向对象编程中常用的对象序列化和对象反序列化进行
COMMIT_EDITMSG:commit修改的最新的相关信息
这里输出的commit内容刚好跟我最近一次提交的信息对应:
config:设置远程仓库地址以及远程仓库分支、本地仓库分支
tips:
public void init() { //create a new .gitlet directory if (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 hidden DosFileAttributeView fileAttributeView = Files.getFileAttributeView(GITLET_DIR.toPath(), DosFileAttributeView.class); try { fileAttributeView.setHidden(true); } catch (IOException e) { throw new RuntimeException(e); } //get the current time long time = System.currentTimeMillis(); //change the type of the time to a string String timestamp = String.valueOf(time); //create the initial commit Commit initialCommit = new Commit("initial commit", null,timestamp, null); //set the branch to master by defult branch = "master"; //create object area for commits areas File object = join(GITLET_DIR, "object"); //create subdirectories for commits areas and blobs File commit = join(object, "commit"); //create subdirectories for blobs areas File blob = join(object, "blob"); //create head area for commits areas File head = join(GITLET_DIR, "HEAD"); //init the head to master writeContents(head, "master"); //create staging area for commits File staging = join(GITLET_DIR, "staging"); //create the files above object.mkdir(); commit.mkdir(); blob.mkdir(); staging.mkdir(); } }
//add a copy of the files as currently exist to the stagging area public void add(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 message if (!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 it if (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 area if (!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 version else { writeContents(join(GITLET_DIR, "staging", filename), readContentsAsString(join(CWD, filename))); } }
//commit the files in the staging area public void commit(String message) { //if no files have been staged, print an error message if (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 work Commit newCommit = new Commit(message, readObject(join(GITLET_DIR, "HEAD", "HEAD"), Commit.class), String.valueOf(System.currentTimeMillis()), null); //create a new commit file File commitFile = join(GITLET_DIR, "object", "commit", newCommit.getUID()); //serialize the commit object and write it to the commit file writeObject(commitFile, newCommit); //update the head to the new commit writeContents(join(GITLET_DIR, "HEAD", "HEAD"), newCommit.getUID()); //clear the staging area for (File file : join(GITLET_DIR, "staging").listFiles()) { file.delete(); } }
//remove the file from the staging area
public void rm(String filename) {
//if the file is not staged, print an error message
if (!join(GITLET_DIR, "staging", filename).exists()) {
System.out.println("No reason to remove the file.");
return;
}
//remove the file from the staging area
join(GITLET_DIR, "staging", filename).delete();
}
//print the commit history, but we only display the first parent commit links and ignore any second parent links public void log() { //get into the commit directory File commit = join(GITLET_DIR, "object", "commit"); //get the current commit and deserialize it to a commit object File currentCommit = join(commit, readContentsAsString(join(GITLET_DIR, "HEAD", "HEAD"))); Commit current = readObject(currentCommit, Commit.class); //print the commit history while (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 } }
//print the commit history of all commits
public void global_log() {
//get the global log of all commits
File 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();
}
}
//find the commit with the given message
public void find(String message) {
//get the global log of all commits
File 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 line
return;
}
}
}
//print the status of the repository public void status() { //get the current branch File head = join(GITLET_DIR, "HEAD"); String currentBranch = readContentsAsString(head); //print the current branch System.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 files System.out.println("=== Staged Files ==="); for (File file : join(GITLET_DIR, "staging").listFiles()) { System.out.println(file.getName()); } System.out.println(); //print the removed files System.out.println("=== Removed Files ==="); for (File file : join(GITLET_DIR, "staging").listFiles()) { System.out.println(file.getName()); } System.out.println(); //print the modified files System.out.println("=== Modifications Not Staged For Commit ==="); System.out.println(); //print the untracked files System.out.println("=== Untracked Files ==="); System.out.println(); }
//checkout the file from the current commit public void checkout(String filename) { //get the current commit File 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 commit File file = join(GITLET_DIR, "object", "blob", current.getUID(), filename); //copy the file to the current working directory writeContents(join(CWD, filename), readContents(file)); } //checkout the file from the commit id public void checkout(String commitID, String filename) { //get the commit from the commit id File commit = join(GITLET_DIR, "object", "commit"); File currentCommit = join(commit, commitID); Commit current = readObject(currentCommit, Commit.class); //get the file from the commit File file = join(GITLET_DIR, "object", "blob", current.getUID(), filename); //copy the file to the current working directory writeContents(join(CWD, filename), readContents(file)); } //checkout the branch public void checkoutBranch(String branchName) { //get the current branch File head = join(GITLET_DIR, "HEAD"); String currentBranch = readContentsAsString(head); //if the branch does not exist, print an error message if (!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 message if (currentBranch.equals(branchName)) { System.out.println("Already on the target branch, no need to change the branch"); return; } //get the current commit File commit = join(GITLET_DIR, "object", "commit"); File currentCommit = join(commit, readContentsAsString(join(GITLET_DIR, "HEAD"))); Commit current = readObject(currentCommit, Commit.class); //get the branch commit File branchCommit = join(commit, branchName); Commit branch = readObject(branchCommit, Commit.class); //get the files from the branch commit for (File file : join(GITLET_DIR, "object", "blob", branch.getUID()).listFiles()) { //copy the file to the current working directory writeContents(join(CWD, file.getName()), readContents(file)); } //delete the files that are not in the branch commit for (File file : join(CWD).listFiles()) { if (!join(GITLET_DIR, "object", "blob", branch.getUID(), file.getName()).exists()) { file.delete(); } } //update the head to the branch commit writeContents(join(GITLET_DIR, "HEAD"), branchName); }
//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”. public void branch(String branchName) { //get the current branch File head = join(GITLET_DIR, "HEAD"); String currentBranch = readContentsAsString(head); //if the branch already exists, print an error message if (join(GITLET_DIR, "object", "commit", branchName).exists()) { System.out.println("A branch with that name already exists."); return; } //create a new branch commit File commit = join(GITLET_DIR, "object", "commit"); File currentCommit = join(commit, readContentsAsString(join(GITLET_DIR, "HEAD"))); Commit current = readObject(currentCommit, Commit.class); Commit newBranch = new Commit(current.getMessage(), current, String.valueOf(System.currentTimeMillis()), null); //create a new branch commit file File branchCommit = join(commit, branchName); //serialize the branch commit object and write it to the branch commit file writeObject(branchCommit, newBranch); }
//remove the branch public void rm_branch(String branchName) { //get the current branch File head = join(GITLET_DIR, "HEAD"); String currentBranch = readContentsAsString(head); //if the branch does not exist, print an error message if (!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 message if (currentBranch.equals(branchName)) { System.out.println("Cannot remove the current branch."); return; } //remove the branch join(GITLET_DIR, "object", "commit", branchName).delete(); }
//reset the commit header to the given commit public void reset(String commitID) { //get the commit from the commit id File commit = join(GITLET_DIR, "object", "commit"); File currentCommit = join(commit, commitID); Commit current = readObject(currentCommit, Commit.class); //get the files from the commit for (File file : join(GITLET_DIR, "object", "blob", current.getUID()).listFiles()) { //copy the file to the current working directory writeContents(join(CWD, file.getName()), readContents(file)); } //delete the files that are not in the commit for (File file : join(CWD).listFiles()) { if (!join(GITLET_DIR, "object", "blob", current.getUID(), file.getName()).exists()) { file.delete(); } } //update the head to the commit writeContents(join(GITLET_DIR, "HEAD"), commitID); }
//merge the branch with the current branch public void merge(String branchName) { //get the current branch File head = join(GITLET_DIR, "HEAD"); String currentBranch = readContentsAsString(head); //if the branch does not exist, print an error message if (!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 message if (currentBranch.equals(branchName)) { System.out.println("Cannot merge a branch with itself."); return; } //get the current commit File commit = join(GITLET_DIR, "object", "commit"); File currentCommit = join(commit, readContentsAsString(join(GITLET_DIR, "HEAD"))); Commit current = readObject(currentCommit, Commit.class); //get the branch commit File branchCommit = join(commit, branchName); Commit branch = readObject(branchCommit, Commit.class); //get the split point commit Commit splitPoint = findSplitPoint(current, branch); //get the files from the split point commit for (File file : join(GITLET_DIR, "object", "blob", splitPoint.getUID()).listFiles()) { //copy the file to the current working directory writeContents(join(CWD, file.getName()), readContents(file)); } //get the files from the branch commit for (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 directory if (!join(GITLET_DIR, "object", "blob", splitPoint.getUID(), file.getName()).exists()) { writeContents(join(CWD, file.getName()), readContents(file)); } } //get the files from the current commit for (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 directory if (!join(GITLET_DIR, "object", "blob", splitPoint.getUID(), file.getName()).exists()) { writeContents(join(CWD, file.getName()), readContents(file)); } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。