implement commit-tree command

This commit is contained in:
Joseph Aquino 2025-12-25 15:48:31 -05:00
parent f7e0105ba1
commit aebdb21d84
9 changed files with 196 additions and 31 deletions

2
.gitignore vendored
View File

@ -387,4 +387,4 @@ Makefile
.idea/ .idea/
cmake*/ cmake-build*/

View File

@ -8,6 +8,14 @@ FetchContent_Declare(ZStrGitRepo
) )
FetchContent_MakeAvailable(ZStrGitRepo) # defines INTERFACE target 'zstr::zstr' 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) set(CMAKE_CXX_STANDARD 23)
add_executable(basic-vcs src/main.cpp add_executable(basic-vcs src/main.cpp
@ -24,7 +32,10 @@ add_executable(basic-vcs src/main.cpp
src/commands/hash-object.cpp src/commands/hash-object.cpp
include/hash-object.h include/hash-object.h
src/commands/clone.cpp 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) target_include_directories(basic-vcs PRIVATE include)

3
include/commit-tree.h Normal file
View File

@ -0,0 +1,3 @@
#pragma once
int commitTree(int argc, const char** argv);

View File

@ -28,6 +28,7 @@ int catFile(const int argc, const char** argv)
const std::string file = input.substr(2); const std::string file = input.substr(2);
zstr::ifstream inputFile("./.git/objects/" + dir + "/" + file, std::ofstream::binary); zstr::ifstream inputFile("./.git/objects/" + dir + "/" + file, std::ofstream::binary);
if (!inputFile.is_open()) if (!inputFile.is_open())
{ {
std::cerr << "Could not open file\n"; 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<char>(inputFile), std::istreambuf_iterator<char>()}; const std::string output{std::istreambuf_iterator<char>(inputFile), std::istreambuf_iterator<char>()};
std::cout << std::string_view(output).substr(output.find('\0') + 1); std::cout << std::string_view(output).substr(output.find('\0') + 1);
inputFile.close(); inputFile.close();
return 0; return 0;

View File

@ -0,0 +1,127 @@
#include "commit-tree.h"
#include <iostream>
#include <filesystem>
#include <fstream>
#include <string>
#include <string_view>
#include <chrono>
#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;
}

View File

@ -24,6 +24,7 @@ int hashObject(const int argc, const char** argv)
std::cerr << flag << "is an invalid flag, please use \"-w\"\n"; std::cerr << flag << "is an invalid flag, please use \"-w\"\n";
return 1; return 1;
} }
const std::string path = argv[3]; const std::string path = argv[3];
std::ifstream inputFile("./" + path); std::ifstream inputFile("./" + path);
if (!inputFile.is_open()) if (!inputFile.is_open())
@ -40,9 +41,14 @@ int hashObject(const int argc, const char** argv)
const std::string hash = checksum.final(); const std::string hash = checksum.final();
std::cout << hash << "\n"; 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()); outputFile.write(objectData.c_str(), objectData.size());
inputFile.close(); inputFile.close();

View File

@ -34,6 +34,7 @@ int lsTree(const int argc, const char** argv)
} }
const std::string output{std::istreambuf_iterator<char>(inputFile), std::istreambuf_iterator<char>()}; const std::string output{std::istreambuf_iterator<char>(inputFile), std::istreambuf_iterator<char>()};
//start right after tree <size>\0 //start right after tree <size>\0
size_t startChar = output.find('\0') + 1; size_t startChar = output.find('\0') + 1;
size_t nextNullChar = output.find('\0', startChar); size_t nextNullChar = output.find('\0', startChar);
@ -59,16 +60,18 @@ int lsTree(const int argc, const char** argv)
} }
for (const auto& value : hash) for (const auto& value : hash)
std::printf("%02x", static_cast<unsigned char>(value)); // simpler solution std::printf("%02x", static_cast<unsigned char>(value)); // easy way to display raw bytes as hex chars
std::cout << " "; std::cout << " ";
} }
std::cout << name << "\n"; std::cout << name << "\n";
startChar = nextNullChar + 21; startChar = nextNullChar + 21;
nextNullChar = output.find('\0', startChar); nextNullChar = output.find('\0', startChar);
} }
inputFile.close(); inputFile.close();
return 0;
return 0;
} }

View File

@ -16,27 +16,27 @@ struct node
std::string hash; std::string hash;
}; };
uint8_t hexToRaw(char firstDigit, char secondDigit) uint8_t hexToRaw(const char firstDigit, const char secondDigit)
{ {
uint8_t result = 0; uint8_t result = 0;
switch (firstDigit) switch (firstDigit)
{ {
case '0': result = 0; break; case '0': result = 0x0; break;
case '1': result = 1 << 4; break; case '1': result = 0x1 << 4; break;
case '2': result = 2 << 4; break; case '2': result = 0x2 << 4; break;
case '3': result = 3 << 4; break; case '3': result = 0x3 << 4; break;
case '4': result = 4 << 4; break; case '4': result = 0x4 << 4; break;
case '5': result = 5 << 4; break; case '5': result = 0x5 << 4; break;
case '6': result = 6 << 4; break; case '6': result = 0x6 << 4; break;
case '7': result = 7 << 4; break; case '7': result = 0x7 << 4; break;
case '8': result = 8 << 4; break; case '8': result = 0x8 << 4; break;
case '9': result = 9 << 4; break; case '9': result = 0x9 << 4; break;
case 'a': result = 10 << 4; break; case 'a': result = 0xa << 4; break;
case 'b': result = 11 << 4; break; case 'b': result = 0xb << 4; break;
case 'c': result = 12 << 4; break; case 'c': result = 0xc << 4; break;
case 'd': result = 13 << 4; break; case 'd': result = 0xd << 4; break;
case 'e': result = 14 << 4; break; case 'e': result = 0xe << 4; break;
case 'f': result = 15 << 4; break; case 'f': result = 0xf << 4; break;
default: result = 0; 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)) for (const auto& object : std::filesystem::directory_iterator(path))
{ {
if (object.is_directory()) if (object.is_directory())
{ {//recurse into the directory
nodes.emplace_back(object.path().filename().string(), 40000, hashDir(object.path().string())); nodes.emplace_back(object.path().filename().string(), 40000, hashDir(object.path().string()));
} }
else else
@ -82,7 +82,9 @@ std::string hashDir(const std::string& path)
SHA1 checksum; SHA1 checksum;
checksum.update("blob " + std::to_string(fileContent.size()) + '\0' + fileContent); checksum.update("blob " + std::to_string(fileContent.size()) + '\0' + fileContent);
nodes.emplace_back(object.path().filename(), 100644, checksum.final()); nodes.emplace_back(object.path().filename(), 100644, checksum.final());
file.close(); file.close();
} }
} }
@ -102,14 +104,12 @@ std::string hashDir(const std::string& path)
treeObject += static_cast<char>(byte); treeObject += static_cast<char>(byte);
} }
} }
auto temp = treeObject.size();
treeObject = "tree " + std::to_string(treeObject.size()) + '\0' + treeObject; treeObject = "tree " + std::to_string(treeObject.size()) + '\0' + treeObject;
SHA1 checksum; SHA1 checksum;
checksum.update(treeObject); checksum.update(treeObject);
return checksum.final(); return checksum.final();
} }
int writeTree(const char** argv) int writeTree(const char** argv)
@ -125,16 +125,19 @@ int writeTree(const char** argv)
if (object.path().filename() == ".git") continue; if (object.path().filename() == ".git") continue;
if (object.is_directory()) if (object.is_directory())
{ {//recurse into the directory
nodes.emplace_back(object.path().filename().string(), 40000, hashDir(object.path().string())); nodes.emplace_back(object.path().filename().string(), 40000, hashDir(object.path().string()));
} }
else else
{ {
std::ifstream file(object.path().string()); std::ifstream file(object.path().string());
std::string fileContent {std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()}; std::string fileContent {std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()};
SHA1 checksum; SHA1 checksum;
checksum.update("blob " + std::to_string(fileContent.size()) + '\0' + fileContent); checksum.update("blob " + std::to_string(fileContent.size()) + '\0' + fileContent);
nodes.emplace_back(object.path().filename(), 100644, checksum.final()); nodes.emplace_back(object.path().filename(), 100644, checksum.final());
file.close(); file.close();
} }
@ -164,9 +167,14 @@ int writeTree(const char** argv)
const std::string hash = checksum.final(); const std::string hash = checksum.final();
std::cout << hash << "\n"; 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.write(treeObject.c_str(), treeObject.size());
outputFile.close(); outputFile.close();

View File

@ -2,6 +2,7 @@
#include <string_view> #include <string_view>
#include <sstream> #include <sstream>
#include "commands.h" #include "commands.h"
#include "commit-tree.h"
#include "../include/hash-object.h" #include "../include/hash-object.h"
int main(const int argc, const char** argv) int main(const int argc, const char** argv)
@ -34,6 +35,10 @@ int main(const int argc, const char** argv)
{ {
return writeTree(argv); return writeTree(argv);
} }
else if (command == "commit-tree")
{
return commitTree(argc, argv);
}
else if (command == "clone") else if (command == "clone")
{ {
return clone(argc, argv); return clone(argc, argv);