除了ec外全部为完成,评分1440(-160的style)
public static final File CWD = new File(System.getProperty("user.dir"));
/**
* The .gitlet directory.
*/
public static final File GITLET_DIR = join(CWD, ".gitlet");
public static final File OBJECTS_DIR = join(GITLET_DIR, "objects");
public static final File COMMIT_DIR = join(OBJECTS_DIR, "commit");
public static final File BLOB_DIR = join(OBJECTS_DIR, "blob");
public static final File REFS_DIR = join(GITLET_DIR, "refs");
public static final File HEADS_DIR = join(REFS_DIR, "heads");
public static final File MASTER_DIR = join(HEADS_DIR, "master");
public static final File ADDSTAGE_DIR = join(GITLET_DIR, "addstage");
public static final File REMOVESATGE_DIR = join(GITLET_DIR, "removestage");
public static final File HEAD_FILE = join(GITLET_DIR, "HEAD");
public static final File REMOTES_DIR = join(GITLET_DIR, "remotes");
public static File currentBranch = join(REFS_DIR, "currentBranch");
public static boolean is_changed;
/* TODO: fill in the rest of this class. */
public static void init() {
if (!GITLET_DIR.exists()) {
GITLET_DIR.mkdir();
OBJECTS_DIR.mkdir();
COMMIT_DIR.mkdir();
BLOB_DIR.mkdir();
REFS_DIR.mkdir();
HEADS_DIR.mkdir();
REMOTES_DIR.mkdir();
try {
MASTER_DIR.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
ADDSTAGE_DIR.mkdir();
REMOVESATGE_DIR.mkdir();
try {
HEAD_FILE.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
currentBranch.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
writeObject(currentBranch, MASTER_DIR);
Commit commit = new Commit();
commit.initCommit();
} else {
System.out.println("A Gitlet version-control system already exists in the current directory.");
}
}
这是init的代码,初始化创建一大堆文件夹和一些文件,其中HEAD文件为头指针,存放最新的commit的hashID,heads里存放分支,比如初始分支master,或者你后来通过branch方法创建的分支,都放在heads里,currentBranch是我额外创建的一个文件,在refs下,和heads同目录,这个文件里存放的是当前的分支的文件对象,不过我看别人似乎都没有维护这么一个文件。
在初始化时,还要添加一个初始的commit到objects里的commit里,这个初始commit很简单,通过writeobject往里面写commit类就行。
Commit类存储的是commit的信息,commit里记得要存blob的id;还要存父commit,这里别学我直接存父commit类了,要存父commit的id,虽然写起来方便点,但是每次commit会把以前所有的commit全部迭代然后塞在最新的commit里面,很臃肿。
blob类是每个add过的工作目录中的文件生成的blob文件,文件名我直接用了blob的hash id
repository里是主要你要写的东西,各种方法都写在这里;
Main方法里挺简单的,我就不贴了
另外提供一下merge思路,我是遍历了得到所有需要改动的文件名,然后分析每一个文件名在当前分支的头提交,给定分支的头提交,分割点的提交中的状态,最后那些one,two,three什么的可读性很差是吧,因为我是根据知乎的gitlet指南里分的七种情况挨个讨论if else的,直接去知乎搜gitlet指南就行。根据不同的情况干不同的事情。
另外再给点关于评分测试的坑,1.要注意报错信息一定要和文档里给的一模一样,不能错一点,在merge no conflict 测试里,最后一个 "."不能缺少,不然会报错,你的log,status,global-
log报错很大可能是格式不对
2.你的某个方法错了不一定就是那个方法错了,还有可能是同一测试里的其他方法错了导致的报错。注意看报错信息
3.报错信息要从下往上看,set up 1 ,set up 2 然后test这样,最下面那个是你的报错信息输出,最上面的是在测试的哪行报错了。
4. “*”这个符号代表检测文件应该被删掉,没有删掉会报错,“=”代表这个文件应该存在,不存在会报错
后面我懒得打了,直接贴源代码吧,我的代码只提供一点思路,没做过的小伙伴不要抄,一是我的代码写比较史山,二是代码逻辑基本上不一样。
最后,这个proj的难度还是不小的,我做了大概40-50小时,但是坚持就是胜利,祝成功!
package gitlet;
// TODO: any imports you need here
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import static gitlet.Utils.*;
/** Represents a gitlet commit object.
* TODO: It's a good idea to give a description here of what else this Class
* does at a high level.
*
* @author TODO
*/
public class Commit implements Serializable {
/**
* TODO: add instance variables here.
*
* List all instance variables of the Commit class here with a useful
* comment above them describing what that variable represents and how that
* variable is used. We've provided one example for `message`.
*/
/** The message of this Commit. */
public String message;
public List<Commit> parents;
public String author;
public String timestamp;
public String ID;
public String fileName;
public Date currentDate;
public Map<String,String> pathToBlobID ;
public ArrayList<String> blobID;
public File branch;
public Map<String, String> fileMap; // 文件到Blob的映射 (文件名 -> Blob ID)
// 获取当前提交的文件树
public Map<String, String> getFileMap() {
return fileMap;
}
Commit(){}
Commit (String message, List<Commit> parents,String timestamp,Map<String,String> pathToBlobID ) {
this.message = message;
this.parents = parents;
this.timestamp = timestamp;
this.pathToBlobID = pathToBlobID;
}
Commit (String message, List<Commit> parents,Date currentDate,Map<String,String> pathToBlobID ) {//用于生成hash id
this.message = message;
this.parents = parents;
this.currentDate = currentDate;
this.pathToBlobID = pathToBlobID;
}
Commit (String message, List<Commit> parents,Date currentDate,ArrayList<String> blobID ) {//用于生成hash id
this.message = message;
this.parents = parents;
this.currentDate = currentDate;
this.blobID = blobID;
}
Commit (String message, List<Commit> parents,String timestamp,Map<String,String> pathToBlobID ,String ID,String author,String fileName) {
this.message = message;
this.parents = parents;
this.timestamp = timestamp;
this.pathToBlobID = pathToBlobID;
this.ID = ID;
this.author = author;
this.fileName = fileName;
}
Commit (String message, List<Commit> parents,String timestamp,ArrayList<String> blobID ,String ID,String author,String fileName, File branch,Map<String, String> fileMap) {
this.message = message;
this.parents = parents;
this.timestamp = timestamp;
this.blobID = blobID;
this.ID = ID;
this.author = author;
this.fileName = fileName;
this.branch = branch;
this.fileMap = fileMap;
}
public static String dateToTimeStamp(Date date) {
DateFormat dateFormat = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy Z", Locale.US);
return dateFormat.format(date);
}
public String generatelID(){
return Utils.sha1(dateToTimeStamp(currentDate), message, parents.toString());
}
public void initCommit() {
this.currentDate=new Date(0);
Commit commit = new Commit("initial commit", new ArrayList<>(),currentDate,new HashMap<>());
String initCommitHashID = commit.generatelID();
Commit initCommit =new Commit("initial commit", new ArrayList<>(), dateToTimeStamp(currentDate),new HashMap<>(),initCommitHashID,"","initCommit");
File f = join(Repository.COMMIT_DIR, initCommitHashID);
try {
f.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
writeObject(f, initCommit);//把initcommit对象写入文件
writeContents(Repository.HEAD_FILE, initCommitHashID);//把头指针指向初始化的commit
writeContents(Repository.MASTER_DIR, initCommitHashID);
}
}
package gitlet;
import java.io.File;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
public class Blob implements Serializable {
private String ID;
public String fileName;
private String filePath;
public byte[] fileContent;
Blob() {}
Blob(String fileName, String filePath,String ID,byte[] fileContent) {
this.fileName = fileName;
this.filePath = filePath;
this.ID = ID;
this.fileContent = fileContent;
}
public String getFileName(){
return fileName;
}
public String getFilePath(){
return filePath;
}
public String getID(){
return ID;
}
public String getFileContent(){
return new String(fileContent, StandardCharsets.UTF_8);
}
public String generatelID(byte[] fileContent,String fileName){
return Utils.sha1(fileContent,fileName);
}
//tttt
}
ackage gitlet;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.PosixFileAttributes;
import java.time.LocalDateTime;
import java.util.*;
import static gitlet.Utils.*;
// TODO: any imports you need here
/** Represents a gitlet repository.
* TODO: It's a good idea to give a description here of what else this Class
* does at a high level.
*
* @author TODO
*/
public class Repository {
/**
* TODO: add instance variables here.
*
* List all instance variables of the Repository class here with a useful
* comment above them describing what that variable represents and how that
* variable is used. We've provided two examples for you.
*/
/**
* The current working directory.
*/
public static final File CWD = new File(System.getProperty("user.dir"));
/**
* The .gitlet directory.
*/
public static final File GITLET_DIR = join(CWD, ".gitlet");
public static final File OBJECTS_DIR = join(GITLET_DIR, "objects");
public static final File COMMIT_DIR = join(OBJECTS_DIR, "commit");
public static final File BLOB_DIR = join(OBJECTS_DIR, "blob");
public static final File REFS_DIR = join(GITLET_DIR, "refs");
public static final File HEADS_DIR = join(REFS_DIR, "heads");
public static final File MASTER_DIR = join(HEADS_DIR, "master");
public static final File ADDSTAGE_DIR = join(GITLET_DIR, "addstage");
public static final File REMOVESATGE_DIR = join(GITLET_DIR, "removestage");
public static final File HEAD_FILE = join(GITLET_DIR, "HEAD");
public static File currentBranch = join(REFS_DIR, "currentBranch");
public static boolean is_changed;
/* TODO: fill in the rest of this class. */
public static void init() {
if (!GITLET_DIR.exists()) {
GITLET_DIR.mkdir();
OBJECTS_DIR.mkdir();
COMMIT_DIR.mkdir();
BLOB_DIR.mkdir();
REFS_DIR.mkdir();
HEADS_DIR.mkdir();
try {
MASTER_DIR.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
ADDSTAGE_DIR.mkdir();
REMOVESATGE_DIR.mkdir();
try {
HEAD_FILE.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
currentBranch.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
writeObject(currentBranch, MASTER_DIR);
Commit commit = new Commit();
commit.initCommit();
} else {
System.out.println("A Gitlet version-control system already exists in the current directory.");
}
}
public static boolean isInitialized() {
if (!GITLET_DIR.exists())
return false;
return true;
}
private static String findFileRecursively(final File directory, final String fileName) {
// 列出目录中的所有文件和子目录
final File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
// 如果是文件且文件名匹配,则打印文件路径并返回
if (file.isFile() && file.getName().equalsIgnoreCase(fileName)) {
return file.getAbsolutePath();
}
// 如果是子目录,则递归查找并检查返回值
else if (file.isDirectory()) {
String result = findFileRecursively(file, fileName);
if (result != null) {
return result; // 如果递归调用找到了文件,则返回其路径
}
}
}
}
// 如果没有找到文件,则返回null
return null;
}
public static void add(String filename) {
String filePath = findFileRecursively(CWD, filename);
Commit commit = getCommitFromHead();
if (filePath == null) {
System.out.println("File does not exist.");
} else {
File file = new File(filePath);
byte[] contents = readContents(file);
String fileName = file.getName();
Blob blob0 = new Blob();
String hashBlobID = blob0.generatelID(contents, fileName);//得到hash id
if (commit.blobID != null) {
if (commit.blobID.contains(hashBlobID)) { //如果两次add的文件完全一致,则不添加
return;
}
}
File removeFile = join(REMOVESATGE_DIR, filename);
if (removeFile.exists()) {
if (removeFile.getName().equals(filename)) { //如果文件名相同就删除
removeFile.delete();
//System.out.println("dddd");
return;
}
}
File f4 = join(BLOB_DIR, hashBlobID);//新建blob目录下的储存blob的文件,名字使用hash id
try {
f4.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
Blob blob = new Blob(filename, filePath, hashBlobID, contents); //新加的blob
writeObject(f4, blob);//把blob对象写入blob文件
File f5 = join(ADDSTAGE_DIR, filename);
//如果版本相同,不添加,版本不同,添加
if (!f5.exists()) { //哈希值不同
try {
f5.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
writeContents(f5, hashBlobID);//把hash写入存在addstage文件里的blob
} else { //哈希值相同
writeContents(f5, hashBlobID);
}
}
}
public static void commit(String message) {
Date date = new Date();
ArrayList<Commit> parents = new ArrayList<>();
parents.add(readObject(join(COMMIT_DIR, readContentsAsString(Repository.HEAD_FILE)), Commit.class));//把头指针中指向的commit文件加为父提交
ArrayList<String> blobIDList = new ArrayList<>();
List<String> stagefileNames = Utils.plainFilenamesIn(ADDSTAGE_DIR);//从addstage暂存区中提取所有的文件名
List<String> removeFileNames = Utils.plainFilenamesIn(REMOVESATGE_DIR);//从removestage暂存区中提取所有的文件名
assert stagefileNames != null;
assert removeFileNames != null;
if (stagefileNames.isEmpty() && removeFileNames.isEmpty()) {
System.out.println("No changes added to the commit.");
return;
}
// readContentsAsString(join(REMOVESATGE_DIR,removeFileName)).equals(headCommitblobID)
Commit headcommit = getCommitFromHead();//先把headcommit里的所有blob id加进来,除了addstage里有的
List<String> headCommitblobIDList = headcommit.blobID;
List<String> removeFileNameList = Utils.plainFilenamesIn(REMOVESATGE_DIR);
if (headCommitblobIDList != null) {
headCommitblobIDList.stream()
.filter(headCommitblobID -> {
try {
Blob blob = readObject(join(BLOB_DIR, headCommitblobID), Blob.class);
boolean shouldAdd = !stagefileNames.contains(blob.fileName);
if (shouldAdd) {
shouldAdd = removeFileNameList.stream()
.noneMatch(removeFileName -> readContentsAsString(join(REMOVESATGE_DIR, removeFileName)).equals(headCommitblobID));
}
return shouldAdd;
} catch (Exception e) {
// 处理异常,例如记录日志
System.err.println("Error processing headCommitblobID: " + headCommitblobID + ", error: " + e.getMessage());
return false;
}
})
.forEach(blobIDList::add);
}
if (!stagefileNames.isEmpty()) {
for (int i = 0; i < stagefileNames.size(); i++) { //再把addstage里存在的blob id加进来
String fileName = stagefileNames.get(i);
File file3 = join(ADDSTAGE_DIR, fileName); //addstage目录下的文件
String hashBlobID = readContentsAsString(file3);
blobIDList.add(hashBlobID); //在commit文件里存放addstage中的blob id
file3.delete(); //删除addstage中的内容
}
}
Commit commit = new Commit(message, parents, date, blobIDList);//创建新的commit,作用是生成hashid
String commitHashID = commit.generatelID();
Map<String, String> fileMap = new HashMap<>();
for (String blobID : blobIDList) {
Blob blob = readObject(join(BLOB_DIR, blobID), Blob.class);
fileMap.put(blob.fileName, blobID);
}
for (String file:removeFileNameList) {
String con = readContentsAsString(join(REMOVESATGE_DIR, file));
blobIDList.remove(con);
}
Commit commit2 = new Commit(message, parents, Commit.dateToTimeStamp(date), blobIDList, commitHashID, "", commitHashID, readObject(currentBranch, File.class), fileMap);//填入所有commit信息
File f2 = join(COMMIT_DIR, commitHashID);//commit的文件名使用hash id
//填入所有commit信息
deleteAllFiles(REMOVESATGE_DIR); //清空removestage
try {
f2.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
writeObject(f2, commit2);
writeContents(Repository.HEAD_FILE, commitHashID);//把头指针指向commit
//writeContents(readObject(currentBranch,File.class), commitHashID);//当前分支指向head//TODO
writeContents(readObject(currentBranch, File.class), commitHashID); //当前分支指向head
List<String> branchNames = new ArrayList<>(plainFilenamesIn(HEADS_DIR));
branchNames.remove("master");
}
public static void rm2(String filename) {
//if the file is not staged, print an error message
File f = join(REMOVESATGE_DIR, filename);
//remove the file from the staging area
String filePath = findFileRecursively(CWD, filename);
if (filePath == null) {
try {
f.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
return;
}
File file = new File(filePath);
byte[] contents = readContents(file);
String workfileName = file.getName();
Blob blob0 = new Blob();
String hashBlobID = blob0.generatelID(contents, workfileName);//得到工作目录中文件的hash id
Commit headCommit = getCommitFromHead();
if (join(ADDSTAGE_DIR, filename).exists()) {
join(ADDSTAGE_DIR, filename).delete();
return;
}
if (headCommit.blobID != null) {
if (headCommit.blobID.contains(hashBlobID)) {
file.delete();
if (join(ADDSTAGE_DIR, filename).exists()) {
join(ADDSTAGE_DIR, filename).delete();
}
try {
f.createNewFile(); //从工作目录和stage中都删除,并且放入removestage
} catch (IOException e) {
throw new RuntimeException(e);
}
writeContents(f, hashBlobID); //removestage中的文件存放hash id
}
} else {
System.out.println("No reason to remove the file.");
}
}
public static void log2() {
String commitHashID = readContentsAsString(Repository.HEAD_FILE);
File f = join(COMMIT_DIR, commitHashID);//头指针指向的commit
Commit commit = readObject(f, Commit.class);
while (!commit.ID.equals(" ")) {
System.out.println("===");
System.out.println("commit " + commit.ID);
if( commit.parents.size() > 1){
System.out.println("Merge: " + commit.parents.get(0).ID.substring(0,7) + " " + commit.parents.get(1).ID.substring(0,7));
}
System.out.println("Date: " + commit.timestamp);
System.out.println(commit.message);
System.out.print("\n");
try {
commit = commit.parents.get(0);
} catch (IndexOutOfBoundsException e) {
break;
}
}
}
public static void log3() {
String commitHashID = readContentsAsString(Repository.HEAD_FILE);
File f = join(COMMIT_DIR, commitHashID);//头指针指向的commit
Commit commit = readObject(f, Commit.class);
while (!commit.ID.equals(" ")) {
System.out.println("===");
if( commit.parents.size() > 1){
System.out.println("commit " + commit.ID);
System.out.println("Merge: " + commit.parents.get(0).ID.substring(0,7) + " " + commit.parents.get(1).ID.substring(0,7));
System.out.println("Date: " + commit.timestamp);
System.out.println(commit.message);
System.out.print("\n");
return;
}
System.out.println("commit " + commit.ID);
//System.out.println("Merge: " + commit.parents.get(0).ID.substring(0,7) + " " + commit.parents.get(1).ID.substring(0,7));
System.out.println("Date: " + commit.timestamp);
System.out.println(commit.message);
System.out.print("\n");
try {
// File f2 = join(COMMIT_DIR, commit.parents.get(0));
// commit = readObject(f2, Commit.class);
commit = commit.parents.get(0);
} catch (IndexOutOfBoundsException e) {
break;
}
}
}
public static void log() {
// 获取当前分支的最新提交
Commit currentCommit = getCommitFromHead();
printCommitHistory(currentCommit, new HashSet<>());
}
private static void printCommitHistory(Commit commit, Set<String> visited) {
if (commit == null || visited.contains(commit.ID)) {
return;
}
// 标记当前提交为已访问
visited.add(commit.ID);
// 打印当前提交的信息
System.out.println("===");
System.out.println("commit " + commit.ID);
if (commit.parents.size() > 1) {
System.out.println("Merge: " + commit.parents.get(0).ID.substring(0, 7) + " " + commit.parents.get(1).ID.substring(0, 7));
}
System.out.println("Date: " + commit.timestamp);
System.out.println(commit.message);
System.out.println();
// 递归打印每个父提交的历史
for (Commit parent : commit.parents) {
printCommitHistory(parent, visited);
}
}
public static void globalLog() {
//String commitHashID = readContentsAsString(Repository.HEAD_FILE);
//File f = join(COMMIT_DIR, commitHashID);//头指针指向的commit
List<String> commitFileNameList = plainFilenamesIn(COMMIT_DIR);
for (String fileName : commitFileNameList) {
File f = join(COMMIT_DIR, fileName);
Commit commit = readObject(f, Commit.class);
System.out.println("===");
System.out.println("commit " + commit.ID);
System.out.println("Date: " + commit.timestamp);
System.out.println(commit.message);
System.out.print("\n");
}
}
public static void find(String message) {
int num = 0;
List<String> fileNames = Utils.plainFilenamesIn(COMMIT_DIR);
for (int i = 0; i < fileNames.size(); i++) {
String fileName = fileNames.get(i);
File f = join(COMMIT_DIR, fileName);
Commit commit = readObject(f, Commit.class);
if (commit.message.equals(message)) {
System.out.println(commit.ID);
num++;
}
}
if (num == 0) {
System.out.println("Found no commit with that message.");
}
}
public static void status() {
try {
MASTER_DIR.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
System.out.println("=== Branches ===");
List<String> branchNames = Utils.plainFilenamesIn(HEADS_DIR);
for (int i = 0; i < branchNames.size(); i++) {
String fileName = branchNames.get(i);
if (readObject(currentBranch, File.class).getName().equals(fileName)) {
System.out.println("*" + fileName);
} else {
System.out.println(fileName);
}
}
System.out.print("\n");
System.out.println("=== Staged Files ===");
List<String> stageFileNames = Utils.plainFilenamesIn(ADDSTAGE_DIR);
for (int i = 0; i < stageFileNames.size(); i++) {
String fileName = stageFileNames.get(i);
System.out.println(fileName);
}
System.out.print("\n");
System.out.println("=== Removed Files ===");
List<String> removeFileNames = Utils.plainFilenamesIn(REMOVESATGE_DIR);
for (int i = 0; i < removeFileNames.size(); i++) {
String fileName = removeFileNames.get(i);
System.out.println(fileName);
}
System.out.print("\n");
System.out.println("=== Modifications Not Staged For Commit ===");
System.out.print("\n");
System.out.println("=== Untracked Files ===");
}
public static List<String> modifedFile() {
Commit commit = getCommitFromHead();
if (commit == null||commit.blobID==null){return null;}
Blob blob = new Blob();
List<String> fileNames = Utils.plainFilenamesIn(CWD);
List<String> modifileNames = new ArrayList<>();
for (String fileName : fileNames) {
String filePath = findFileRecursively(CWD, fileName);
if (filePath == null) {
continue;
}
File file = new File(filePath);
byte[] fileContent = Utils.readContents(file);
String blobID = blob.generatelID(fileContent, fileName);
if (!commit.blobID.contains(blobID)) {
modifileNames.add(fileName);
}
}
return modifileNames;
}
public static List<String> deletedFile() {
Commit commit = getCommitFromHead();
if (commit == null||commit.fileMap==null){return null;}
List<String> fileNames = Utils.plainFilenamesIn(CWD);
Set<String> map = commit.fileMap.keySet();
assert fileNames != null;
fileNames.forEach(map::remove);
return fileNames;
}
public static Commit getCommitFromHead() {
String head = readContentsAsString(HEAD_FILE);
Commit commit = readObject(join(COMMIT_DIR, head), Commit.class);
return commit;
}
public static List<File> getBlobFileListFromCommit(Commit commit) {
List<File> blobFilesList = new ArrayList<>();
if (commit.blobID != null) {
for (String blobID : commit.blobID) {
blobFilesList.add(join(BLOB_DIR, blobID));
}
}
return blobFilesList;
}
public static void checkout(Commit commit, String fileName) { //把工作目录中的文件修改为commit里的
int a = 0;
String filePath = findFileRecursively(CWD, fileName);
if (filePath == null) {
System.out.println("File does not exist in that commit.");
return;
}
File f = new File(filePath); //要修改的文件,工作目录中
List<File> files = getBlobFileListFromCommit(commit);
for (File file : files) {
Blob blob = readObject(file, Blob.class);
if (blob.fileName.equals(fileName)) {
writeContents(f, new String(blob.fileContent, StandardCharsets.UTF_8));
a = 1;
}
}
if (a == 0) {
System.out.println("File does not exist in that commit.");
}
}
public static void checkout1(String fileName) { //只换指定的文件
Commit headCommit = getCommitFromHead();
if (headCommit.blobID.size() != 0) {
checkout(headCommit, fileName);
} else {
System.out.println("File does not exist in that commit.");
}
}
public static void checkout2(String ID, String fileName) {
List<String> commitfilesNames = plainFilenamesIn(COMMIT_DIR);
for (String commitfilesName : commitfilesNames) {
if (commitfilesName.substring(0, 8).equals(ID) || commitfilesName.equals(ID)) {
Commit commit = readObject(join(COMMIT_DIR, commitfilesName), Commit.class);
checkout(commit, fileName);
return;
}
}
System.out.println("No commit with that id exists.");
}
public static void checkout3(String branchName) {
File branch = join(HEADS_DIR, branchName); //提取分支指针指向的commit
if (!branch.exists()) {
System.out.println("No such branch exists.");
} else if (readObject(currentBranch, File.class).equals(branch)) {
System.out.println("No need to checkout the current branch.");
} else {
if (hasUntrackedFiles()) {
System.out.println("There is an untracked file in the way; delete it, or add and commit it first.");
return;
}
String headCommitID = readContentsAsString(branch); //提取分支指针指向的commit
Commit commit = readObject(join(COMMIT_DIR, headCommitID), Commit.class);//从分支指向的commit提取commit对象
//头指针指向分支指向的commit
List<File> blobfiles = getBlobFileListFromCommit(commit);
List<String> blobfileNames = new ArrayList<>();
for (File file5 : blobfiles) {
Blob blob = readObject(file5, Blob.class);
blobfileNames.add(blob.fileName);
}
File[] files = CWD.listFiles();
if (files != null) {
for (File file : files) {
String f = file.getName(); //工作目录中的文件
if (!blobfileNames.contains(f)) {
file.delete();
}
}
}
for (File blobfile : blobfiles) {
Blob blob = readObject(blobfile, Blob.class);
writeContents(join(CWD, blob.fileName), new String(blob.fileContent, StandardCharsets.UTF_8));//把当前分支中commit中的文件写入工作目录的同名文件
}
writeObject(currentBranch, branch);
writeContents(HEAD_FILE, headCommitID);
}
}
public static boolean hasUntrackedFiles() {
File testfile = join(CWD, "test");
File[] workingFiles = CWD.listFiles();
List<String> blobIDs = plainFilenamesIn(BLOB_DIR);
//System.out.println(blobIDs);
if (blobIDs == null) {
return false;
}
for (File file : workingFiles) {
if (file.isFile()) {
byte[] content = readContents(file);
String fileName = file.getName();
Blob blob = new Blob();
String blobID = blob.generatelID(content, fileName);
//System.out.println(fileName+" "+blobID);
if (!blobIDs.contains(blobID)) {
return true; // 发现未跟踪文件
}
}
}
return false; // 没有未跟踪文件
}
public static List<String> hasUntrackedFilesName() {
File[] workingFiles = CWD.listFiles();
List<String> blobIDs = plainFilenamesIn(BLOB_DIR);
List<String> fileNames = new ArrayList<>();
//System.out.println(blobIDs);
if (blobIDs == null) {
return null;
}
for (File file : workingFiles) {
if (file.isFile()) {
byte[] content = readContents(file);
String fileName = file.getName();
Blob blob = new Blob();
String blobID = blob.generatelID(content, fileName);
//System.out.println(fileName+" "+blobID);
if (!blobIDs.contains(blobID)) {
fileNames.add(fileName);
// 发现未跟踪文件
}
}
}
return fileNames; // 没有未跟踪文件
}
public static void branch(String branchName) {
File newBranch = join(HEADS_DIR, branchName);
if (newBranch.exists()) {
System.out.println("A branch with that name already exists.");
return;
}
try {
newBranch.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
writeContents(newBranch,readContentsAsString(readObject(currentBranch, File.class)));
}
public static void rm_branch(String branchName) {
File branch = join(HEADS_DIR, branchName);
if (!branch.exists()) {
System.out.println("A branch with that name does not exist.");
} else if (branch.equals(readObject(currentBranch, File.class))) {
System.out.println("Cannot remove the current branch.");
} else {
branch.delete();
}
}
public static void reset(String commitID) {
int i = 0;
List<String> commitfilesNames = plainFilenamesIn(COMMIT_DIR);
Commit commit = null;
for (String commitfileName : commitfilesNames) {
if (commitfileName.equals(commitID) || commitfileName.substring(0, 8).equals(commitID)) {
commit = readObject(join(COMMIT_DIR, commitfileName), Commit.class);
if (commit.branch.equals(readObject(currentBranch, File.class))) {
writeContents(HEAD_FILE, commitfileName);
writeContents(readObject(currentBranch, File.class), commitfileName);
} else {
writeContents(HEAD_FILE, commitfileName);
}
i = 1;
}
}
if (hasUntrackedFiles()) {
System.out.println("There is an untracked file in the way; delete it, or add and commit it first.");
return;
}
if (i == 0) {
System.out.println("No commit with that id exists.");
return;
}
//提取分支指针指向的commit
//从分支指向的commit提取commit对象
//头指针指向分支指向的commi
List<File> blobfiles = getBlobFileListFromCommit(commit);
List<String> blobfileNames = new ArrayList<String>();
for (File file5 : blobfiles) {
Blob blob = readObject(file5, Blob.class);
blobfileNames.add(blob.fileName);
}
List<String> stagefiles = plainFilenamesIn(ADDSTAGE_DIR);
for (String fileName : stagefiles) {
join(ADDSTAGE_DIR, fileName).delete();
}
File[] files = CWD.listFiles();
if (files != null) {
for (File file : files) {
String f = file.getName(); //工作目录中的文件
if (!blobfileNames.contains(f)) {
file.delete();
}
}
}
for (File blobfile : blobfiles) {
//System.out.println(file.getPath());
Blob blob = readObject(blobfile, Blob.class);
writeContents(join(CWD, blob.fileName), new String(blob.fileContent, StandardCharsets.UTF_8));//把当前分支中commit中的文件写入工作目录的同名文件
}
}
public static void deleteAllFiles(File dir) {
// 获取工作目录中的所有文件和子目录
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
file.delete();
}
}
}
// 获取文件在某个提交中的状态
public static FileStatus getFileStatus(Commit commit, Commit mergeBaseCommit, String fileName) {
if (commit.message.equals("initial commit")){
return new FileStatus(false, false, false, null);
}
// 获取当前提交中的文件映射(文件名 -> Blob ID)
Map<String, String> fileMap = commit.getFileMap();
// 如果文件不存在于当前提交中,返回不存在的状态
if (!fileMap.containsKey(fileName)) {
return new FileStatus(false, false, true, null); // 文件不存在,因此返回删除状态
}
// 获取文件的 Blob ID
String currentBlobId = fileMap.get(fileName);
// 获取分割点(merge base)提交的文件映射
Map<String, String> mergeBaseFileMap = mergeBaseCommit.getFileMap();
// 如果分割点中没有该文件,那么说明它是新增的
if (mergeBaseFileMap!=null) {
if (!mergeBaseFileMap.containsKey(fileName)) {
return new FileStatus(true, false, false, currentBlobId); // 文件是新增的
}
// 获取分割点中该文件的 Blob ID
String mergeBaseBlobId = mergeBaseFileMap.get(fileName);
// 如果当前提交中的 Blob ID 与分割点中的 Blob ID 不同,说明该文件在当前分支中被修改了
if (!mergeBaseBlobId.equals(currentBlobId)) {
return new FileStatus(true, true, false, currentBlobId); // 文件被修改了
}
}
// 如果当前提交中的 Blob ID 和分割点中的 Blob ID 相同,说明该文件在当前分支中没有修改
return new FileStatus(true, false, false, currentBlobId); // 文件未修改
}
public static void checkout4(Commit commit, String fileName) {
// 获取工作目录中的文件
File f = new File(fileName);
// 如果文件不存在,则创建新文件
if (!f.exists()) {
try {
f.createNewFile();
} catch (IOException e) {
throw new RuntimeException("Failed to create file: " + fileName, e);
}
}
// 从 commit 中获取所有 Blob 文件
List<File> files = getBlobFileListFromCommit(commit);
// 查找与 fileName 匹配的 Blob 文件
boolean found = false;
for (File blobFile : files) {
Blob blob = readObject(blobFile, Blob.class);
if (blob != null && blob.fileName.equals(fileName)) {
// 将 Blob 的内容写入工作目录中的文件
writeContents(f, new String(blob.fileContent, StandardCharsets.UTF_8));
found = true;
break; // 找到并写入文件后退出
}
}
// 如果没有找到匹配的 Blob 文件,打印错误信息
if (!found) {
System.out.println("File " + fileName + " not found in the commit.");
}
}
public static void resolveConflict(String fileName, Commit currentCommit, Commit targetCommit) {
System.out.println("Encountered a merge conflict.");
String currentContent = getFileContent(currentCommit, fileName);
String targetContent = getFileContent(targetCommit, fileName);
// 生成冲突标记
String conflictContent = "<<<<<<< HEAD\n" + currentContent +
"=======\n" + targetContent +
">>>>>>>\n";
// 将冲突文件内容写入工作目录
writeConflictToFile(fileName, conflictContent);
}
public static void writeConflictToFile(String fileName, String content) {
try {
Files.write(Paths.get(fileName), content.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
// 获取文件内容的辅助函数
public static String getFileContent(Commit commit, String fileName) {
// 这里返回的是文件在提交中的内容,可以通过 Blob ID 获取
String blobID = commit.getFileMap().get(fileName);
if (blobID == null){
return "";
}
File blobFile = join(BLOB_DIR, blobID);
Blob blob = readObject(blobFile, Blob.class);
return new String(blob.fileContent, StandardCharsets.UTF_8); // 假设 BlobManager 获取文件内容
}
public static void merge(String branchName) {
File givenBranchFile = join(HEADS_DIR, branchName);
File currentBranchFile = readObject(currentBranch, File.class);
List<String> stagedFiles = Utils.plainFilenamesIn(ADDSTAGE_DIR);
if (!stagedFiles.isEmpty()) {
System.out.println("You have uncommitted changes.");
return;
} else if (!givenBranchFile.exists()) {
System.out.println("A branch with that name does not exist.");
return;
} else if (givenBranchFile.equals(currentBranchFile)) {
System.out.println("Cannot merge a branch with itself.");
return;
} else if (hasUntrackedFiles()) {
System.out.println("There is an untracked file in the way; delete it, or add and commit it first.");
return;
}
String currentBranchCommitID = readContentsAsString(currentBranchFile);
Commit currentBranchCommit = readObject(join(COMMIT_DIR, currentBranchCommitID), Commit.class);
List<String> currentBranchCommitBlobIDList = currentBranchCommit.blobID;
Set<String> currentFiles = new HashSet<>();
for (String blobID : currentBranchCommitBlobIDList) {
currentFiles.add(readObject(join(BLOB_DIR, blobID), Blob.class).fileName);
}
// 合并这两个集合,得到所有需要合并的文件
Set<String> allFilesInMerge = new HashSet<>();
String givenBranchCommitID = readContentsAsString(givenBranchFile);
Commit givenBranchCommit = readObject(join(COMMIT_DIR, givenBranchCommitID), Commit.class);
List<String> givenBranchCommitBlobIDList = givenBranchCommit.blobID;
Set<String> targetFiles = new HashSet<>();
for (String blobID : givenBranchCommitBlobIDList) {
targetFiles.add(readObject(join(BLOB_DIR, blobID), Blob.class).fileName);
}
Set<String> splitFiles = new HashSet<>();
Commit splitPointCommit = findSplitPoint(currentBranchCommit, givenBranchCommit);
List<String> splitPointCommitBlobIDList = splitPointCommit.blobID;
if (splitPointCommitBlobIDList != null) {
for (String blobID : splitPointCommitBlobIDList) {
splitFiles.add(readObject(join(BLOB_DIR, blobID), Blob.class).fileName);
}
}
allFilesInMerge.addAll(splitFiles);
allFilesInMerge.addAll(targetFiles);
allFilesInMerge.addAll(currentFiles);
if (splitPointCommit != null) {
if (splitPointCommit.equals(givenBranchCommit)) {
System.out.println("Given branch is an ancestor of the current branch.");
} else if (splitPointCommit.ID.equals(currentBranchCommit.ID)) {
checkout3(branchName); //切换分支,当前分支会被改写为给定分支
writeContents(HEAD_FILE, givenBranchCommitID); //头指针改为给定分支commit
writeContents(readObject(currentBranch,File.class), givenBranchCommitID);//当前分支指向给定分支commit
writeObject(currentBranch,currentBranchFile);//把当前分支
System.out.println("Current branch fast-forwarded.");
} else {
ArrayList<String> blobIDList = new ArrayList<>();
Map<String, String> fileMap = new HashMap<>();
for (String fileName : allFilesInMerge) {
FileStatus currentStatus = getFileStatus(currentBranchCommit, splitPointCommit, fileName);
FileStatus targetStatus = getFileStatus(givenBranchCommit, splitPointCommit, fileName);
FileStatus mergeBaseStatus = getFileStatus(splitPointCommit, splitPointCommit, fileName);
if (One(currentStatus, targetStatus, mergeBaseStatus)) {
checkout4(givenBranchCommit, fileName);
blobIDList.add(targetStatus.getBlobId());
fileMap.put(fileName, targetStatus.getBlobId());
} else if (Two(currentStatus, targetStatus, mergeBaseStatus)) {
blobIDList.add(currentStatus.getBlobId());
fileMap.put(fileName, currentStatus.getBlobId());
} else if (Three1(currentStatus, targetStatus, mergeBaseStatus)) {
if (currentStatus.getBlobId().equals(targetStatus.getBlobId())){
blobIDList.add(currentStatus.getBlobId());
fileMap.put(fileName, currentStatus.getBlobId());
} else if (!currentStatus.getBlobId().equals(targetStatus.getBlobId())) {
resolveConflict(fileName, currentBranchCommit, givenBranchCommit);
}
} else if (Three2(currentStatus, targetStatus, mergeBaseStatus)) {
resolveConflict(fileName, currentBranchCommit, givenBranchCommit);
} else if (Four(currentStatus, targetStatus, mergeBaseStatus)) {
blobIDList.add(currentStatus.getBlobId());
fileMap.put(fileName, currentStatus.getBlobId());
} else if (Five(currentStatus, targetStatus, mergeBaseStatus)) {
checkout4(givenBranchCommit, fileName); //p进这了,
blobIDList.add(targetStatus.getBlobId());
fileMap.put(fileName, targetStatus.getBlobId());
} else if (Six(currentStatus, targetStatus, mergeBaseStatus)) {
String path = findFileRecursively(CWD, fileName);
if (path != null) {
File file = new File(path);
file.delete();
};
} else if (Seven(currentStatus, targetStatus, mergeBaseStatus)) {
String path = findFileRecursively(CWD, fileName);
if (path != null) {
File file = new File(path);
file.delete();
}
}
}
String currentBranchName = readObject(currentBranch,File.class).getName();
List<Commit> parents = new ArrayList<>();
parents.add(currentBranchCommit);
parents.add(givenBranchCommit);
Date date = new Date();
Commit commit = new Commit("Merged "+branchName+" into "+currentBranchName, parents, date, blobIDList);//创建新的commit,作用是生成hashid
String commitHashID = commit.generatelID();
Commit commit2 = new Commit("Merged "+branchName+" into "+currentBranchName+".", parents, Commit.dateToTimeStamp(date), blobIDList, commitHashID, "",commitHashID, currentBranchCommit.branch, fileMap);//填入所有commit信息
File f2 = join(COMMIT_DIR, commitHashID);//commit的文件名使用hash id
try {
f2.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
writeObject(f2, commit2);
writeContents(Repository.HEAD_FILE, commitHashID);
writeContents(readObject(currentBranch,File.class), commitHashID);
}
}
}
public static Commit findSplitPoint (Commit currentBranchCommit, Commit givenBranchCommit){
// 参数校验
if (currentBranchCommit == null || givenBranchCommit == null) {
return null;
}
Set<String> visited = new HashSet<>(); // 用来记录访问过的提交
// 使用队列进行广度优先搜索,处理多父提交的情况
Queue<Commit> queue = new LinkedList<>();
queue.offer(currentBranchCommit);
while (!queue.isEmpty()) {
Commit commit = queue.poll();
if (commit != null) {
visited.add(commit.ID);
for (Commit parent : commit.parents) {
queue.offer(parent);
}
}
}
// 回溯 givenBranchCommit 的历史,直到找到第一个共同的提交
while (givenBranchCommit != null) {
if (visited.contains(givenBranchCommit.ID)) { // 如果 givenBranchCommit 的提交在 visited 中,说明找到了分割点
return givenBranchCommit; // 返回共同的提交
}
for (Commit parent : givenBranchCommit.parents) {
givenBranchCommit = parent;
break; // 只取第一个父提交,保持原逻辑不变
}
}
// 添加日志记录
System.out.println("No common ancestor found between the two branches.");
return null;
}
private static boolean One(FileStatus currentStatus, FileStatus targetStatus, FileStatus mergeBaseStatus) {
if (!currentStatus.isModified() && !mergeBaseStatus.isModified() && targetStatus.isModified()) {
return true;
} else {
return false;
}
}
private static boolean Two(FileStatus currentStatus, FileStatus targetStatus, FileStatus mergeBaseStatus) {
if (currentStatus.isModified() && !mergeBaseStatus.isModified() && !targetStatus.isModified() && targetStatus.exists()) {
return true;
} else {
return false;
}
}
private static boolean Three1(FileStatus currentStatus, FileStatus targetStatus, FileStatus mergeBaseStatus) {
if (currentStatus.isModified() && !mergeBaseStatus.isModified() && targetStatus.isModified()) {
return true;
} else {
return false;
}
}
private static boolean Three3(FileStatus currentStatus, FileStatus targetStatus, FileStatus mergeBaseStatus) {
if ((currentStatus.isModified() && !mergeBaseStatus.isModified() && targetStatus.isDeleted())
||(currentStatus.isDeleted() && !mergeBaseStatus.isModified() && targetStatus.isModified())) {
return true;
} else {
return false;
}
}
private static boolean Three2(FileStatus currentStatus, FileStatus targetStatus, FileStatus mergeBaseStatus) {
// 空值检查
if (currentStatus == null || targetStatus == null || mergeBaseStatus == null) {
throw new IllegalArgumentException("FileStatus parameters cannot be null");
}
// 简化布尔表达式
return (currentStatus.isModified() && !mergeBaseStatus.isModified() && !targetStatus.exists()) ||
(!currentStatus.exists() && !mergeBaseStatus.isModified() && targetStatus.isModified());
}
private static boolean Four(FileStatus currentStatus, FileStatus targetStatus, FileStatus mergeBaseStatus) {
if (currentStatus.exists() && !mergeBaseStatus.exists() && !targetStatus.exists()) {
return true;
} else {
return false;
}
}
private static boolean Five(FileStatus currentStatus, FileStatus targetStatus, FileStatus mergeBaseStatus) {
if (!currentStatus.exists() && !mergeBaseStatus.exists() && targetStatus.exists()) {
return true;
} else {
return false;
}
}
private static boolean Six(FileStatus currentStatus, FileStatus targetStatus, FileStatus mergeBaseStatus) {
if (!currentStatus.isModified() && !mergeBaseStatus.isModified() && !targetStatus.exists()) {
return true;
} else {
return false;
}
}
private static boolean Seven(FileStatus currentStatus, FileStatus targetStatus, FileStatus mergeBaseStatus) {
if (!currentStatus.exists() && !mergeBaseStatus.isModified() && !targetStatus.isModified()) {
return true;
} else {
return false;
}
}
}
public class FileStatus {
private boolean exists; // 文件是否存在
private boolean modified; // 文件是否修改过
private boolean deleted; // 文件是否被删除
private String blobId; // 文件内容的哈希值(如果文件存在且被修改)
// 构造函数
public FileStatus(boolean exists, boolean modified, boolean deleted, String blobId) {
this.exists = exists;
this.modified = modified;
this.deleted = deleted;
this.blobId = blobId;
}
// Getter 方法
public boolean exists() {
return exists;
}
public boolean isModified() {
return modified;
}
public boolean isDeleted() {
return deleted;
}
public String getBlobId() {
return blobId;
}
}