From b28feee16e2a31e4a1224e0873b8247942fb3bce Mon Sep 17 00:00:00 2001 From: Daniel Boianju Date: Fri, 6 Mar 2026 18:00:05 +0200 Subject: [PATCH 1/3] Added functionality to gitConfig to print git tree from specific Folder --- src/GitConfig.cpp | 15 +++++++++++++++ src/GitConfig.h | 2 ++ src/main.cpp | 16 +--------------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/GitConfig.cpp b/src/GitConfig.cpp index a20602d..a2ddbbe 100644 --- a/src/GitConfig.cpp +++ b/src/GitConfig.cpp @@ -128,4 +128,19 @@ bool GitConfig::addGitObj(const std::filesystem::path& p) { } return true; +} + +void GitConfig::printTree() const { + this->printGitTreeFromFolder(this->rootObject, ""); +} + +void GitConfig::printGitTreeFromFolder(const std::shared_ptr& root, std::string printPrefix) const { + for (auto& subObj : root->getSubObjects()) { + if (subObj->isDirectory()) { + std::cout << printPrefix << subObj->getName() << ":" << std::endl; + this->printGitTreeFromFolder(std::dynamic_pointer_cast(subObj), printPrefix + "|- "); + } else { + std::cout << printPrefix << subObj->getName() << std::endl; + } + } } \ No newline at end of file diff --git a/src/GitConfig.h b/src/GitConfig.h index c618c0e..83b370f 100644 --- a/src/GitConfig.h +++ b/src/GitConfig.h @@ -12,11 +12,13 @@ class GitConfig { static void initRepo(const std::filesystem::path&); bool addGitObj(const std::filesystem::path&); std::shared_ptr getRoot(); + void printTree() const; private: GitConfig(const std::filesystem::path&); void initFromConfig(const std::filesystem::path&); std::shared_ptr getFromPath(const std::filesystem::path&) const; + void printGitTreeFromFolder(const std::shared_ptr&, std::string printPrefix) const; GitConfig* conf; std::vector commitList; diff --git a/src/main.cpp b/src/main.cpp index 6d9ce23..1ac9c61 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -15,20 +15,6 @@ GitConfig& getRepoConfig() { return GitConfig::instance(currPath); } -void printGitTree(std::shared_ptr root, const std::string& prefix = "", bool isLast = true) { - std::cout << prefix << (isLast ? "└── " : "├── ") << root->getName() << "/" << std::endl; - const auto& children = root->getSubObjects(); - for (size_t i = 0; i < children.size(); ++i) { - const auto& child = children[i]; - const bool last = (i == children.size() - 1); - const std::string childPrefix = prefix + (isLast ? " " : "│ "); - if (child->isDirectory()) { - printGitTree(std::dynamic_pointer_cast(child), childPrefix, last); - } else { - std::cout << childPrefix << (last ? "└── " : "├── ") << child->getName() << std::endl; - } - } -} int main(int argc, char* argv[]) { @@ -70,7 +56,7 @@ int main(int argc, char* argv[]) { if (!config.addGitObj(addPath)) { std::cerr << "Adding this path to git failed!" << std::endl; } - printGitTree(config.getRoot()); + config.printTree(); } while (false); From 81244aa684f81338c0e19af84091d6e7bbc25fb4 Mon Sep 17 00:00:00 2001 From: Daniel Boianju Date: Fri, 6 Mar 2026 18:08:30 +0200 Subject: [PATCH 2/3] Added project instructions for cursor --- .cursor/rules/project-instructions.mdc | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .cursor/rules/project-instructions.mdc diff --git a/.cursor/rules/project-instructions.mdc b/.cursor/rules/project-instructions.mdc new file mode 100644 index 0000000..9a2054a --- /dev/null +++ b/.cursor/rules/project-instructions.mdc @@ -0,0 +1,9 @@ +--- +alwaysApply: true +--- + +This project is my implementation of git: git_d. This is purely a self-learning project in which I am trying to implement it the best way I think it should be implemented. + +I DONT WANT AI CODING IN THIS PROJECT AT ALL. As this is a project that is for self learning - I want to be the one implementing all of the code in the project. This means no cursor suggestions AT ALL unless I specifically ask for them. + +When I want to consult with you on architectural topics - I want you to reply on the real implementation of git in your answers. But only offer architectural design suggestions and explanations with pros and cons regarding these suggestions. DO NOT add code unless specifically requested. From ceb54ee447d9df999145d78689415b918d85da34 Mon Sep 17 00:00:00 2001 From: Daniel Boianju Date: Sun, 8 Mar 2026 19:26:11 +0200 Subject: [PATCH 3/3] Git add functionality now adds encoded blobs in the objects folder and elements in the index folder (staging). ConfigDiskManaged handles all disk IO and manges the index and objects files --- src/ConfigDiskManager.cpp | 127 ++++++++++++++++++++++++++++++++++++++ src/ConfigDiskManager.h | 30 +++++++++ src/GitConfig.cpp | 61 ++++++++++-------- src/GitConfig.h | 14 +++-- src/main.cpp | 33 ++++------ unix/CMakeLists.txt | 1 + 6 files changed, 214 insertions(+), 52 deletions(-) create mode 100644 src/ConfigDiskManager.cpp create mode 100644 src/ConfigDiskManager.h diff --git a/src/ConfigDiskManager.cpp b/src/ConfigDiskManager.cpp new file mode 100644 index 0000000..d3910e7 --- /dev/null +++ b/src/ConfigDiskManager.cpp @@ -0,0 +1,127 @@ +#include "CryptoUtils.h" +#include "ConfigDiskManager.h" +#include +#include +#include +#include +#include +#include +#include +#include + +static void touch_file(const std::filesystem::path& p) { + std::ofstream ofs(p, std::ios::app); + if (!ofs) throw std::runtime_error("Failed creating the file: " + p.string()); +} + +void ConfigDiskManager::initRepoDisk() { + if (std::filesystem::exists(this->configPath)) { + std::cout << "repository already initialized, .git_d already exists" << std::endl; + readIndexFiles(); + return; + } + std::error_code ec; + std::filesystem::create_directory(this->configPath, ec); + if (ec) throw std::runtime_error("Error in creating .git_d"); + std::filesystem::create_directory(this->configPath / "objects", ec); + if (ec) throw std::runtime_error("Error in creating the objects dir"); + touch_file(this->configPath / "config"); + touch_file(this->configPath / "index"); + touch_file(this->configPath / "HEAD"); +} + +ConfigDiskManager::ConfigDiskManager(const std::filesystem::path& repoPath) : configPath(repoPath / ".git_d") {} + +ConfigDiskManager& ConfigDiskManager::instance(const std::filesystem::path& repoPath) { + static ConfigDiskManager inst(repoPath); + return inst; +} + +void ConfigDiskManager::stageFile(const std::filesystem::path& p) { + std::string fileBlob = this->createBlob(p); + std::string fileHash = CryptoUtils::sha256_hex(fileBlob); + this->saveBlobToObjects(fileBlob, fileHash); + // TODO: should make sure that here or in main.cpp the path is always relative to the root repo + this->saveBlobHashToIndex(fileHash, p); +} + +std::string ConfigDiskManager::createBlob(const std::filesystem::path& p) const { + std::ifstream file(p, std::ios::binary); + if (!file) throw std::runtime_error("failed to read the file!"); + + std::string content((std::istream_iterator(file)), std::istream_iterator()); + + std::string blob = "blob" + std::to_string(content.size()) + '\0' + content; + + // TODO: should compress the blob before returning it + + return blob; +} + +void ConfigDiskManager::saveBlobToObjects(const std::string& blob, std::string hash) const { + std::filesystem::path objectsPath = this->configPath / "objects"; + assert(std::filesystem::exists(objectsPath)); + // save the two chars of the hash as a separate folder - to decrease the amount of objects + std::string twoChars = hash.substr(0, 2); + hash.erase(0,2); + std::filesystem::path blobDir = objectsPath / twoChars; + if (!std::filesystem::exists(blobDir)) std::filesystem::create_directory(blobDir); + std::filesystem::path blobPath = blobDir / hash; + std::ofstream outFile(blobPath, std::ios::binary); + if (!outFile) { + throw std::runtime_error("Failed to create blob file at " + blobPath.string()); + } + outFile.write(blob.data(), blob.size()); + outFile.close(); +} + +void ConfigDiskManager::readIndexFiles() { + assert(indexFiles.empty()); + assert(std::filesystem::exists(this->configPath)); + std::filesystem::path indexPath = this->configPath / "index"; + std::ifstream indexFile(indexPath, std::ios::binary); + if (!indexFile) { + throw std::runtime_error("Failed to open index file: " + indexPath.string()); + } + std::string line; + std::string::size_type pos; + while (std::getline(indexFile, line)) { + if (!line.empty() && line.back() == '\r') line.pop_back(); // for windows devices + if (line.empty()) continue; + pos = line.find(" "); + if (pos == std::string::npos) { + throw std::runtime_error("Failed to parse the index file!"); + } + std::string pathRelative(line.substr(0, pos)); + std::string latestFileHash(line.substr(pos + 1)); + IndexFile indexFile(pathRelative, latestFileHash); + this->indexFiles.emplace(pathRelative, indexFile); + } +} + +void ConfigDiskManager::addToIndexFiles(const std::string& pathRelative, const std::string& latestFileHash) { + auto item = this->indexFiles.find(pathRelative); + if (item != this->indexFiles.end()) { + if (item->second.latestFileHash == latestFileHash) return; + } + IndexFile indexFile(pathRelative, latestFileHash); + + // assign or overwrite the previous one + this->indexFiles.insert_or_assign(pathRelative, indexFile); + + std::filesystem::path indexPath = this->configPath / "index"; + std::ofstream indexFileStream(indexPath, std::ios::binary | std::ios::trunc); + if (!indexFileStream) { + throw std::runtime_error("Failed to open index file for writing: " + indexPath.string()); + } + for (const auto& [key, fileObj] : this->indexFiles) { + indexFileStream << fileObj.pathRelative << " " << fileObj.latestFileHash << "\n"; + } + indexFileStream.close(); +} + + +void ConfigDiskManager::saveBlobHashToIndex(const std::string& latestFileHash, const std::filesystem::path& pathReltive) { + // TODO: Make sure that all of the paths that are used here are relative to the root repo and not absolute + this->addToIndexFiles(pathReltive.string(), latestFileHash); +} \ No newline at end of file diff --git a/src/ConfigDiskManager.h b/src/ConfigDiskManager.h new file mode 100644 index 0000000..52e2085 --- /dev/null +++ b/src/ConfigDiskManager.h @@ -0,0 +1,30 @@ +#pragma once +#include +#include +#include + + +struct IndexFile { + std::string pathRelative; + std::string latestFileHash; + IndexFile(std::string _pathRelative, std::string _latestFileHash): pathRelative(_pathRelative),latestFileHash(_latestFileHash) {} +}; + + +class ConfigDiskManager { + + public: + static ConfigDiskManager& instance(const std::filesystem::path&); + void initRepoDisk(); // file to init and / or initialize all git objects + void stageFile(const std::filesystem::path&); + + private: + std::string createBlob(const std::filesystem::path&) const; + void saveBlobToObjects(const std::string&, std::string) const; + void saveBlobHashToIndex(const std::string&, const std::filesystem::path&); + ConfigDiskManager(const std::filesystem::path&); + void readIndexFiles(); + void addToIndexFiles(const std::string&, const std::string&); + const std::filesystem::path configPath; + std::map indexFiles; +}; \ No newline at end of file diff --git a/src/GitConfig.cpp b/src/GitConfig.cpp index a2ddbbe..3ca9c98 100644 --- a/src/GitConfig.cpp +++ b/src/GitConfig.cpp @@ -2,42 +2,26 @@ #include #include #include -#include -#include -#include #include +#include #include "GitConfig.h" +#include "ConfigDiskManager.h" #include "GitFolder.h" #include "GitObject.h" -static void touch_file(std::filesystem::path p) { - std::ofstream ofs(p, std::ios::app); - if (!ofs) throw std::runtime_error("Failed creating the file: " + p.string()); -} + std::shared_ptr GitConfig::getRoot() { return this->rootObject; } -void GitConfig::initRepo(const std::filesystem::path &repoPath) { - if (!std::filesystem::exists(repoPath)) throw std::runtime_error("Repository path not found"); - std::filesystem::path gitConfigPath = repoPath / ".git_d"; - if (std::filesystem::exists(gitConfigPath)) { - std::cout << "repository already initialized, .git_d already exists" << std::endl; - return; - } - std::error_code ec; - std::filesystem::create_directory(gitConfigPath, ec); - if (ec) throw std::runtime_error("Error in creating .git_d"); - std::filesystem::create_directory(gitConfigPath / "config", ec); - if (ec) throw std::runtime_error("Error in creating .git_d/config"); - touch_file(gitConfigPath / "index"); - touch_file(gitConfigPath / "HEAD"); +void GitConfig::initRepo() { + this->configDiskManager.initRepoDisk(); } -GitConfig& GitConfig::instance(const std::filesystem::path &p) { +GitConfig& GitConfig::instance() { // static initialization makes sure that it's calld only once - static GitConfig inst(p); + static GitConfig inst(std::filesystem::current_path()); return inst; } @@ -58,10 +42,8 @@ std::shared_ptr GitConfig::getFromPath(const std::filesystem::path& p } -GitConfig::GitConfig(const std::filesystem::path &p) { - if (!std::filesystem::exists(p / ".git_d")) { - initRepo(p); - } +GitConfig::GitConfig(const std::filesystem::path &p) : configDiskManager(ConfigDiskManager::instance(p)) { + initRepo(); this->rootObject = GitFolder::create(p, false); } @@ -143,4 +125,29 @@ void GitConfig::printGitTreeFromFolder(const std::shared_ptr& root, s std::cout << printPrefix << subObj->getName() << std::endl; } } +} + +bool GitConfig::isInitialized() { + return std::filesystem::exists(std::filesystem::current_path() / ".git_d"); +} + +void GitConfig::stageDir(const std::filesystem::path& p) const { + assert(std::filesystem::is_directory(p)); + for (const auto& entry : std::filesystem::directory_iterator(p)) { + const std::filesystem::path& entryPath = entry.path(); + if (std::filesystem::is_directory(entryPath)) { + this->stageDir(entryPath); + } else { + this->stageFile(entryPath); + } + } +} + +void GitConfig::stageFile(const std::filesystem::path& p) const { + assert(!std::filesystem::is_directory(p)); + try { + this->configDiskManager.stageFile(p); + } catch (std::runtime_error e) { + std::cerr << "Failed to stage file: " << e.what() << std::endl; + } } \ No newline at end of file diff --git a/src/GitConfig.h b/src/GitConfig.h index 83b370f..213865f 100644 --- a/src/GitConfig.h +++ b/src/GitConfig.h @@ -2,25 +2,29 @@ #include "GitCommit.h" #include "GitFolder.h" #include "GitObject.h" +#include "ConfigDiskManager.h" #include #include - + class GitConfig { public: - static GitConfig& instance(const std::filesystem::path&); - static void initRepo(const std::filesystem::path&); + static GitConfig& instance(); + static bool isInitialized(); bool addGitObj(const std::filesystem::path&); std::shared_ptr getRoot(); void printTree() const; - + void stageFile(const std::filesystem::path&) const; + void stageDir(const std::filesystem::path&) const; + private: + void initRepo(); GitConfig(const std::filesystem::path&); void initFromConfig(const std::filesystem::path&); std::shared_ptr getFromPath(const std::filesystem::path&) const; void printGitTreeFromFolder(const std::shared_ptr&, std::string printPrefix) const; - GitConfig* conf; + ConfigDiskManager& configDiskManager; std::vector commitList; std::shared_ptr rootObject; }; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 1ac9c61..6aff442 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,22 +1,10 @@ #include "GitConfig.h" -#include "GitFile.h" -#include "GitFolder.h" -#include "GitObject.h" #include #include #include -#include #include #include - -GitConfig& getRepoConfig() { - std::filesystem::path currPath = std::filesystem::current_path(); - return GitConfig::instance(currPath); -} - - - int main(int argc, char* argv[]) { if (argc < 2) { std::cerr << "Usage: git_d " << std::endl; @@ -26,19 +14,22 @@ int main(int argc, char* argv[]) { std::string command = argv[1]; if (command == "init") { - std::filesystem::path repoPath = std::filesystem::current_path(); if (argc >= 3) { std::cerr << "init currently does not support dynamic paths" << std::endl; - // repoPath = argv[2]; } try { - GitConfig::initRepo(repoPath); + GitConfig::instance(); // initializes the repo and creates the git folders in the repoPath } catch (const std::exception& ex) { std::cerr << "Init failed: " << ex.what() << std::endl; } return 0; } + if (!GitConfig::isInitialized()) { + std::cerr << "The repository is not initialized. Please init before doing any git acion" << std::endl; + return 0; + } + if (command == "add") { do { @@ -51,12 +42,14 @@ int main(int argc, char* argv[]) { std::cerr << "add failed. Please provide a valid path" << std::endl; break; } - addPath = std::filesystem::canonical(addPath); - GitConfig& config = getRepoConfig(); - if (!config.addGitObj(addPath)) { - std::cerr << "Adding this path to git failed!" << std::endl; + if (addPath.is_absolute()) { + std::cerr << "git only supports relative paths from the root repo" << std::endl; + } + if (std::filesystem::is_directory(addPath)) { + GitConfig::instance().stageDir(addPath); + } else { + GitConfig::instance().stageFile(addPath); } - config.printTree(); } while (false); diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index 305621b..a212427 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -19,6 +19,7 @@ add_executable(git_d ${SRC_DIR}/FileChunk.cpp ${SRC_DIR}/CryptoUtils.cpp ${SRC_DIR}/GitFile.cpp + ${SRC_DIR}/ConfigDiskManager.cpp ) target_compile_options(git_d PRIVATE