diff --git a/.gitignore b/.gitignore index 487523d..f95d553 100644 --- a/.gitignore +++ b/.gitignore @@ -387,4 +387,4 @@ Makefile .idea/ -cmake*/ \ No newline at end of file +cmake-build*/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 06414ee..4f9b7cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,14 @@ FetchContent_Declare(ZStrGitRepo ) FetchContent_MakeAvailable(ZStrGitRepo) # defines INTERFACE target 'zstr::zstr' +set(BUILD_TZ_LIB ON)# timezone support + +FetchContent_Declare( date_src + GIT_REPOSITORY https://gitlab.com/JosephA1997/date.git + GIT_TAG "master" +) +FetchContent_MakeAvailable(date_src) + set(CMAKE_CXX_STANDARD 23) add_executable(basic-vcs src/main.cpp @@ -24,7 +32,10 @@ add_executable(basic-vcs src/main.cpp src/commands/hash-object.cpp include/hash-object.h src/commands/clone.cpp - include/clone.h) + include/clone.h + src/commands/commit-tree.cpp + include/commit-tree.h + include/commit-tree.h) -target_link_libraries(basic-vcs zstr::zstr) +target_link_libraries(basic-vcs PRIVATE zstr::zstr date::date date::date-tz) target_include_directories(basic-vcs PRIVATE include) \ No newline at end of file diff --git a/include/commit-tree.h b/include/commit-tree.h new file mode 100644 index 0000000..6adfe4c --- /dev/null +++ b/include/commit-tree.h @@ -0,0 +1,3 @@ +#pragma once + +int commitTree(int argc, const char** argv); \ No newline at end of file diff --git a/src/commands/cat-file.cpp b/src/commands/cat-file.cpp index 325d1ef..4d47229 100644 --- a/src/commands/cat-file.cpp +++ b/src/commands/cat-file.cpp @@ -28,6 +28,7 @@ int catFile(const int argc, const char** argv) const std::string file = input.substr(2); zstr::ifstream inputFile("./.git/objects/" + dir + "/" + file, std::ofstream::binary); + if (!inputFile.is_open()) { std::cerr << "Could not open file\n"; @@ -36,6 +37,7 @@ int catFile(const int argc, const char** argv) const std::string output{std::istreambuf_iterator(inputFile), std::istreambuf_iterator()}; std::cout << std::string_view(output).substr(output.find('\0') + 1); + inputFile.close(); return 0; diff --git a/src/commands/commit-tree.cpp b/src/commands/commit-tree.cpp new file mode 100644 index 0000000..6e7aa71 --- /dev/null +++ b/src/commands/commit-tree.cpp @@ -0,0 +1,127 @@ +#include "commit-tree.h" + +#include +#include +#include +#include +#include +#include + +#include "sha1.hpp" +#include "zstr.hpp" +#include "date/tz.h" + +int commitTree(int argc, const char** argv) +{ + const std::string_view firstFlag = argv[3]; + if (firstFlag != "-m" and firstFlag != "-p") + { + std::cerr << "\n\nplease use '-p' to specify the parent commit, or '-m' to specify initial commit message" << "\n\n"; + return 1; + } + + bool initialCommit = false; + if (firstFlag == "-m") + initialCommit = true; + + std::string commitHeader; + std::string commitBody; + std::string authorName; + std::string authorEmail; + std::string_view commitMessage; + std::string_view treeSha = argv[2]; + + commitBody.append("tree "); + commitBody.append(treeSha); + commitBody.append("\n"); + + if (!initialCommit) + { + const std::string_view parentSha = argv[4]; + commitBody.append("parent "); + commitBody.append(parentSha).append("\n"); + commitMessage = argv[6]; + } + else + { + commitMessage = argv[4]; + } + + // get author info + if (std::filesystem::exists("./.git/author.txt")) + { + std::ifstream authorInfo("./.git/author.txt"); + if (!authorInfo) + { + std::cerr << "\n\n'/.git/author.txt exists but could not be opened\n\n"; + return 1; + } + + //use getline() to include spaces between first and last name + std::getline(authorInfo, authorName); + std::getline(authorInfo, authorEmail); + authorInfo.close(); + } + else + { + //use getline() to include spaces between first and last name + std::cout << "please enter the name of the author/commiter: "; + std::getline(std::cin, authorName); + std::cout << "please enter the email of the author/commiter: "; + std::getline(std::cin, authorEmail); + + char input{}; + do + { + std::cout << "would you like to save this information for future commits? (Y/N): "; + std::cin >> input; + input = std::toupper(input); + } while (input != 'Y' and input != 'N'); + + if (input == 'Y') + { + std::ofstream authorFile("./.git/author.txt"); + if (!authorFile) + { + std::cerr << "\n\ncould not write author info to '/.git/author.txt'\n\n"; + authorFile.close(); + } + else + { + authorFile << authorName << "\n" << authorEmail << "\n"; + authorFile.close(); + std::cout << "successfully wrote author info to '/.git/author.txt\n"; + } + } + }// end get author info + + auto timeZoneLocal = date::make_zoned(date::current_zone(), std::chrono::system_clock::now()); + std::ostringstream tzOffset; + tzOffset << timeZoneLocal.get_info().offset; + + //assume commiter and author are the same + commitBody.append("author " + authorName + " <" + authorEmail + "> " + std::to_string(timeZoneLocal.get_local_time().time_since_epoch().count()) + " " + tzOffset.str().substr(0,5) + "\n") ;//use substr() to exclude the char that defines the unit of measurement + commitBody.append("commiter " + authorName + " <" + authorEmail + "> " + std::to_string(timeZoneLocal.get_local_time().time_since_epoch().count()) + " " + tzOffset.str().substr(0,5) + "\n\n") ; + commitBody.append(commitMessage); + + commitHeader.append("commit " + std::to_string(commitBody.size()) + '\0'); + + std::string commitObject = commitHeader + commitBody; + + SHA1 checksum; + checksum.update(commitObject); + const std::string hash = checksum.final(); + const std::string hashDir = "./.git/objects/" + hash.substr(0,2); + + if (!std::filesystem::exists(hashDir)) + { + std::filesystem::create_directory(hashDir); + } + + zstr::ofstream outputFile(hashDir + "/" + hash.substr(2)); + outputFile.write(commitObject.c_str(), commitObject.size()); + + outputFile.close(); + + return 0; +} diff --git a/src/commands/hash-object.cpp b/src/commands/hash-object.cpp index af74064..29c8dcf 100644 --- a/src/commands/hash-object.cpp +++ b/src/commands/hash-object.cpp @@ -24,6 +24,7 @@ int hashObject(const int argc, const char** argv) std::cerr << flag << "is an invalid flag, please use \"-w\"\n"; return 1; } + const std::string path = argv[3]; std::ifstream inputFile("./" + path); if (!inputFile.is_open()) @@ -40,9 +41,14 @@ int hashObject(const int argc, const char** argv) const std::string hash = checksum.final(); std::cout << hash << "\n"; - std::filesystem::create_directory("./.git/objects/" + hash.substr(0,2)); + const std::string hashDir = "./.git/objects/" + hash.substr(0,2); - zstr::ofstream outputFile("./.git/objects/" + hash.substr(0,2) + "/" + hash.substr(2)); + if (!std::filesystem::exists(hashDir)) + { + std::filesystem::create_directory(hashDir); + } + + zstr::ofstream outputFile(hashDir + "/" + hash.substr(2)); outputFile.write(objectData.c_str(), objectData.size()); inputFile.close(); diff --git a/src/commands/ls-tree.cpp b/src/commands/ls-tree.cpp index a7156e3..ba27c40 100644 --- a/src/commands/ls-tree.cpp +++ b/src/commands/ls-tree.cpp @@ -34,6 +34,7 @@ int lsTree(const int argc, const char** argv) } const std::string output{std::istreambuf_iterator(inputFile), std::istreambuf_iterator()}; + //start right after tree \0 size_t startChar = output.find('\0') + 1; size_t nextNullChar = output.find('\0', startChar); @@ -59,16 +60,18 @@ int lsTree(const int argc, const char** argv) } for (const auto& value : hash) - std::printf("%02x", static_cast(value)); // simpler solution + std::printf("%02x", static_cast(value)); // easy way to display raw bytes as hex chars std::cout << " "; } + std::cout << name << "\n"; + startChar = nextNullChar + 21; nextNullChar = output.find('\0', startChar); } inputFile.close(); - return 0; + return 0; } diff --git a/src/commands/write-tree.cpp b/src/commands/write-tree.cpp index 44a1757..003e3f9 100644 --- a/src/commands/write-tree.cpp +++ b/src/commands/write-tree.cpp @@ -16,27 +16,27 @@ struct node std::string hash; }; -uint8_t hexToRaw(char firstDigit, char secondDigit) +uint8_t hexToRaw(const char firstDigit, const char secondDigit) { uint8_t result = 0; switch (firstDigit) { - case '0': result = 0; break; - case '1': result = 1 << 4; break; - case '2': result = 2 << 4; break; - case '3': result = 3 << 4; break; - case '4': result = 4 << 4; break; - case '5': result = 5 << 4; break; - case '6': result = 6 << 4; break; - case '7': result = 7 << 4; break; - case '8': result = 8 << 4; break; - case '9': result = 9 << 4; break; - case 'a': result = 10 << 4; break; - case 'b': result = 11 << 4; break; - case 'c': result = 12 << 4; break; - case 'd': result = 13 << 4; break; - case 'e': result = 14 << 4; break; - case 'f': result = 15 << 4; break; + case '0': result = 0x0; break; + case '1': result = 0x1 << 4; break; + case '2': result = 0x2 << 4; break; + case '3': result = 0x3 << 4; break; + case '4': result = 0x4 << 4; break; + case '5': result = 0x5 << 4; break; + case '6': result = 0x6 << 4; break; + case '7': result = 0x7 << 4; break; + case '8': result = 0x8 << 4; break; + case '9': result = 0x9 << 4; break; + case 'a': result = 0xa << 4; break; + case 'b': result = 0xb << 4; break; + case 'c': result = 0xc << 4; break; + case 'd': result = 0xd << 4; break; + case 'e': result = 0xe << 4; break; + case 'f': result = 0xf << 4; break; default: result = 0; break; } @@ -72,7 +72,7 @@ std::string hashDir(const std::string& path) for (const auto& object : std::filesystem::directory_iterator(path)) { if (object.is_directory()) - { + {//recurse into the directory nodes.emplace_back(object.path().filename().string(), 40000, hashDir(object.path().string())); } else @@ -82,7 +82,9 @@ std::string hashDir(const std::string& path) SHA1 checksum; checksum.update("blob " + std::to_string(fileContent.size()) + '\0' + fileContent); + nodes.emplace_back(object.path().filename(), 100644, checksum.final()); + file.close(); } } @@ -102,14 +104,12 @@ std::string hashDir(const std::string& path) treeObject += static_cast(byte); } } - auto temp = treeObject.size(); treeObject = "tree " + std::to_string(treeObject.size()) + '\0' + treeObject; + SHA1 checksum; checksum.update(treeObject); - return checksum.final(); - } int writeTree(const char** argv) @@ -125,16 +125,19 @@ int writeTree(const char** argv) if (object.path().filename() == ".git") continue; if (object.is_directory()) - { + {//recurse into the directory nodes.emplace_back(object.path().filename().string(), 40000, hashDir(object.path().string())); } else { std::ifstream file(object.path().string()); std::string fileContent {std::istreambuf_iterator(file), std::istreambuf_iterator()}; + SHA1 checksum; checksum.update("blob " + std::to_string(fileContent.size()) + '\0' + fileContent); + nodes.emplace_back(object.path().filename(), 100644, checksum.final()); + file.close(); } @@ -164,9 +167,14 @@ int writeTree(const char** argv) const std::string hash = checksum.final(); std::cout << hash << "\n"; - std::filesystem::create_directory("./.git/objects/" + hash.substr(0,2)); + const std::string hashDir = "./.git/objects/" + hash.substr(0,2); - zstr::ofstream outputFile("./.git/objects/" + hash.substr(0,2) + "/" + hash.substr(2)); + if (!std::filesystem::exists(hashDir)) + { + std::filesystem::create_directory(hashDir); + } + + zstr::ofstream outputFile(hashDir + "/" + hash.substr(2)); outputFile.write(treeObject.c_str(), treeObject.size()); outputFile.close(); diff --git a/src/main.cpp b/src/main.cpp index 20125fe..897c6b0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,7 @@ #include #include #include "commands.h" +#include "commit-tree.h" #include "../include/hash-object.h" int main(const int argc, const char** argv) @@ -34,6 +35,10 @@ int main(const int argc, const char** argv) { return writeTree(argv); } + else if (command == "commit-tree") + { + return commitTree(argc, argv); + } else if (command == "clone") { return clone(argc, argv);