From 8c01048d33de532bf78ab256daf51227c0738ca9 Mon Sep 17 00:00:00 2001 From: unknown <907162427@qq.com> Date: Sat, 21 Oct 2023 10:39:46 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=B2=A1=E6=9C=89?= =?UTF-8?q?=E4=BB=BB=E4=BD=95=E6=8F=90=E4=BA=A4=E6=97=B6=E5=80=99sha1?= =?UTF-8?q?=E8=AE=BE=E4=B8=BANULL=E7=9A=84=E9=97=AE=E9=A2=98=EF=BC=8C?= =?UTF-8?q?=E6=8C=89git=E7=9A=84=E6=83=85=E5=86=B5=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 2 ++ checkout/checkout.cpp | 3 +-- commit/commit.cpp | 4 ++-- merge/merge.cpp | 20 ++++++++++++++++++++ object/object.cpp | 7 ++++--- s-git.cpp | 1 + s-git.h | 1 + test/test.py | 1 + 8 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 merge/merge.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f534a31..c7cf707 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ aux_source_directory(./tag TAG_LIST) aux_source_directory(./branch BRANCH_LIST) aux_source_directory(./checkout CHECKOUT_LIST) aux_source_directory(./ls-tree LS_TREE_LIST) +aux_source_directory(./merge MERGE_LIST) set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}) @@ -33,6 +34,7 @@ add_executable(s-git ${MAIN_LIST} ${BRANCH_LIST} ${CHECKOUT_LIST} ${LS_TREE_LIST} + ${MERGE_LIST} ) if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") target_link_libraries(s-git stdc++fs) diff --git a/checkout/checkout.cpp b/checkout/checkout.cpp index 5175c0a..4b119c5 100644 --- a/checkout/checkout.cpp +++ b/checkout/checkout.cpp @@ -55,8 +55,7 @@ bool deleteDir(fs::path path) { } void copyBranches(const fs::path& dir, const std::string& sha1) { - if (sha1 == "NULL") { - + if (sha1 == "") { return; } if (sha1Exist(sha1)) { diff --git a/commit/commit.cpp b/commit/commit.cpp index f1b7d79..c31bf60 100644 --- a/commit/commit.cpp +++ b/commit/commit.cpp @@ -118,7 +118,7 @@ int commitMain(int argc, const char* argv[]) { std::vector index; auto treeOpt = build_tree(ROOT_DIR.value()); // file tree of s-git root directory - std::string newcommitSha1 = writeCommit(treeOpt.has_value() ? treeOpt.value().sha1 : "NULL" , "NULL", message); + std::string newcommitSha1 = writeCommit(treeOpt.has_value() ? treeOpt.value().sha1 : "" , "", message); writeMain(newcommitSha1); std::cout << "first commit \n"; @@ -135,7 +135,7 @@ int commitMain(int argc, const char* argv[]) { if (!readCommit(commitSha1, parentCommit)) { auto treeOpt = build_tree(ROOT_DIR.value()); // file tree of s-git root directory - std::string newcommitSha1 = writeCommit(treeOpt.has_value() ? treeOpt.value().sha1 : "NULL", "NULL", message); + std::string newcommitSha1 = writeCommit(treeOpt.has_value() ? treeOpt.value().sha1 : "", "", message); writeMain(newcommitSha1); std::cout << "first commit \n"; diff --git a/merge/merge.cpp b/merge/merge.cpp new file mode 100644 index 0000000..c3dff6b --- /dev/null +++ b/merge/merge.cpp @@ -0,0 +1,20 @@ +#include "../s-git.h" +#include "../cache/file.h" +#include "../object/object.h" +#include +#include +#include +#include +#include + +int merge(int argc, const char* argv[]); + +Command MergeCommand{ + merge, + "Join two or more development histories together" +}; + +int merge(int argc, const char* argv[]){ + std::cout << "Join two or more development histories together" << std::endl; + return 1; +} diff --git a/object/object.cpp b/object/object.cpp index b35fcd2..42d7f62 100644 --- a/object/object.cpp +++ b/object/object.cpp @@ -34,8 +34,9 @@ bool checkSha1(const std::string &sha1) { } bool readCommit(const std::string &sha1, commit& thisCommit) { - if (sha1 == "NULL") { + if (sha1 == ""){ // not error, but also not commit + std::cerr << red << "Error: " << GIT_NAME << " your current branch 'master' does not have any commits yet" << Reset << std::endl; return false; } @@ -69,7 +70,7 @@ bool readCommit(const std::string &sha1, commit& thisCommit) { } void readTree(const fs::path &dir, const std::string &sha1, std::vector& path) { - if (sha1 == "NULL") { + if (sha1 == "") { return; } if (sha1Exist(sha1)) { @@ -254,7 +255,7 @@ std::optional> readRawFile(const std::string& sha1) { - if (sha1 == "NULL") { + if (sha1 == "") { return {}; } if (sha1Exist(sha1)) { diff --git a/s-git.cpp b/s-git.cpp index aec096f..8e8a17f 100644 --- a/s-git.cpp +++ b/s-git.cpp @@ -27,6 +27,7 @@ static void initFuncTable() { funcTable.emplace("log", LogCommand); funcTable.emplace("branch", BranchCommand); funcTable.emplace("ls-tree", LsTreeCommand); + funcTable.emplace("merge", MergeCommand); } static void usage() { diff --git a/s-git.h b/s-git.h index c84ef3a..09c80d9 100644 --- a/s-git.h +++ b/s-git.h @@ -45,3 +45,4 @@ extern Command CheckoutCommand; extern Command LogCommand; extern Command BranchCommand; extern Command LsTreeCommand; +extern Command MergeCommand; diff --git a/test/test.py b/test/test.py index 7114ebc..dfc39c6 100644 --- a/test/test.py +++ b/test/test.py @@ -252,6 +252,7 @@ def main(): try: TestInit() TestStatusNoCommit() + TestLog(["second", "first"]) TestStatusUntracked() shaFirstShort = TestCommitFirst() TestBranchAdd("master") From 2238117756e0bb0b50b8b04de2c8f8d4edebbc95 Mon Sep 17 00:00:00 2001 From: unknown <907162427@qq.com> Date: Mon, 23 Oct 2023 20:53:33 +0800 Subject: [PATCH 2/4] Fix issue #5 --- logCommand/log.cpp | 2 +- merge/merge.cpp | 17 ++++++++++++++--- object/object.cpp | 6 +++++- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/logCommand/log.cpp b/logCommand/log.cpp index ab6068c..116bc31 100644 --- a/logCommand/log.cpp +++ b/logCommand/log.cpp @@ -22,7 +22,7 @@ int logMain(int argc, const char* argv[]) { commit currCommit; while (readCommit(sha1, currCommit)) { std::cout << yellow << "[commit " << currCommit.sha1 << ']' << Reset << std::endl; - std::cout << cyan << "mmessage:" << Reset << std::endl; + std::cout << cyan << "message:" << Reset << std::endl; std::cout << std::endl; std::cout << currCommit.message; diff --git a/merge/merge.cpp b/merge/merge.cpp index c3dff6b..81826f9 100644 --- a/merge/merge.cpp +++ b/merge/merge.cpp @@ -7,14 +7,25 @@ #include #include -int merge(int argc, const char* argv[]); +int mergeMain(int argc, const char* argv[]); Command MergeCommand{ - merge, + mergeMain, "Join two or more development histories together" }; -int merge(int argc, const char* argv[]){ +//数据结构定义先 + +int mergeMain(int argc, const char* argv[]){ + cmdline::parser argParser; + argParser.add("message", 'm', "merge message", true, ""); + argParser.parse_check(argc, argv); + // 参数解析 + + // 判断是否需要 merge 是否可以merege先 + // 1. 判断是否有冲突 + // 2. 判断是否有未提交的修改 + std::cout << "Join two or more development histories together" << std::endl; return 1; } diff --git a/object/object.cpp b/object/object.cpp index 42d7f62..96e4358 100644 --- a/object/object.cpp +++ b/object/object.cpp @@ -36,7 +36,11 @@ bool checkSha1(const std::string &sha1) { bool readCommit(const std::string &sha1, commit& thisCommit) { if (sha1 == ""){ // not error, but also not commit - std::cerr << red << "Error: " << GIT_NAME << " your current branch 'master' does not have any commits yet" << Reset << std::endl; + // if this commit is empty ,then means there is no commit + if (thisCommit.sha1 == "") + std::cerr << red << "Error: " << GIT_NAME << " your current branch 'master' does not have any commits yet" << Reset << std::endl; + + return false; } From d60e4ad386370312f0a91507a72a29f4559f7c16 Mon Sep 17 00:00:00 2001 From: unknown <907162427@qq.com> Date: Mon, 23 Oct 2023 21:43:57 +0800 Subject: [PATCH 3/4] for merge --- merge/merge.cpp | 15 +++++++++++++++ test/test.py | 7 ++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/merge/merge.cpp b/merge/merge.cpp index 81826f9..b566069 100644 --- a/merge/merge.cpp +++ b/merge/merge.cpp @@ -9,6 +9,10 @@ int mergeMain(int argc, const char* argv[]); +struct MergeResult{ + std::vector parents; + std::string sha1; +}; Command MergeCommand{ mergeMain, "Join two or more development histories together" @@ -19,7 +23,18 @@ Command MergeCommand{ int mergeMain(int argc, const char* argv[]){ cmdline::parser argParser; argParser.add("message", 'm', "merge message", true, ""); + argParser.footer(" ..."); + argParser.parse_check(argc, argv); + // 首先获得当前分支的, + if (argParser.rest().size() == 0) { + std::cout << argParser.usage(); + return 0; + } + for (auto branch : argParser.rest()) { + std::cout << branch << std::endl; + } + // 参数解析 // 判断是否需要 merge 是否可以merege先 diff --git a/test/test.py b/test/test.py index dfc39c6..01d0881 100644 --- a/test/test.py +++ b/test/test.py @@ -252,7 +252,7 @@ def main(): try: TestInit() TestStatusNoCommit() - TestLog(["second", "first"]) + TestLog([]) TestStatusUntracked() shaFirstShort = TestCommitFirst() TestBranchAdd("master") @@ -293,8 +293,9 @@ def main(): parser.add_argument("exe", metavar = "executable-path") args = parser.parse_args() - app = os.path.abspath(args.exe) - + app = os.path.abspath('D:\learn\programming\s-git\s-git.exe') + + print(app) LoadTmpDir() main() DeleteTmpDir() From 83e40ac17f411bc5c8630d1db66e7f1bd1737abe Mon Sep 17 00:00:00 2001 From: loach1528 <907162427@qq.com> Date: Wed, 1 Nov 2023 16:29:48 +0800 Subject: [PATCH 4/4] Basically completed the three-way merge of blob files, assuming there is a common ancestor node. --- checkout/checkout.cpp | 32 +++++ merge/merge.cpp | 281 ++++++++++++++++++++++++++++++++++++++++-- object/object.cpp | 51 ++++++++ object/object.h | 19 +++ 4 files changed, 374 insertions(+), 9 deletions(-) diff --git a/checkout/checkout.cpp b/checkout/checkout.cpp index 4b119c5..7bba081 100644 --- a/checkout/checkout.cpp +++ b/checkout/checkout.cpp @@ -239,3 +239,35 @@ int checkoutMain(int argc, const char* argv[]) { return 0; } + +int checkout(std::string branch){ + auto resolveBranch = resolve(branch); + std::string sha1 = resolveBranch.has_value() ? resolveBranch.value() : readBranch(branch); + if (sha1.length() == 0) { + std::cerr << "Error:" << " doesn't have this branch :" << branch << "\n"; + return 1; + } + + deleteDir(ROOT_DIR.value()); + + commit branchCommit; + readCommit(sha1, branchCommit); + copyBranches(ROOT_DIR.value(), branchCommit.tree); + + if (resolveBranch.has_value()) { + // write HEAD + fs::path headDir = GIT_DIR.value() / "HEAD"; + write_file(headDir, sha1.data(), sha1.size()); + } + else { + // write head branchCommit.sha1 + fs::path branchPath = fs::path("refs") / "heads" / branch; + fs::path headDir = GIT_DIR.value() / "HEAD"; + std::string branchWrite = std::string{ "ref: " } +branchPath.generic_u8string(); + write_file(headDir, branchWrite.data(), branchWrite.size()); + } + + std::cout << "Switched to branch '" << branch << '\'' << std::endl; + return 0; + +} \ No newline at end of file diff --git a/merge/merge.cpp b/merge/merge.cpp index b566069..59d3364 100644 --- a/merge/merge.cpp +++ b/merge/merge.cpp @@ -6,7 +6,8 @@ #include #include #include - +#include +namespace fs = std::filesystem; int mergeMain(int argc, const char* argv[]); struct MergeResult{ @@ -17,8 +18,165 @@ Command MergeCommand{ mergeMain, "Join two or more development histories together" }; +// 定义数据结构 + +bool isAncestor(const commit& ancestor, const commit& child){ + //判断ancestor是否是child的祖先 + // 通过child的parent来判断 + if (child.sha1 == "")return false; + if (ancestor.sha1 == child.sha1) { + return true; + } + if (child.parent == "") { + return false; + } + commit parentCommit; + if (!readCommit(child.parent, parentCommit)) { + return false; + } + return (isAncestor(ancestor,parentCommit)); +} + +//直接将当前分支指向mergeFromCommit +extern +int checkout(std::string branch); + +// 寻找最近公共祖先,简易版本 +commit LCA(const commit& A, const commit& B){ + // 先计算深度 + int alength = commitLength(A); + int blength = commitLength(B); + commit currA,currB; + if (alength > blength) { + // 交换A,B + currA = B; + currB = A; + + }else{ + currA = A; + currB = B; + } + // 从B开始向上找 + for (int i = 0;i < std::abs(alength - blength);i++) { + currB = Parent(currB); + } + while (currA.sha1 != currB.sha1 && currA.sha1 != "" && currB.sha1 != "") { + currA = Parent(currA); + currB = Parent(currB); + } + if (currA.sha1 == currB.sha1) { + return currA; + } +} +std::string mergeContent(std::string based,std::string source,std::string curr){ + //逐行对比 通过换行符来分割 + + std::vector basedLines; + std::vector sourceLines; + std::vector currLines; + std::string line; + std::stringstream basedStream(based); + std::stringstream sourceStream(source); + std::stringstream currStream(curr); + while (std::getline(basedStream,line)) { + basedLines.push_back(line); + } + while (std::getline(sourceStream,line)) { + sourceLines.push_back(line); + } + while (std::getline(currStream,line)) { + currLines.push_back(line); + } + // 三路归并 + std::vector mergedLines; + //通过填补空行来实现三者行数对齐 + int basedLinesSize = basedLines.size(); + int sourceLinesSize = sourceLines.size(); + int currLinesSize = currLines.size(); + int maxLinesSize = std::max(basedLinesSize,std::max(sourceLinesSize,currLinesSize)); + for (int i = 0;i>>>>>>"); + mergedLines.push_back(currLines[i]); + } + } + std::string mergedContent; + for (auto &line : mergedLines) { + mergedContent += line + "\n"; + } + return mergedContent; +} + +object mergeFile(object based,object source,object curr){ + if (based.object_type == "blob") { + // 三者都是blob + // 1. 如果三者都相同,直接返回 + if (based.sha1 == source.sha1 && source.sha1 == curr.sha1) { + return based; + } + // 2. 如果based和source相同,返回curr + if (based.sha1 == source.sha1) { + return curr; + } + // 3. 如果based和curr相同,返回source + if (based.sha1 == curr.sha1) { + return source; + } + // 4. 如果source和curr相同,返回source + if (source.sha1 == curr.sha1) { + return source; + } + // 5. 如果三者都不同,返回冲突 -//数据结构定义先 + fs::path basedPath = sha1_to_path(based.sha1); + auto basedbuffer = readFile(basedPath); + fs::path sourcePath = sha1_to_path(source.sha1); + auto sourcebuffer = readFile(sourcePath); + fs::path currPath = sha1_to_path(curr.sha1); + auto currbuffer = readFile(currPath); + std::string mergedContent = mergeContent({basedbuffer.begin(),basedbuffer.end()}, + {sourcebuffer.begin(),sourcebuffer.end()}, + {currbuffer.begin(),currbuffer.end()}); + // 应该直接删除对应的blob文件,写下新的blob文件,然后直接复用build_tree这个提交commit的方法。 + write_file(curr.path,mergedContent.data(),mergedContent.size()); + + std::string sha1 = hash_object("blob",mergedContent); + object mergedObject; + mergedObject.object_type = "blob"; + mergedObject.sha1 = sha1; + mergedObject.path = based.path; + return mergedObject;; + } +} int mergeMain(int argc, const char* argv[]){ cmdline::parser argParser; @@ -31,16 +189,121 @@ int mergeMain(int argc, const char* argv[]){ std::cout << argParser.usage(); return 0; } - for (auto branch : argParser.rest()) { - std::cout << branch << std::endl; + //从rest中获得需要merge的分支 + if (argParser.rest().size() > 1) { + std::cout << "merge only support one branch" << std::endl; + return 0; } - - // 参数解析 + std::string mergeFrom = argParser.rest()[0]; + //判断mergeFrom是否存在 + auto resolvebranch = resolveBranch(mergeFrom); + std::string sha1 = resolvebranch.has_value() ? resolvebranch.value() : readBranch(mergeFrom); + if (sha1.length() == 0) { + std::cerr << "Error:" << " doesn't have this branch :" << mergeFrom << "\n"; + return 1; + } + commit mergeFromCommit ; + readCommit(sha1,mergeFromCommit); + // 这个部分可以重用吧,从branch名字读取到这个commit的sha1 + std::string currBranch = readBranchName(); + auto resolvecurrBranch = resolveBranch(currBranch); + sha1 = resolvecurrBranch.has_value() ? resolvecurrBranch.value() : readBranch(currBranch); + if (sha1.length() == 0) { + std::cerr << "Error:" << " doesn't have this branch :" << currBranch << "\n"; + return 1; + } + commit currBranchCommit ; + readCommit(sha1,currBranchCommit); - // 判断是否需要 merge 是否可以merege先 - // 1. 判断是否有冲突 - // 2. 判断是否有未提交的修改 + //首先判断是否是同一个分支的路,通过commit的parent来判断 + std::cout << "Merge from " << mergeFrom << std::endl; + std::cout << "On branch " << currBranch << std::endl; + + //判断两者是否互为祖先 + if (isAncestor(currBranchCommit,mergeFromCommit)) { + std::cout << "Already up to date." << std::endl; + return 0; + } + if (isAncestor(mergeFromCommit,currBranchCommit)) { + std::cout << "Fast-forward" << std::endl; + checkout(mergeFrom); + return 0; + } + // 寻找最近公共祖先 //先假设是有祖先的情况 + commit ancestor = LCA(currBranchCommit,mergeFromCommit); + std::cout << ancestor.message << std::endl; + // 三路归并文件树 + // 1. 读取ancestor的文件树 + std::vector ancestorEntries; + readTree(ROOT_DIR.value(), ancestor.tree, ancestorEntries); + // 将 ancestorEntries 转换为set + std::unordered_set ancestorSet; + for (auto &entry : ancestorEntries) { + ancestorSet.insert(entry); + } + for (auto &entry : ancestorSet) { + std::cout << entry.path << std::endl; + } + std::cout << std::endl; + std::vector mergeFromEntries; + readTree(ROOT_DIR.value(), mergeFromCommit.tree, mergeFromEntries); + // 将 mergeFromEntries 转换为set + std::unordered_set mergeFromSet; + for (auto &entry : mergeFromEntries) { + mergeFromSet.insert(entry); + } + for (auto &entry : mergeFromEntries) { + std::cout << entry.path << std::endl; + } + std::cout << std::endl; + std::vector currBranchEntries; + readTree(ROOT_DIR.value(), currBranchCommit.tree, currBranchEntries); + // 将 currBranchEntries 转换为set + std::unordered_set currBranchSet; + for (auto &entry : currBranchEntries) { + currBranchSet.insert(entry); + } + for (auto &entry : currBranchSet) { + std::cout << entry.path << std::endl; + } + std::cout << std::endl; + std::vector mergedEntries; + std::unordered_set allSet; + allSet.insert(ancestorSet.begin(),ancestorSet.end()); + allSet.insert(mergeFromSet.begin(),mergeFromSet.end()); + allSet.insert(currBranchSet.begin(),currBranchSet.end()); + for (auto &entry :allSet){ + auto ancestorenobject = ancestorSet.find(entry); + auto mergeFromobject = mergeFromSet.find(entry); + auto currBranchobject = currBranchSet.find(entry); + if (mergeFromobject != mergeFromSet.end() && currBranchobject != currBranchSet.end()) { + //三个地方都有 + // 其实要处理三者是否相同 + // 三路归并文件 + + auto mergedEntry = mergeFile(*ancestorenobject,*mergeFromobject,*currBranchobject); + mergedEntries.push_back(entry); + } + if (mergeFromSet.find(entry) != mergeFromSet.end() && currBranchSet.find(entry) == currBranchSet.end()) { + // mergeFrom有,currBranch没有 + mergedEntries.push_back(entry); + } + if (mergeFromSet.find(entry) == mergeFromSet.end() && currBranchSet.find(entry) != currBranchSet.end()) { + // mergeFrom没有,currBranch有 + + mergedEntries.push_back(entry); + } + } + + + std::cout << "mergedEntries" << std::endl; + for (auto &entry : mergedEntries) { + std::cout << entry.path << std::endl; + } + // 将mergedEntries 建树 并提交一份新的commit + commit newCommit; + std::cout << "Join two or more development histories together" << std::endl; return 1; } diff --git a/object/object.cpp b/object/object.cpp index 96e4358..b6453e3 100644 --- a/object/object.cpp +++ b/object/object.cpp @@ -301,4 +301,55 @@ void copy_object_tofile(const fs::path& path, const std::string& sha1) { auto buffer = readRawFile(sha1); if (buffer.has_value()) write_file(path, buffer.value()); +} + +std::optional resolveBranch(std::string &branch) { + // return commit sha1 + auto idx = branch.rfind('~'); + if (idx == std::string::npos) { + return {}; + } + + int count; + try { + count = std::stoi(branch.substr(idx + 1)); + } + catch (std::invalid_argument) { + return {}; + } + catch (std::out_of_range) { + return {}; + } + + if (count < 0) { + return {}; + } + + auto name = branch.substr(0, idx); + if (count == 0) { + branch = name; + return {}; + } + + std::string commitSha1 = readBranch(name); + commit currCommit; + while (count-- != 0) { + if (!readCommit(commitSha1, currCommit)) { + return {}; + } + commitSha1 = currCommit.parent; + } + return commitSha1; +} + +struct commit Parent(struct commit child){ + commit parentCommit; + if (!readCommit(child.parent, parentCommit)) { + return {}; + } + return parentCommit; +} +int commitLength(const struct commit c){ + if (c.parent == "") return 0; + else return 1 + commitLength(Parent(c)); } \ No newline at end of file diff --git a/object/object.h b/object/object.h index f25acab..1f5bfd2 100644 --- a/object/object.h +++ b/object/object.h @@ -4,6 +4,7 @@ #include #include #include +#include namespace fs = std::filesystem; @@ -53,3 +54,21 @@ std::set paths(const std::filesystem::path &path); void copy_object_tofile(const std::filesystem::path& path, const std::string& sha1); + +std::optional resolveBranch(std::string &branch); + +struct commit Parent(struct commit child); + +int commitLength(const struct commit c); + +struct object_equal { + bool operator()(const object& obj1, const object& obj2) const { + return obj1.path.string() == obj2.path.string(); + } +}; + +struct object_hash { + std::size_t operator()(const object& obj) const { + return std::hash()(obj.path.string()); + } +}; \ No newline at end of file