From 8e5610af29f2e43e9e28a5948dfc235661403d20 Mon Sep 17 00:00:00 2001 From: Kadoski <100347326+iamkadoski@users.noreply.github.com> Date: Mon, 13 Jan 2025 15:59:44 +0100 Subject: [PATCH 1/4] Update codekeeper.cpp --- codekeeper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codekeeper.cpp b/codekeeper.cpp index f90f219..807754e 100644 --- a/codekeeper.cpp +++ b/codekeeper.cpp @@ -76,7 +76,7 @@ std::string generateGUID() // Function to check if the repository has been initialized bool isRepositoryInitialized(const std::string &projectName) { - std::string centralOriginPath = "/usr/bin/codekeeper"; // You can change this path if needed + std::string centralOriginPath = "/usr/bin/Codekeeper"; // You can change this path if needed std::string keepDirectory = centralOriginPath + "/.keep"; // Path to the .keep directory std::string repoName = "." + projectName; std::string repositoryPath = keepDirectory + "/" + repoName; // Path to the project folder under .keep From 70fd29f43aa0ee631a60da5716fb7bd23dff765a Mon Sep 17 00:00:00 2001 From: Kadoski <100347326+iamkadoski@users.noreply.github.com> Date: Mon, 13 Jan 2025 16:22:54 +0100 Subject: [PATCH 2/4] Update codekeeper.cpp --- codekeeper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codekeeper.cpp b/codekeeper.cpp index 807754e..c5c9fb9 100644 --- a/codekeeper.cpp +++ b/codekeeper.cpp @@ -228,7 +228,7 @@ void initRepository(const std::string &projectName) std::string loadRepositoryPath() { - std::string centralConfigFile = "/usr/bin/codekeeper/codekeeper_config"; + std::string centralConfigFile = "/usr/bin/Codekeeper/codekeeper_config"; std::ifstream configFile(centralConfigFile); if (!configFile.is_open()) From 7f635106432396a4aafd5e0abaf7b48085b980e5 Mon Sep 17 00:00:00 2001 From: Kadoski <100347326+iamkadoski@users.noreply.github.com> Date: Thu, 10 Apr 2025 18:11:36 +0100 Subject: [PATCH 3/4] inclusion of file hasinging, character escape and init repo review --- codekeeper.cpp | 812 ++++++++++++++++--------------------------------- 1 file changed, 261 insertions(+), 551 deletions(-) diff --git a/codekeeper.cpp b/codekeeper.cpp index c5c9fb9..23150ce 100644 --- a/codekeeper.cpp +++ b/codekeeper.cpp @@ -7,18 +7,15 @@ #include #include #include -#include -#include -#include -#include +#include #include -#include // For getuid() +#include +#include namespace fs = std::filesystem; // Structure to hold metadata for commits -struct Commit -{ +struct Commit { std::string message; std::string timestamp; std::vector filePaths; @@ -26,453 +23,245 @@ struct Commit }; std::string repositoryPath; -std::string projectName; // Function to generate a timestamp -std::string getTimestamp() -{ +std::string getTimestamp() { std::time_t now = std::time(nullptr); char buf[80]; std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", std::localtime(&now)); return buf; } -// Function to split a string by delimiter -std::vector splitString(const std::string &str, char delimiter) +// utility function +std::vector split(const std::string &str, char delimiter) { std::vector tokens; - std::istringstream iss(str); + std::stringstream ss(str); std::string token; - while (std::getline(iss, token, delimiter)) + while (std::getline(ss, token, delimiter)) { tokens.push_back(token); } return tokens; } -std::string generateGUID() +// Function to split a string by delimiter +std::vector splitString(const std::string& str, char delimiter) { + std::vector tokens; + std::istringstream iss(str); + std::string token; + while (std::getline(iss, token, delimiter)) { + tokens.push_back(token); + } + return tokens; +} + +bool filesAreEqual(const std::string &filePath1, const std::string &filePath2) { - // Get the current timestamp - auto timestamp = std::chrono::high_resolution_clock::now().time_since_epoch().count(); + std::ifstream file1(filePath1, std::ios::binary); + std::ifstream file2(filePath2, std::ios::binary); + return std::equal(std::istreambuf_iterator(file1), std::istreambuf_iterator(), + std::istreambuf_iterator(file2)); +} - // Generate random components - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution dis(0, 15); // For hex characters +// Function to load the repository path - std::stringstream guidStream; - guidStream << std::hex << std::setw(8) << std::setfill('0') << (timestamp & 0xFFFFFFFF); - guidStream << "-"; - for (int i = 0; i < 3; ++i) - { - guidStream << std::setw(4) << std::setfill('0') << dis(gen); - guidStream << "-"; +// Escape special characters for log safety +std::string escape(const std::string& input) { + std::string output; + for (char c : input) { + if (c == '|') output += "\\|"; + else output += c; } - guidStream << std::setw(12) << std::setfill('0') << dis(gen); - - return guidStream.str(); + return output; } -// Function to check if the repository has been initialized -bool isRepositoryInitialized(const std::string &projectName) -{ - std::string centralOriginPath = "/usr/bin/Codekeeper"; // You can change this path if needed - std::string keepDirectory = centralOriginPath + "/.keep"; // Path to the .keep directory - std::string repoName = "." + projectName; - std::string repositoryPath = keepDirectory + "/" + repoName; // Path to the project folder under .keep +// Function to compute SHA-256 hash of a file (simple implementation) +#include - // Check if the .keep directory exists and the project folder is there - if (fs::exists(keepDirectory) && fs::exists(repositoryPath)) - { - std::string projectDetailsFile = repositoryPath + "/projectdetails"; - if (fs::exists(projectDetailsFile)) - { - return true; // Repository is initialized - } - } +std::string computeFileHash(const fs::path& filePath) { + std::ifstream file(filePath, std::ios::binary); + if (!file) return ""; - return false; // Repository not initialized -} + EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); + if (!mdctx) return ""; -// Function to load the repository path -void initRepository(const std::string &projectName) -{ - if (projectName.empty()) - { - std::cerr << "Error: Project name cannot be empty.\n"; - return; - } + const EVP_MD* md = EVP_sha256(); + if (1 != EVP_DigestInit_ex(mdctx, md, nullptr)) return ""; - // Ensure the application has proper permissions - if (getuid() != 0) - { // Check if running as root - std::cerr << "Error: You must run 'codekeeper init' as root to set up the central repository.\n"; - return; + char buffer[8192]; + while (file.good()) { + file.read(buffer, sizeof(buffer)); + if (1 != EVP_DigestUpdate(mdctx, buffer, file.gcount())) return ""; } - std::string centralOriginPath = "/usr/bin/Codekeeper"; // You can change this path if needed + unsigned char hash[EVP_MAX_MD_SIZE]; + unsigned int hashLen = 0; + if (1 != EVP_DigestFinal_ex(mdctx, hash, &hashLen)) return ""; - // Create central origin directory if it doesn't exist - if (!fs::exists(centralOriginPath)) - { - fs::create_directories(centralOriginPath); + EVP_MD_CTX_free(mdctx); + + std::ostringstream result; + for (unsigned int i = 0; i < hashLen; ++i) { + result << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i]; } - std::string centralConfigFile = centralOriginPath + "/codekeeper_config"; + return result.str(); +} - std::string repoName = "." + projectName; - std::string keepDirectory = centralOriginPath + "/.keep"; // Create the .keep directory within the central origin path - std::string repositoryPath = keepDirectory + "/" + repoName; // Place the project folder under .keep - // Create the .keep directory if it doesn't exist - if (!fs::exists(keepDirectory)) - { - fs::create_directory(keepDirectory); +// Function to load the repository path +void loadRepositoryPath() { + std::ifstream repoFile(".repo_path"); + if (repoFile.is_open()) { + std::getline(repoFile, repositoryPath); + repoFile.close(); + } else { + std::cerr << "Error: Repository not initialized. Run 'codekeeper init'.\n"; + repositoryPath.clear(); + } +} + +// Initialize the repository +void initRepository(const std::string& projectName) { + std::string repoName = projectName.empty() ? fs::current_path().filename().string() : projectName; + fs::path baseDir = fs::current_path(); + fs::path repoPath = fs::weakly_canonical(baseDir / fs::path(repoName).filename()); + + if (!fs::exists(repoPath)) { + fs::create_directory(repoPath); } - // Create the project details file in the local repo - std::ofstream repoFile(keepDirectory + "/projectdetails"); + repositoryPath = repoPath.string(); + + std::ofstream repoFile(".repo_path"); repoFile << repositoryPath; repoFile.close(); - // Create the ".versions" folder inside the project folder - fs::create_directories(repositoryPath + "/.versions"); + fs::create_directory(repoPath / "versions"); - // Create the .bypass file inside the hidden repository - std::ofstream bypassFile(repositoryPath + "/.bypass"); + std::ofstream bypassFile(repoPath / ".bypass"); bypassFile << "# Add files or patterns to ignore\n"; bypassFile.close(); - // Create the commit_log.txt file - std::ofstream logFile(repositoryPath + "/commit_log.txt"); + std::ofstream logFile(repoPath / "commit_log.txt"); logFile.close(); - // Write the central repository path to the global config - std::ofstream centralConfig(centralConfigFile); - if (centralConfig.is_open()) - { - centralConfig << "central_repository_path=" << repositoryPath << "\n"; - centralConfig.close(); - } - else - { - std::cerr << "Error: Unable to write to central configuration file.\n"; - return; - } - std::cout << "Repository '" << repositoryPath << "' initialized successfully.\n"; - std::cout << "Central repository configured in '" << centralConfigFile << "'.\n"; + std::cout << "Change your terminal to the project folder: cd " << repositoryPath << "\n"; } -// // Function to load the repository path -// void initRepository(const std::string& projectName) { -// if (projectName.empty()) { -// std::cerr << "Error: Project name cannot be empty.\n"; -// return; -// } - -// // Ensure the application has proper permissions -// if (getuid() != 0) { // Check if running as root -// std::cerr << "Error: You must run 'codekeeper init' as root to set up the central repository.\n"; -// return; -// } - -// std::string centralOriginPath = "/usr/bin/codekeeper"; // You can change this path if needed - -// // Create central origin directory if it doesn't exist -// if (!fs::exists(centralOriginPath)) { -// fs::create_directories(centralOriginPath); -// } - -// std::string centralConfigFile = centralOriginPath + "/codekeeper_config"; - -// std::string repoName = "." + projectName; -// std::string keepDirectory = centralOriginPath + "/.keep"; // Create the .keep directory within the central origin path -// std::string repositoryPath = keepDirectory + "/" + repoName; // Place the project folder under .keep - -// // Create the .keep directory if it doesn't exist -// if (!fs::exists(keepDirectory)) { -// fs::create_directory(keepDirectory); -// } - -// // Create the project details file in the local repo -// std::ofstream repoFile(keepDirectory + "/projectdetails"); -// repoFile << repositoryPath; -// repoFile.close(); - -// // Create the ".versions" folder inside the project folder -// fs::create_directories(repositoryPath + "/.versions"); - -// // Create the .bypass file inside the hidden repository -// std::ofstream bypassFile(repositoryPath + "/.bypass"); -// bypassFile << "# Add files or patterns to ignore\n"; -// bypassFile.close(); - -// // Create the commit_log.txt file -// std::ofstream logFile(repositoryPath + "/commit_log.txt"); -// logFile.close(); - -// // Write the central repository path to the global config -// std::ofstream centralConfig(centralConfigFile); -// if (centralConfig.is_open()) { -// centralConfig << "central_repository_path=" << repositoryPath << "\n"; -// centralConfig.close(); -// } else { -// std::cerr << "Error: Unable to write to central configuration file.\n"; -// return; -// } - -// std::cout << "Repository '" << repositoryPath << "' initialized successfully.\n"; -// std::cout << "Central repository configured in '" << centralConfigFile << "'.\n"; -// } - -std::string loadRepositoryPath() -{ - std::string centralConfigFile = "/usr/bin/Codekeeper/codekeeper_config"; - std::ifstream configFile(centralConfigFile); +// Function to commit multiple files +void commitFiles(const std::vector& filePaths, const std::string& commitMessage) { + loadRepositoryPath(); + fs::path repoPath = fs::weakly_canonical(repositoryPath); + fs::path versionDir = repoPath / "versions"; - if (!configFile.is_open()) - { - std::cerr << "Error: Central configuration file not found. Run 'codekeeper init'.\n"; - return ""; + if (repositoryPath.empty() || !fs::exists(versionDir)) { + std::cerr << "Error: Repository not initialized. Run 'codekeeper init'.\n"; + return; } - std::string line, repositoryPath; - while (std::getline(configFile, line)) - { - if (line.find("central_repository_path=") == 0) - { - repositoryPath = line.substr(line.find('=') + 1); - break; + std::ifstream bypassFile(repoPath / ".bypass"); + std::vector ignoredFiles; + std::string line; + while (std::getline(bypassFile, line)) { + if (!line.empty() && line[0] != '#') { + ignoredFiles.push_back(line); } } - if (repositoryPath.empty()) - { - std::cerr << "Error: Central repository path not set in the configuration file.\n"; + std::vector versionPaths; + std::vector fileHashes; + for (const auto& filePath : filePaths) { + fs::path file = fs::absolute(filePath); + + if (!fs::exists(file)) { + std::cerr << "Error: File " << file << " does not exist.\n"; + return; + } + + if (std::find(ignoredFiles.begin(), ignoredFiles.end(), filePath) != ignoredFiles.end()) { + std::cout << "Skipping ignored file: " << filePath << "\n"; + continue; + } + + std::string versionFile = "version_" + std::to_string(std::time(nullptr)) + "_" + file.filename().string(); + fs::path versionFilePath = versionDir / versionFile; + + fs::copy(file, versionFilePath, fs::copy_options::overwrite_existing); + versionPaths.push_back(versionFilePath.string()); + fileHashes.push_back(computeFileHash(file)); } - return repositoryPath; -} + std::string timestamp = getTimestamp(); + std::ofstream logFile(repoPath / "commit_log.txt", std::ios::app); + logFile << escape(commitMessage) << "|" << timestamp; -std::string getCentralRepositoryPath() -{ - std::ifstream configFile(".codekeeper_config"); - std::string repositoryPath; - if (configFile.is_open()) - { - std::getline(configFile, repositoryPath); + for (size_t i = 0; i < filePaths.size(); ++i) { + logFile << "|" << escape(fs::absolute(filePaths[i]).string()) + << "|" << fileHashes[i]; } - return repositoryPath; -} -void setCentralRepositoryPath(const std::string &path) -{ - std::ofstream configFile(".codekeeper_config"); - configFile << path; + logFile << "|"; + + for (const auto& versionPath : versionPaths) { + logFile << versionPath << "|"; + } + + logFile << "\n"; + logFile.close(); + std::cout << "Files committed successfully with message: " << commitMessage << "\n"; } // Function to retrieve files by commit message -void retrieveFiles(const std::string &commitMessage) -{ - if (repositoryPath.empty() || !fs::exists(repositoryPath + "/commit_log.txt")) - { - std::cerr << "Error: Repository not initialized or log file missing.\n"; - return; - } +void retrieveFiles(const std::string& commitMessage) { + loadRepositoryPath(); + fs::path repoPath = fs::weakly_canonical(repositoryPath); + fs::path logPath = repoPath / "commit_log.txt"; - std::ifstream logFile(repositoryPath + "/commit_log.txt"); - if (!logFile) - { - std::cerr << "Error: Commit log file not found.\n"; + if (repositoryPath.empty() || !fs::exists(logPath)) { + std::cerr << "Error: Repository not initialized or log file missing.\n"; return; } + std::ifstream logFile(logPath); std::string line; - while (std::getline(logFile, line)) - { + + while (std::getline(logFile, line)) { size_t pos = line.find('|'); std::string message = line.substr(0, pos); - if (message == commitMessage) - { + if (message == escape(commitMessage)) { size_t pos2 = line.find('|', pos + 1); size_t pos3 = line.find_last_of('|'); std::string fileData = line.substr(pos2 + 1, pos3 - pos2 - 1); std::vector tokens = splitString(fileData, '|'); - size_t half = tokens.size() / 2; + std::vector originalPaths, hashes; - for (size_t i = 0; i < half; ++i) - { - fs::copy(tokens[half + i], fs::path(tokens[i]).filename(), - fs::copy_options::overwrite_existing); + for (size_t i = 0; i + 1 < tokens.size(); i += 2) { + originalPaths.push_back(tokens[i]); + hashes.push_back(tokens[i + 1]); + } + + size_t half = tokens.size() / 2; + for (size_t i = 0; i < originalPaths.size(); ++i) { + fs::path dest = fs::current_path() / fs::path(originalPaths[i]).filename(); + fs::copy(tokens[half + i * 2], dest, fs::copy_options::overwrite_existing); } std::cout << "Files retrieved successfully.\n"; - logFile.close(); return; } } std::cerr << "Error: Commit message not found.\n"; - logFile.close(); -} - -// utility function -std::vector split(const std::string &str, char delimiter) -{ - std::vector tokens; - std::stringstream ss(str); - std::string token; - while (std::getline(ss, token, delimiter)) - { - tokens.push_back(token); - } - return tokens; -} - -bool filesAreEqual(const std::string &filePath1, const std::string &filePath2) -{ - std::ifstream file1(filePath1, std::ios::binary); - std::ifstream file2(filePath2, std::ios::binary); - return std::equal(std::istreambuf_iterator(file1), std::istreambuf_iterator(), - std::istreambuf_iterator(file2)); -} - -// function to View History -void viewHistory() -{ - repositoryPath = loadRepositoryPath(); - - if (repositoryPath.empty()) - { - std::cerr << "Error: Repository not initialized. Run 'codekeeper init'.\n"; - return; - } - - std::ifstream logFile(repositoryPath + "/commit_log.txt"); - if (!logFile.is_open()) - { - std::cerr << "Error: Commit log file not found.\n"; - return; - } - - std::string line; - while (std::getline(logFile, line)) - { - - // Commit format: GUID|Message|Timestamp|File1|File2|...|VersionPath1|VersionPath2|... - std::vector tokens = split(line, '|'); - if (tokens.size() < 4) - continue; // Skip malformed entries - - std::cout << "Commit GUID: " << tokens[0] << "\n"; - std::cout << "Message: " << tokens[1] << "\n"; - std::cout << "Timestamp: " << tokens[2] << "\n"; - std::cout << "Files:\n"; - for (size_t i = 3; i < tokens.size() - (tokens.size() - 3) / 2; ++i) - { - std::cout << " - " << tokens[i] << "\n"; - } - std::cout << "------------------------\n"; - } - - logFile.close(); -} - -// function for Conflict resolution -bool checkConflicts(const std::string &filePath) -{ - repositoryPath = loadRepositoryPath(); - - if (repositoryPath.empty()) - { - std::cerr << "Error: Repository not initialized. Run 'codekeeper init'.\n"; - return false; - } - - std::ifstream logFile(repositoryPath + "/commit_log.txt"); - if (!logFile.is_open()) - { - std::cerr << "Error: Commit log file not found.\n"; - return false; - } - - std::string latestVersion; - std::string line; - while (std::getline(logFile, line)) - { - std::vector tokens = split(line, '|'); - if (std::find(tokens.begin() + 3, tokens.end(), filePath) != tokens.end()) - { - size_t versionIndex = 3 + (tokens.size() - 3) / 2; - for (size_t i = versionIndex; i < tokens.size(); ++i) - { - if (fs::path(tokens[i]).filename() == fs::path(filePath).filename()) - { - latestVersion = tokens[i]; - break; - } - } - } - } - - if (!latestVersion.empty() && fs::exists(filePath)) - { - // Compare latest version with the current file - if (!filesAreEqual(filePath, latestVersion)) - { - std::cerr << "Conflict detected in file: " << filePath << "\n"; - return true; - } - } - - return false; -} - -void resolveConflict(const std::string &filePath, const std::string &resolutionPath) -{ - fs::copy(resolutionPath, filePath, fs::copy_options::overwrite_existing); - std::cout << "Conflict resolved for " << filePath << " using " << resolutionPath << "\n"; -} - -// function foir archiving - -void archiveVersions() -{ - repositoryPath = loadRepositoryPath(); - - if (repositoryPath.empty()) - { - std::cerr << "Error: Repository not initialized. Run 'codekeeper init'.\n"; - return; - } - - std::string versionsPath = repositoryPath + "/.versions"; - if (!fs::exists(versionsPath)) - { - std::cerr << "Error: No .versions folder found.\n"; - return; - } - - std::string archivePath = repositoryPath + "/.versions_archive_" + getTimestamp() + ".zip"; - - // Use a system call to zip the .versions folder (requires zip utility installed) - std::string command = "zip -r " + archivePath + " " + versionsPath; - if (system(command.c_str()) == 0) - { - std::cout << "Archived .versions to " << archivePath << "\n"; - } - else - { - std::cerr << "Error: Failed to archive .versions folder.\n"; - } } // Function for Rollback void rollback(const std::string &target, const std::string &commitGUID = "") { - repositoryPath = loadRepositoryPath(); + loadRepositoryPath(); if (repositoryPath.empty()) { @@ -520,12 +309,36 @@ void rollback(const std::string &target, const std::string &commitGUID = "") std::cout << "Rolled back " << target << " to version: " << foundVersionPath << "\n"; } + + + +// Function to check if the repository has been initialized +bool isRepositoryInitialized(const std::string &projectName) +{ + std::string centralOriginPath = "/usr/bin/codekeeper"; // You can change this path if needed + std::string keepDirectory = centralOriginPath + "/.keep"; // Path to the .keep directory + std::string repoName = "." + projectName; + std::string repositoryPath = keepDirectory + "/" + repoName; // Path to the project folder under .keep + + // Check if the .keep directory exists and the project folder is there + if (fs::exists(keepDirectory) && fs::exists(repositoryPath)) + { + std::string projectDetailsFile = repositoryPath + "/projectdetails"; + if (fs::exists(projectDetailsFile)) + { + return true; // Repository is initialized + } + } + + return false; // Repository not initialized +} + // Function to create a new branch void createBranch(const std::string &branchName) { if (repositoryPath.empty()) { - repositoryPath = loadRepositoryPath(); + loadRepositoryPath(); if (repositoryPath.empty()) { return; @@ -548,48 +361,10 @@ void createBranch(const std::string &branchName) fs::create_directory(branchPath); std::cout << "Branch '" << branchName << "' created successfully.\n"; } - -// Function to collect all files from a directory -void collectFilesFromDirectory(const std::string &dirPath, std::vector &files, const std::vector &ignoredFiles) -{ - for (const auto &entry : std::filesystem::recursive_directory_iterator(dirPath)) - { - if (entry.is_regular_file()) - { - std::string filePath = entry.path().string(); - if (std::find(ignoredFiles.begin(), ignoredFiles.end(), filePath) == ignoredFiles.end()) - { - files.push_back(filePath); - } - else - { - std::cout << "Skipping ignored file: " << filePath << "\n"; - } - } - } -} - -// Function to expand wildcards for files and directories -void expandWildcard(const std::string &pattern, std::vector &files) -{ - std::regex re(pattern); - for (const auto &entry : std::filesystem::directory_iterator(".")) - { - if (std::filesystem::is_regular_file(entry) || std::filesystem::is_directory(entry)) - { - std::string entryPath = entry.path().string(); - if (std::regex_match(entryPath, re)) - { - files.push_back(entryPath); - } - } - } -} - -// Function to commit files -void commitFiles(const std::vector &filePaths, const std::string &commitMessage) +// function to View History +void viewHistory() { - repositoryPath = loadRepositoryPath(); + loadRepositoryPath(); if (repositoryPath.empty()) { @@ -597,174 +372,93 @@ void commitFiles(const std::vector &filePaths, const std::string &c return; } - // Generate a unique GUID for this commit - std::string commitID = generateGUID(); - std::cout << "Commit ID: " << commitID << "\n"; // Optional, for debugging - - // Load ignored files from .bypass - std::ifstream bypassFile(repositoryPath + "/.bypass"); - std::vector ignoredFiles; - std::string line; - while (std::getline(bypassFile, line)) + std::ifstream logFile(repositoryPath + "/commit_log.txt"); + if (!logFile.is_open()) { - if (!line.empty() && line[0] != '#') - { - ignoredFiles.push_back(line); - } + std::cerr << "Error: Commit log file not found.\n"; + return; } - std::vector allFiles; - for (const auto &filePath : filePaths) + std::string line; + while (std::getline(logFile, line)) { - if (!fs::exists(filePath)) - { - std::cerr << "Error: File or directory " << filePath << " does not exist.\n"; - continue; - } - if (fs::is_regular_file(filePath)) - { - if (std::find(ignoredFiles.begin(), ignoredFiles.end(), filePath) == ignoredFiles.end()) - { - allFiles.push_back(filePath); - } - else - { - std::cout << "Skipping ignored file: " << filePath << "\n"; - } - } - else if (fs::is_directory(filePath)) - { - collectFilesFromDirectory(filePath, allFiles, ignoredFiles); - } - else + // Commit format: GUID|Message|Timestamp|File1|File2|...|VersionPath1|VersionPath2|... + std::vector tokens = split(line, '|'); + if (tokens.size() < 4) + continue; // Skip malformed entries + + std::cout << "Commit GUID: " << tokens[0] << "\n"; + std::cout << "Message: " << tokens[1] << "\n"; + std::cout << "Timestamp: " << tokens[2] << "\n"; + std::cout << "Files:\n"; + for (size_t i = 3; i < tokens.size() - (tokens.size() - 3) / 2; ++i) { - std::cerr << "Error: Unsupported file type for " << filePath << ".\n"; + std::cout << " - " << tokens[i] << "\n"; } + std::cout << "------------------------\n"; } - // Commit the collected files - std::vector versionPaths; - for (const auto &filePath : allFiles) - { - std::string versionFile = repositoryPath + "/.versions/version_" + commitID + "_" + std::to_string(std::time(nullptr)) + - "_" + fs::path(filePath).filename().string(); - fs::copy(filePath, versionFile, fs::copy_options::overwrite_existing); - versionPaths.push_back(versionFile); - } - - std::string timestamp = getTimestamp(); - std::ofstream logFile(repositoryPath + "/commit_log.txt", std::ios::app); - logFile << commitMessage << "|" << commitID << "|" << timestamp; - - for (const auto &filePath : allFiles) - { - logFile << "|" << filePath; - } - logFile << "|"; - - for (const auto &versionPath : versionPaths) - { - logFile << versionPath << "|"; - } - - logFile << "\n"; logFile.close(); - std::cout << "Files committed successfully with message: " << commitMessage << "\n"; } -// Function to display help message -void displayHelp() +// function for Conflict resolution +bool checkConflicts(const std::string &filePath) { - std::cout << "CodeKeeper Help:\n"; - std::cout << "Available Commands:\n"; - std::cout << " init Initialize a new repository.\n"; - std::cout << " commit [files] Commit specified files or directories.\n"; - std::cout << " Use '*.*' or '.' to commit all files.\n"; - std::cout << " rollback [file|guid] Revert a file or repository to a specific version.\n"; - std::cout << " history View commit history.\n"; - std::cout << " conflicts [file] Check for conflicts in a file.\n"; - std::cout << " resolve [file] [res] Resolve a conflict with the specified resolution file.\n"; - std::cout << " archive Archive the .versions folder.\n"; - std::cout << " auth Authenticate a user.\n"; - std::cout << " merge [branch1 branch2] Merge changes from two branches.\n"; - std::cout << "\nAuthentication:\n"; - std::cout << " Users must authenticate using a valid username and password.\n"; - std::cout << " Only authenticated users can commit, rollback, or resolve conflicts.\n"; - std::cout << "\nFor more details, consult the documentation.\n"; -} + loadRepositoryPath(); -// merging files -void mergeFiles(const std::string &file1, const std::string &file2, const std::string &outputPath) -{ - std::ifstream input1(file1); - std::ifstream input2(file2); - std::ofstream output(outputPath); + if (repositoryPath.empty()) + { + std::cerr << "Error: Repository not initialized. Run 'codekeeper init'.\n"; + return false; + } - if (!input1.is_open() || !input2.is_open() || !output.is_open()) + std::ifstream logFile(repositoryPath + "/commit_log.txt"); + if (!logFile.is_open()) { - std::cerr << "Error: Unable to open one or more files for merging.\n"; - return; + std::cerr << "Error: Commit log file not found.\n"; + return false; } - std::string line1, line2; - while (std::getline(input1, line1) || std::getline(input2, line2)) + std::string latestVersion; + std::string line; + while (std::getline(logFile, line)) { - if (!line1.empty() && !line2.empty() && line1 != line2) + std::vector tokens = split(line, '|'); + if (std::find(tokens.begin() + 3, tokens.end(), filePath) != tokens.end()) { - // Conflict: Append both lines with markers - output << "<<<<<<< " << file1 << "\n" - << line1 << "\n=======\n" - << line2 << "\n>>>>>>>\n"; + size_t versionIndex = 3 + (tokens.size() - 3) / 2; + for (size_t i = versionIndex; i < tokens.size(); ++i) + { + if (fs::path(tokens[i]).filename() == fs::path(filePath).filename()) + { + latestVersion = tokens[i]; + break; + } + } } - else + } + + if (!latestVersion.empty() && fs::exists(filePath)) + { + // Compare latest version with the current file + if (!filesAreEqual(filePath, latestVersion)) { - // No conflict: Append whichever line is available - output << (!line1.empty() ? line1 : line2) << "\n"; + std::cerr << "Conflict detected in file: " << filePath << "\n"; + return true; } - line1.clear(); - line2.clear(); } - input1.close(); - input2.close(); - output.close(); - std::cout << "Merge complete. Output written to: " << outputPath << "\n"; + return false; } -// merging branches -void mergeBranches(const std::string &branch1, const std::string &branch2) +void resolveConflict(const std::string &filePath, const std::string &resolutionPath) { - repositoryPath = getCentralRepositoryPath(); - - if (repositoryPath.empty()) - { - std::cerr << "Error: Central repository not configured.\n"; - return; - } - - std::string branch1Path = repositoryPath + "/branches/" + branch1; - std::string branch2Path = repositoryPath + "/branches/" + branch2; - - if (!fs::exists(branch1Path) || !fs::exists(branch2Path)) - { - std::cerr << "Error: One or both branches do not exist.\n"; - return; - } - - for (const auto &entry : fs::directory_iterator(branch1Path)) - { - std::string file1 = entry.path(); - std::string file2 = branch2Path + "/" + fs::path(file1).filename().string(); - if (fs::exists(file2)) - { - std::string outputFile = branch1Path + "/merged_" + fs::path(file1).filename().string(); - mergeFiles(file1, file2, outputFile); - } - } - std::cout << "Branch merge complete. Resolve conflicts in the merged files if necessary.\n"; + fs::copy(resolutionPath, filePath, fs::copy_options::overwrite_existing); + std::cout << "Conflict resolved for " << filePath << " using " << resolutionPath << "\n"; } + void moveToGitRepo(const std::string &folderPath, const std::string &repoPath) { // Ensure the source folder exists @@ -850,6 +544,27 @@ void convertToGitRepo(const std::string &folderPath) } } +// Function to display help message +void displayHelp() +{ + std::cout << "CodeKeeper Help:\n"; + std::cout << "Available Commands:\n"; + std::cout << " init Initialize a new repository.\n"; + std::cout << " commit [files] Commit specified files or directories.\n"; + std::cout << " Use '*.*' or '.' to commit all files.\n"; + std::cout << " rollback [file|guid] Revert a file or repository to a specific version.\n"; + std::cout << " history View commit history.\n"; + std::cout << " conflicts [file] Check for conflicts in a file.\n"; + std::cout << " resolve [file] [res] Resolve a conflict with the specified resolution file.\n"; + std::cout << " archive Archive the .versions folder.\n"; + std::cout << " auth Authenticate a user.\n"; + std::cout << " merge [branch1 branch2] Merge changes from two branches.\n"; + std::cout << "\nAuthentication:\n"; + std::cout << " Users must authenticate using a valid username and password.\n"; + std::cout << " Only authenticated users can commit, rollback, or resolve conflicts.\n"; + std::cout << "\nFor more details, consult the documentation.\n"; +} + int main(int argc, char *argv[]) { if (argc < 2) @@ -923,11 +638,6 @@ int main(int argc, char *argv[]) { viewHistory(); - } - else if (command == "archive") - { - - archiveVersions(); } else if (command == "conflicts") { From 6cf06bd20cf42abd397e0e4e9680458603a4bb70 Mon Sep 17 00:00:00 2001 From: Kadoski <100347326+iamkadoski@users.noreply.github.com> Date: Mon, 14 Apr 2025 17:48:10 +0100 Subject: [PATCH 4/4] Update codekeeper.cpp --- codekeeper.cpp | 269 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 222 insertions(+), 47 deletions(-) diff --git a/codekeeper.cpp b/codekeeper.cpp index 23150ce..9d805c9 100644 --- a/codekeeper.cpp +++ b/codekeeper.cpp @@ -10,8 +10,11 @@ #include #include #include - +#include +#include #include + + namespace fs = std::filesystem; // Structure to hold metadata for commits @@ -24,11 +27,13 @@ struct Commit { std::string repositoryPath; + // Function to generate a timestamp std::string getTimestamp() { std::time_t now = std::time(nullptr); char buf[80]; - std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", std::localtime(&now)); + std::strftime(buf, sizeof(buf), "%Y_%m_%d_%H_%M_%S", std::localtime(&now)); + return buf; } @@ -64,8 +69,6 @@ bool filesAreEqual(const std::string &filePath1, const std::string &filePath2) std::istreambuf_iterator(file2)); } -// Function to load the repository path - // Escape special characters for log safety std::string escape(const std::string& input) { std::string output; @@ -76,11 +79,9 @@ std::string escape(const std::string& input) { return output; } -// Function to compute SHA-256 hash of a file (simple implementation) -#include - +/// Function to compute SHA-256 hash of a file (simple implementation) std::string computeFileHash(const fs::path& filePath) { - std::ifstream file(filePath, std::ios::binary); + std::ifstream file(filePath, std::ios::binary); if (!file) return ""; EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); @@ -109,6 +110,17 @@ std::string computeFileHash(const fs::path& filePath) { return result.str(); } +//function to get current repo path +std::string getCentralRepositoryPath() +{ + std::ifstream configFile(".repo_path"); + std::string repositoryPath; + if (configFile.is_open()) + { + std::getline(configFile, repositoryPath); + } + return repositoryPath; +} // Function to load the repository path void loadRepositoryPath() { @@ -123,10 +135,25 @@ void loadRepositoryPath() { } // Initialize the repository -void initRepository(const std::string& projectName) { - std::string repoName = projectName.empty() ? fs::current_path().filename().string() : projectName; - fs::path baseDir = fs::current_path(); - fs::path repoPath = fs::weakly_canonical(baseDir / fs::path(repoName).filename()); +void initRepository(const std::string& projectName) +{ + // std::string repoName = projectName.empty() ? fs::current_path().filename().string() : projectName; + // fs::path baseDir = fs::current_path(); + // fs::path repoPath = fs::weakly_canonical(baseDir / fs::path(repoName).filename()); + +fs::path baseDir = "/var/lib/CodeKeeper"; // A safer, writable location +if (!fs::exists(baseDir)) { + fs::create_directories(baseDir); +} + +std::regex safeName("^[a-zA-Z0-9_-]+$"); +if (!std::regex_match(projectName, safeName)) { + throw std::runtime_error("Invalid project name: only letters, digits, _ and - are allowed."); +} + +std::string repoName = projectName.empty() ? baseDir.filename().string() : projectName; +fs::path repoPath = fs::weakly_canonical(baseDir / fs::path(repoName).filename()); + if (!fs::exists(repoPath)) { fs::create_directory(repoPath); @@ -140,7 +167,7 @@ void initRepository(const std::string& projectName) { fs::create_directory(repoPath / "versions"); - std::ofstream bypassFile(repoPath / ".bypass"); + std::ofstream bypassFile(fs::current_path() / ".bypass"); bypassFile << "# Add files or patterns to ignore\n"; bypassFile.close(); @@ -152,7 +179,8 @@ void initRepository(const std::string& projectName) { } // Function to commit multiple files -void commitFiles(const std::vector& filePaths, const std::string& commitMessage) { +void commitFiles(const std::vector& filePaths, const std::string& commitMessage) +{ loadRepositoryPath(); fs::path repoPath = fs::weakly_canonical(repositoryPath); fs::path versionDir = repoPath / "versions"; @@ -162,7 +190,7 @@ void commitFiles(const std::vector& filePaths, const std::string& c return; } - std::ifstream bypassFile(repoPath / ".bypass"); + std::ifstream bypassFile(fs::current_path() / ".bypass"); std::vector ignoredFiles; std::string line; while (std::getline(bypassFile, line)) { @@ -261,15 +289,14 @@ void retrieveFiles(const std::string& commitMessage) { // Function for Rollback void rollback(const std::string &target, const std::string &commitGUID = "") { - loadRepositoryPath(); - - if (repositoryPath.empty()) + std::string repopath = getCentralRepositoryPath(); + if (repopath.empty()) { std::cerr << "Error: Repository not initialized. Run 'codekeeper init'.\n"; return; } - std::ifstream logFile(repositoryPath + "/commit_log.txt"); + std::ifstream logFile(repopath + "/commit_log.txt"); if (!logFile.is_open()) { std::cerr << "Error: Commit log file not found.\n"; @@ -280,38 +307,58 @@ void rollback(const std::string &target, const std::string &commitGUID = "") while (std::getline(logFile, line)) { std::vector tokens = split(line, '|'); - if (tokens.size() < 4) - continue; + if (tokens.size() < 4) continue; - if ((!commitGUID.empty() && tokens[0] == commitGUID) || - (commitGUID.empty() && std::find(tokens.begin() + 3, tokens.end(), target) != tokens.end())) + std::string message = tokens[0]; + std::string timestamp = tokens[1]; + + size_t totalTokens = tokens.size(); + size_t fileHashCount = (totalTokens - 2) / 2; // after message and timestamp + size_t versionStart = 2 + fileHashCount; + + // If commitGUID is provided, match it exactly + if (!commitGUID.empty() && message != commitGUID) continue; + + for (size_t i = 2; i < versionStart; i += 2) { - // Found matching commit or file - size_t versionIndex = 3 + (tokens.size() - 3) / 2; - for (size_t i = versionIndex; i < tokens.size(); ++i) + std::string filePath = tokens[i]; + std::string hash = tokens[i + 1]; + std::string filename = fs::path(filePath).filename().string(); + + if (filename == fs::path(target).filename().string()) { - if (fs::path(tokens[i]).filename() == fs::path(target).filename()) + // Corresponding version path is at same index in versionPaths + size_t versionIndex = versionStart + (i - 2) / 2; + if (versionIndex < tokens.size()) { - foundVersionPath = tokens[i]; + foundVersionPath = tokens[versionIndex]; break; } } } + + if (!foundVersionPath.empty()) break; } + logFile.close(); + if (foundVersionPath.empty()) { std::cerr << "Error: No matching commit or version found for " << target << ".\n"; return; } - fs::copy(foundVersionPath, target, fs::copy_options::overwrite_existing); - std::cout << "Rolled back " << target << " to version: " << foundVersionPath << "\n"; + try + { + fs::copy(foundVersionPath, target, fs::copy_options::overwrite_existing); + std::cout << "✅ Rolled back " << target << " to version: " << foundVersionPath << "\n"; + } + catch (const std::exception &e) + { + std::cerr << "Error during rollback: " << e.what() << "\n"; + } } - - - // Function to check if the repository has been initialized bool isRepositoryInitialized(const std::string &projectName) { @@ -361,6 +408,7 @@ void createBranch(const std::string &branchName) fs::create_directory(branchPath); std::cout << "Branch '" << branchName << "' created successfully.\n"; } + // function to View History void viewHistory() { @@ -514,6 +562,111 @@ void moveToGitRepo(const std::string &folderPath, const std::string &repoPath) } } +void archiveVersions() +{ + std::string tmeformat =getTimestamp(); + + std::string repopath = getCentralRepositoryPath(); + std::cout << "repository path " << repopath << "\n"; + if (repopath.empty()) + { + std::cerr << "Error: Repository not initialized. Run 'codekeeper init'.\n"; + return; + } + + std::string versionsPath = repopath + "/versions"; + if (!fs::exists(versionsPath)) + { + std::cerr << "Error: No .versions folder found.\n"; + return; + } + + std::string archivePath = repopath + "/.versions_archive_" + tmeformat + ".zip"; + + // Use a system call to zip the .versions folder (requires zip utility installed) + std::string command = "zip -r " + archivePath + " " + versionsPath; + if (system(command.c_str()) == 0) + { + std::cout << "Archived .versions to " << archivePath << "\n"; + } + else + { + std::cerr << "Error: Failed to archive .versions folder.\n"; + } +} + +// merging files +void mergeFiles(const std::string &file1, const std::string &file2, const std::string &outputPath) +{ + std::ifstream input1(file1); + std::ifstream input2(file2); + std::ofstream output(outputPath); + + if (!input1.is_open() || !input2.is_open() || !output.is_open()) + { + std::cerr << "Error: Unable to open one or more files for merging.\n"; + return; + } + + std::string line1, line2; + while (std::getline(input1, line1) || std::getline(input2, line2)) + { + if (!line1.empty() && !line2.empty() && line1 != line2) + { + // Conflict: Append both lines with markers + output << "<<<<<<< " << file1 << "\n" + << line1 << "\n=======\n" + << line2 << "\n>>>>>>>\n"; + } + else + { + // No conflict: Append whichever line is available + output << (!line1.empty() ? line1 : line2) << "\n"; + } + line1.clear(); + line2.clear(); + } + + input1.close(); + input2.close(); + output.close(); + std::cout << "Merge complete. Output written to: " << outputPath << "\n"; +} + +// merging branches +void mergeBranches(const std::string &branch1, const std::string &branch2) +{ + repositoryPath = getCentralRepositoryPath(); + + if (repositoryPath.empty()) + { + std::cerr << "Error: Central repository not configured.\n"; + return; + } + + std::string branch1Path = repositoryPath + "/branches/" + branch1; + std::string branch2Path = repositoryPath + "/branches/" + branch2; + + if (!fs::exists(branch1Path) || !fs::exists(branch2Path)) + { + std::cerr << "Error: One or both branches do not exist.\n"; + return; + } + + for (const auto &entry : fs::directory_iterator(branch1Path)) + { + std::string file1 = entry.path(); + std::string file2 = branch2Path + "/" + fs::path(file1).filename().string(); + if (fs::exists(file2)) + { + std::string outputFile = branch1Path + "/merged_" + fs::path(file1).filename().string(); + mergeFiles(file1, file2, outputFile); + } + } + std::cout << "Branch merge complete. Resolve conflicts in the merged files if necessary.\n"; +} + + void convertToGitRepo(const std::string &folderPath) { // Ensure the folder exists @@ -549,16 +702,16 @@ void displayHelp() { std::cout << "CodeKeeper Help:\n"; std::cout << "Available Commands:\n"; - std::cout << " init Initialize a new repository.\n"; - std::cout << " commit [files] Commit specified files or directories.\n"; - std::cout << " Use '*.*' or '.' to commit all files.\n"; - std::cout << " rollback [file|guid] Revert a file or repository to a specific version.\n"; - std::cout << " history View commit history.\n"; - std::cout << " conflicts [file] Check for conflicts in a file.\n"; - std::cout << " resolve [file] [res] Resolve a conflict with the specified resolution file.\n"; - std::cout << " archive Archive the .versions folder.\n"; - std::cout << " auth Authenticate a user.\n"; - std::cout << " merge [branch1 branch2] Merge changes from two branches.\n"; + std::cout << " init Initialize a new repository.\n"; + std::cout << " commit [message] [files] Commit specified files or directories.\n"; + std::cout << " Use '*.*' or '.' to commit all files.\n"; + std::cout << " rollback [target] [file|guid] Revert a file or repository to a specific version.\n"; + std::cout << " history View commit history.\n"; + std::cout << " conflicts [file] Check for conflicts in a file.\n"; + std::cout << " resolve [file] [res] Resolve a conflict with the specified resolution file.\n"; + std::cout << " archive Archive the .versions folder.\n"; + std::cout << " auth Authenticate a user.\n"; + std::cout << " merge [branch1 branch2] Merge changes from two branches.\n"; std::cout << "\nAuthentication:\n"; std::cout << " Users must authenticate using a valid username and password.\n"; std::cout << " Only authenticated users can commit, rollback, or resolve conflicts.\n"; @@ -606,13 +759,15 @@ int main(int argc, char *argv[]) } else if (command == "rollack") { - if (argv[3] == "") + if (argc < 3) { - std::cerr << "Error: Please provide commit-id or filename.\n"; + std::cerr << "Error: Please provide filename or commit-id.\n"; return 1; } - std::string filename = argv[3]; - rollback(filename); + + std::string target = argv[2]; + std::string commitId = (argc >= 4) ? argv[3] : ""; + rollback(target, commitId); } else if (command == "retrieve") { @@ -638,6 +793,26 @@ int main(int argc, char *argv[]) { viewHistory(); + }else if (command == "archive") + { + archiveVersions(); + } + else if (command == "auth") + { + std::cout << "Authentication feature is not implemented yet.\n"; + } + else if (command == "merge") + { + if (argc < 4) + { + std::cout << "the merge feature require 2 arguements i.e. branch1 and branch2 .\n"; + return 1; + } + std::string branch1 = argv[2]; + std::string branch2 = argv[3]; + mergeBranches(branch1, branch2); + + } else if (command == "conflicts") {