diff --git a/codekeeper.cpp b/codekeeper.cpp index f90f219..9d805c9 100644 --- a/codekeeper.cpp +++ b/codekeeper.cpp @@ -7,18 +7,18 @@ #include #include #include -#include -#include -#include -#include +#include #include -#include // For getuid() +#include +#include +#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,323 +26,393 @@ 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)); + 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 +// 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; + } + return output; +} - 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 << "-"; +/// 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); + if (!file) return ""; + + EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); + if (!mdctx) return ""; + + const EVP_MD* md = EVP_sha256(); + if (1 != EVP_DigestInit_ex(mdctx, md, nullptr)) return ""; + + char buffer[8192]; + while (file.good()) { + file.read(buffer, sizeof(buffer)); + if (1 != EVP_DigestUpdate(mdctx, buffer, file.gcount())) return ""; } - guidStream << std::setw(12) << std::setfill('0') << dis(gen); - return guidStream.str(); -} + unsigned char hash[EVP_MAX_MD_SIZE]; + unsigned int hashLen = 0; + if (1 != EVP_DigestFinal_ex(mdctx, hash, &hashLen)) return ""; -// 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 + EVP_MD_CTX_free(mdctx); - // 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::ostringstream result; + for (unsigned int i = 0; i < hashLen; ++i) { + result << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i]; } - return false; // Repository not initialized + return result.str(); } -// Function to load the repository path -void initRepository(const std::string &projectName) +//function to get current repo path +std::string getCentralRepositoryPath() { - if (projectName.empty()) + std::ifstream configFile(".repo_path"); + std::string repositoryPath; + if (configFile.is_open()) { - std::cerr << "Error: Project name cannot be empty.\n"; - return; + std::getline(configFile, repositoryPath); } + return repositoryPath; +} - // 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; +// 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()); - std::string centralOriginPath = "/usr/bin/Codekeeper"; // You can change this path if needed +fs::path baseDir = "/var/lib/CodeKeeper"; // A safer, writable location +if (!fs::exists(baseDir)) { + fs::create_directories(baseDir); +} - // Create central origin directory if it doesn't exist - if (!fs::exists(centralOriginPath)) - { - fs::create_directories(centralOriginPath); - } +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 centralConfigFile = centralOriginPath + "/codekeeper_config"; +std::string repoName = projectName.empty() ? baseDir.filename().string() : projectName; +fs::path repoPath = fs::weakly_canonical(baseDir / fs::path(repoName).filename()); - 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); + 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(fs::current_path() / ".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() +// Function to commit multiple files +void commitFiles(const std::vector& filePaths, const std::string& commitMessage) { - std::string centralConfigFile = "/usr/bin/codekeeper/codekeeper_config"; - std::ifstream configFile(centralConfigFile); + 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(fs::current_path() / ".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"; +} + +// Function for Rollback +void rollback(const std::string &target, const std::string &commitGUID = "") +{ + std::string repopath = getCentralRepositoryPath(); + if (repopath.empty()) + { + std::cerr << "Error: Repository not initialized. Run 'codekeeper init'.\n"; + return; + } + + std::ifstream logFile(repopath + "/commit_log.txt"); + if (!logFile.is_open()) + { + std::cerr << "Error: Commit log file not found.\n"; + return; + } + + std::string line, foundVersionPath; + while (std::getline(logFile, line)) + { + std::vector tokens = split(line, '|'); + if (tokens.size() < 4) continue; + + 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) + { + 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()) + { + // Corresponding version path is at same index in versionPaths + size_t versionIndex = versionStart + (i - 2) / 2; + if (versionIndex < tokens.size()) + { + 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; + } + + 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"; + } } -// utility function -std::vector split(const std::string &str, char delimiter) +// Function to check if the repository has been initialized +bool isRepositoryInitialized(const std::string &projectName) { - std::vector tokens; - std::stringstream ss(str); - std::string token; - while (std::getline(ss, token, delimiter)) + 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)) { - tokens.push_back(token); + std::string projectDetailsFile = repositoryPath + "/projectdetails"; + if (fs::exists(projectDetailsFile)) + { + return true; // Repository is initialized + } } - return tokens; + + return false; // Repository not initialized } -bool filesAreEqual(const std::string &filePath1, const std::string &filePath2) +// Function to create a new branch +void createBranch(const std::string &branchName) { - 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)); + if (repositoryPath.empty()) + { + loadRepositoryPath(); + if (repositoryPath.empty()) + { + return; + } + } + + std::string branchesPath = repositoryPath + "/branches"; + if (!fs::exists(branchesPath)) + { + fs::create_directory(branchesPath); + } + + std::string branchPath = branchesPath + "/" + branchName; + if (fs::exists(branchPath)) + { + std::cerr << "Error: Branch '" << branchName << "' already exists.\n"; + return; + } + + fs::create_directory(branchPath); + std::cout << "Branch '" << branchName << "' created successfully.\n"; } // function to View History void viewHistory() { - repositoryPath = loadRepositoryPath(); + loadRepositoryPath(); if (repositoryPath.empty()) { @@ -383,7 +453,7 @@ void viewHistory() // function for Conflict resolution bool checkConflicts(const std::string &filePath) { - repositoryPath = loadRepositoryPath(); + loadRepositoryPath(); if (repositoryPath.empty()) { @@ -436,262 +506,93 @@ void resolveConflict(const std::string &filePath, const std::string &resolutionP std::cout << "Conflict resolved for " << filePath << " using " << resolutionPath << "\n"; } -// function foir archiving -void archiveVersions() +void moveToGitRepo(const std::string &folderPath, const std::string &repoPath) { - repositoryPath = loadRepositoryPath(); - - if (repositoryPath.empty()) + // Ensure the source folder exists + if (!fs::exists(folderPath) || !fs::is_directory(folderPath)) { - std::cerr << "Error: Repository not initialized. Run 'codekeeper init'.\n"; + std::cerr << "Error: Source folder does not exist or is not a directory.\n"; return; } - std::string versionsPath = repositoryPath + "/.versions"; - if (!fs::exists(versionsPath)) + // Ensure the destination repo folder exists + if (!fs::exists(repoPath)) { - std::cerr << "Error: No .versions folder found.\n"; + std::cerr << "Error: Git repository does not exist at the specified path.\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(); + // Create a path for the folder inside the Git repo + std::string targetPath = repoPath + "/" + fs::path(folderPath).filename().string(); - if (repositoryPath.empty()) + // Move the folder to the Git repository + try { - std::cerr << "Error: Repository not initialized. Run 'codekeeper init'.\n"; - return; + fs::rename(folderPath, targetPath); + std::cout << "Successfully moved folder to: " << targetPath << std::endl; } - - std::ifstream logFile(repositoryPath + "/commit_log.txt"); - if (!logFile.is_open()) + catch (const std::exception &e) { - std::cerr << "Error: Commit log file not found.\n"; + std::cerr << "Error: Failed to move folder. " << e.what() << std::endl; return; } - std::string line, foundVersionPath; - while (std::getline(logFile, line)) - { - std::vector tokens = split(line, '|'); - if (tokens.size() < 4) - continue; - - if ((!commitGUID.empty() && tokens[0] == commitGUID) || - (commitGUID.empty() && std::find(tokens.begin() + 3, tokens.end(), target) != tokens.end())) - { - // Found matching commit or file - 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(target).filename()) - { - foundVersionPath = tokens[i]; - break; - } - } - } - } - - if (foundVersionPath.empty()) + // Change directory to the Git repository + if (fs::exists(repoPath + "/.git")) { - std::cerr << "Error: No matching commit or version found for " << target << ".\n"; - return; - } + std::cout << "Git repository found. Proceeding with Git operations...\n"; - fs::copy(foundVersionPath, target, fs::copy_options::overwrite_existing); - std::cout << "Rolled back " << target << " to version: " << foundVersionPath << "\n"; -} + // Change the current working directory to the Git repository + std::string gitCommand = "cd " + repoPath + " && git add . && git commit -m \"Added folder: " + fs::path(folderPath).filename().string() + "\""; + int result = std::system(gitCommand.c_str()); -// Function to create a new branch -void createBranch(const std::string &branchName) -{ - if (repositoryPath.empty()) - { - repositoryPath = loadRepositoryPath(); - if (repositoryPath.empty()) + if (result == 0) { - return; + std::cout << "Successfully committed the folder to Git.\n"; } - } - - std::string branchesPath = repositoryPath + "/branches"; - if (!fs::exists(branchesPath)) - { - fs::create_directory(branchesPath); - } - - std::string branchPath = branchesPath + "/" + branchName; - if (fs::exists(branchPath)) - { - std::cerr << "Error: Branch '" << branchName << "' already exists.\n"; - return; - } - - 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()) + else { - 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"; - } + std::cerr << "Error: Failed to commit the folder to Git.\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(".")) + else { - 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); - } - } + std::cerr << "Error: No Git repository found in the specified path.\n"; } } -// Function to commit files -void commitFiles(const std::vector &filePaths, const std::string &commitMessage) +void archiveVersions() { - repositoryPath = loadRepositoryPath(); + std::string tmeformat =getTimestamp(); - if (repositoryPath.empty()) + std::string repopath = getCentralRepositoryPath(); + std::cout << "repository path " << repopath << "\n"; + if (repopath.empty()) { std::cerr << "Error: Repository not initialized. Run 'codekeeper init'.\n"; 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)) - { - if (!line.empty() && line[0] != '#') - { - ignoredFiles.push_back(line); - } - } - - std::vector allFiles; - for (const auto &filePath : filePaths) - { - 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 - { - std::cerr << "Error: Unsupported file type for " << filePath << ".\n"; - } - } - - // Commit the collected files - std::vector versionPaths; - for (const auto &filePath : allFiles) + std::string versionsPath = repopath + "/versions"; + if (!fs::exists(versionsPath)) { - 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::cerr << "Error: No .versions folder found.\n"; + return; } - std::string timestamp = getTimestamp(); - std::ofstream logFile(repositoryPath + "/commit_log.txt", std::ios::app); - logFile << commitMessage << "|" << commitID << "|" << timestamp; + std::string archivePath = repopath + "/.versions_archive_" + tmeformat + ".zip"; - for (const auto &filePath : allFiles) + // 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) { - logFile << "|" << filePath; + std::cout << "Archived .versions to " << archivePath << "\n"; } - logFile << "|"; - - for (const auto &versionPath : versionPaths) + else { - logFile << versionPath << "|"; + std::cerr << "Error: Failed to archive .versions folder.\n"; } - - logFile << "\n"; - logFile.close(); - std::cout << "Files committed successfully with message: " << commitMessage << "\n"; -} - -// 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"; } // merging files @@ -765,60 +666,6 @@ void mergeBranches(const std::string &branch1, const std::string &branch2) std::cout << "Branch merge complete. Resolve conflicts in the merged files if necessary.\n"; } -void moveToGitRepo(const std::string &folderPath, const std::string &repoPath) -{ - // Ensure the source folder exists - if (!fs::exists(folderPath) || !fs::is_directory(folderPath)) - { - std::cerr << "Error: Source folder does not exist or is not a directory.\n"; - return; - } - - // Ensure the destination repo folder exists - if (!fs::exists(repoPath)) - { - std::cerr << "Error: Git repository does not exist at the specified path.\n"; - return; - } - - // Create a path for the folder inside the Git repo - std::string targetPath = repoPath + "/" + fs::path(folderPath).filename().string(); - - // Move the folder to the Git repository - try - { - fs::rename(folderPath, targetPath); - std::cout << "Successfully moved folder to: " << targetPath << std::endl; - } - catch (const std::exception &e) - { - std::cerr << "Error: Failed to move folder. " << e.what() << std::endl; - return; - } - - // Change directory to the Git repository - if (fs::exists(repoPath + "/.git")) - { - std::cout << "Git repository found. Proceeding with Git operations...\n"; - - // Change the current working directory to the Git repository - std::string gitCommand = "cd " + repoPath + " && git add . && git commit -m \"Added folder: " + fs::path(folderPath).filename().string() + "\""; - int result = std::system(gitCommand.c_str()); - - if (result == 0) - { - std::cout << "Successfully committed the folder to Git.\n"; - } - else - { - std::cerr << "Error: Failed to commit the folder to Git.\n"; - } - } - else - { - std::cerr << "Error: No Git repository found in the specified path.\n"; - } -} void convertToGitRepo(const std::string &folderPath) { @@ -850,6 +697,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 [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"; + std::cout << "\nFor more details, consult the documentation.\n"; +} + int main(int argc, char *argv[]) { if (argc < 2) @@ -891,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") { @@ -923,11 +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 == "archive") + 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); + - archiveVersions(); } else if (command == "conflicts") {