From 27735880231f465c286803e04293a83c1db8a3eb Mon Sep 17 00:00:00 2001 From: lisk77 Date: Thu, 11 Sep 2025 23:52:34 +0200 Subject: [PATCH] feat(tree): added save_tree_diff --- include/tree.h | 4 +- src/tree.c | 429 +++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 401 insertions(+), 32 deletions(-) diff --git a/include/tree.h b/include/tree.h index 40d89d7..20b139b 100644 --- a/include/tree.h +++ b/include/tree.h @@ -6,6 +6,8 @@ #include "utilities.h" #include "hash.h" -void snapshot_tree(FileInfoBuffer*); +void snapshot_tree(FileInfoBuffer*, char*); +int save_tree_diff(FileInfoBuffer*, char*, char*, size_t, char*, char*); +int save_tree_diff_internal(ActionList*, char*, char*, char*, File*, char*); #endif // TREE_H diff --git a/src/tree.c b/src/tree.c index f115d0f..36287d4 100644 --- a/src/tree.c +++ b/src/tree.c @@ -1,7 +1,12 @@ #include "tree.h" -void snapshot_tree(FileInfoBuffer* tree) { +void snapshot_tree(FileInfoBuffer* tree, char* hash) { FileInfoBuffer* files = file_info_buffer_new(); + if (!files) { + perror("ERROR: failed to create file info buffer!"); + return; + } + char relative[PATH_MAX] = ""; char* root = find_root(relative); if (!root) { @@ -13,58 +18,420 @@ void snapshot_tree(FileInfoBuffer* tree) { walk(root, relative, relative, files, 1, root); file_info_buffer_sort(files); - for (int i = 0; i < files->len; i++) { - printf("%s\n", ((FileInfo*)files->items + i)->name); - } - - char tmp[1024]; - size_t offset = 0; - - offset += snprintf(tmp, sizeof(tmp), "%zu ", files->len); + size_t buffer_size = 0; for (size_t idx = 0; idx < files->len; idx++) { - size_t remaining = sizeof(tmp) - offset; + FileInfo* file_info = (FileInfo*)files->items + idx; - char base_file[strlen(((FileInfo*)files->items + idx)->name)+2]; - snprintf(base_file, sizeof(base_file), "%s_0", ((FileInfo*)files->items + idx)->name); + buffer_size += snprintf(NULL, 0, "%o %s %s\n", file_info->mode, file_info->name, file_info->hash); + } + buffer_size += 1; - char file_hash[41]; - object_hash(FileObject, base_file, file_hash); + char* tmp = calloc(buffer_size, sizeof(char)); + if (!tmp) { + perror("ERROR: memory allocation failed in snapshot_tree!"); + file_info_buffer_free(files); + free(root); + return; + } + + size_t offset = 0; + + for (size_t idx = 0; idx < files->len; idx++) { + FileInfo* file_info = (FileInfo*)files->items + idx; + size_t remaining = buffer_size - offset; + + int written = snprintf(tmp + offset, remaining, "%o %s %s\n", + file_info->mode, file_info->name, file_info->hash); - int written = snprintf(tmp + offset, remaining, "%o %s %s ", ((FileInfo*)files->items + idx)->mode, ((FileInfo*)files->items + idx)->name, file_hash); if (written < 0 || (size_t)written >= remaining) { - perror("ERROR: buffer overflow in snapshot_tree!\n"); + perror("ERROR: buffer overflow in snapshot_tree!"); + free(tmp); + file_info_buffer_free(files); + free(root); return; } - offset += (size_t)written; } - char hash[41]; object_hash(TreeObject, tmp, hash); char dir_path[PATH_MAX]; char file_path[PATH_MAX]; - snprintf(dir_path, sizeof(dir_path), "%s/.merk/objects/%.2s", root, hash); mkdir(dir_path, 0755); - snprintf(file_path, sizeof(file_path), "%s/%s", dir_path, hash+2); FILE* fp = fopen(file_path, "wb"); - if (!fp) { perror("ERROR: cannot open path in snapshot_tree!\n"); return; } - - - uLong originalLen = strlen(tmp) + 1; - - uLong compressedLen = compressBound(originalLen); - Bytef compressed[compressedLen]; - - if (compress(compressed, &compressedLen, (const Bytef*)tmp, originalLen) != Z_OK) { - perror("ERROR: compression failed in snapshot_tree!"); + if (!fp) { + perror("ERROR: cannot open path in snapshot_tree!"); + free(tmp); + file_info_buffer_free(files); + free(root); return; } - fwrite(compressed, sizeof(Bytef), compressedLen, fp); + uLong originalLen = strlen(tmp) + 1; + uLong compressedLen = compressBound(originalLen); + Bytef* compressed = malloc(compressedLen); + if (!compressed) { + fclose(fp); + free(tmp); + file_info_buffer_free(files); + free(root); + return; + } + + if (compress(compressed, &compressedLen, (const Bytef*)tmp, originalLen) != Z_OK) { + perror("ERROR: compression failed in snapshot_tree!"); + free(compressed); + free(tmp); + fclose(fp); + file_info_buffer_free(files); + free(root); + return; + } + + fprintf(fp, "%lu ", (unsigned long)originalLen); + fwrite(compressed, 1, compressedLen, fp); + fclose(fp); + + chmod(file_path, S_IRUSR | S_IRGRP | S_IROTH); + + free(compressed); + free(tmp); + file_info_buffer_free(files); + free(root); +} + +int save_tree_diff(FileInfoBuffer* current_tree, char* root, char* branch_name, size_t diff_id, char* base_tree_hash, char* hash) { + if (!current_tree || !root || !branch_name || !base_tree_hash || !hash) { + return 0; + } + + // Step 1: Read the base tree file and convert to File* + char base_tree_location[PATH_MAX]; + snprintf(base_tree_location, sizeof(base_tree_location), "%s/.merk/objects/%.2s/%s", root, base_tree_hash, base_tree_hash+2); + + size_t compressed_size; + unsigned char* compressed_data = (unsigned char*)get_file_content_with_size(base_tree_location, &compressed_size); + if (!compressed_data) { + return 0; + } + + // Parse length from compressed data + size_t idx = 0; + while (idx < compressed_size && IS_DIGIT(compressed_data[idx])) { + idx++; + } + + if (idx == 0) { + perror("ERROR: no length found in base tree file!"); + free(compressed_data); + return 0; + } + + char* size_str = calloc(idx + 1, sizeof(char)); + if (!size_str) { + free(compressed_data); + return 0; + } + + memcpy(size_str, compressed_data, idx); + size_str[idx] = '\0'; + + char* end; + long size = strtol(size_str, &end, 10); + if (end == size_str || *end != '\0' || size <= 0) { + perror("ERROR: invalid length in base tree file"); + free(size_str); + free(compressed_data); + return 0; + } + + size_t offset = strlen(size_str) + 1; // +1 for space after length + free(size_str); + + // Decompress base tree content + char* base_tree_content = calloc(size + 1, sizeof(char)); + if (!base_tree_content) { + free(compressed_data); + return 0; + } + + uLongf dest_len = (uLongf)size; + int result = uncompress((unsigned char*)base_tree_content, &dest_len, compressed_data + offset, compressed_size - offset); + free(compressed_data); + + if (result != Z_OK) { + perror("ERROR: decompression of base tree failed!"); + free(base_tree_content); + return 0; + } + + base_tree_content[dest_len] = '\0'; + + // Convert base tree content to File* + File* base_tree_file = from_string(base_tree_content); + if (!base_tree_file) { + free(base_tree_content); + return 0; + } + + // Step 2: Convert FileInfoBuffer to string (same as snapshot_tree logic) + size_t buffer_size = 0; + for (size_t i = 0; i < current_tree->len; i++) { + FileInfo* file_info = (FileInfo*)current_tree->items + i; + buffer_size += snprintf(NULL, 0, "%o %s %s\n", file_info->mode, file_info->name, file_info->hash); + } + buffer_size += 1; // null terminator + + char* current_tree_content = malloc(buffer_size); + if (!current_tree_content) { + free(base_tree_content); + free(base_tree_file->content[0]); + free(base_tree_file->content); + free(base_tree_file); + return 0; + } + + size_t content_offset = 0; + for (size_t i = 0; i < current_tree->len; i++) { + FileInfo* file_info = (FileInfo*)current_tree->items + i; + size_t remaining = buffer_size - content_offset; + int written = snprintf(current_tree_content + content_offset, remaining, "%o %s %s\n", + file_info->mode, file_info->name, file_info->hash); + if (written < 0 || (size_t)written >= remaining) { + perror("ERROR: buffer overflow in current tree content!"); + free(current_tree_content); + free(base_tree_content); + free(base_tree_file->content[0]); + free(base_tree_file->content); + free(base_tree_file); + return 0; + } + content_offset += (size_t)written; + } + current_tree_content[content_offset] = '\0'; + + // Convert current tree content to File* + File* current_tree_file = from_string(current_tree_content); + if (!current_tree_file) { + free(current_tree_content); + free(base_tree_content); + free(base_tree_file->content[0]); + free(base_tree_file->content); + free(base_tree_file); + return 0; + } + + // Step 3: Generate diff using Myers algorithm + ActionList* diff = myers_diff(base_tree_file, current_tree_file, 0, 0); + + // Handle case where trees are identical + if (!diff || diff->len == 0) { + strcpy(hash, base_tree_hash); + + // Cleanup + free(current_tree_content); + free(base_tree_content); + free(base_tree_file->content[0]); + free(base_tree_file->content); + free(base_tree_file); + free(current_tree_file->content[0]); + free(current_tree_file->content); + free(current_tree_file); + if (diff) { + free(diff->actions); + free(diff); + } + return 1; + } + + // Step 4: Save the diff + // Calculate size for diff content string + size_t diff_buffer_size = strlen(base_tree_hash) + 1; // base hash + space + diff_buffer_size += snprintf(NULL, 0, "%zu ", diff->len); // action count + space + + for (size_t i = 0; i < diff->len; i++) { + Action action = diff->actions[i]; + if (action.type == DELETE) { + diff_buffer_size += snprintf(NULL, 0, "0 %zu %zu ", action.line_original, action.line_changed); + } else { // INSERT + diff_buffer_size += snprintf(NULL, 0, "1 %zu %zu %s ", + action.line_original, action.line_changed, current_tree_file->content[action.line_changed]); + } + } + diff_buffer_size += 1; // null terminator + + // Build diff content string + char* diff_content = malloc(diff_buffer_size); + if (!diff_content) { + perror("ERROR: memory allocation failed for diff content!"); + free(current_tree_content); + free(base_tree_content); + free(base_tree_file->content[0]); + free(base_tree_file->content); + free(base_tree_file); + free(current_tree_file->content[0]); + free(current_tree_file->content); + free(current_tree_file); + free(diff->actions); + free(diff); + return 0; + } + + size_t diff_offset = 0; + diff_offset += snprintf(diff_content, diff_buffer_size, "%s %zu ", base_tree_hash, diff->len); + + for (size_t i = 0; i < diff->len; i++) { + Action action = diff->actions[i]; + size_t remaining = diff_buffer_size - diff_offset; + + if (action.type == DELETE) { + int written = snprintf(diff_content + diff_offset, remaining, "0 %zu %zu ", + action.line_original, action.line_changed); + if (written < 0 || (size_t)written >= remaining) { + perror("ERROR: buffer overflow in diff content!"); + free(diff_content); + free(current_tree_content); + free(base_tree_content); + free(base_tree_file->content[0]); + free(base_tree_file->content); + free(base_tree_file); + free(current_tree_file->content[0]); + free(current_tree_file->content); + free(current_tree_file); + free(diff->actions); + free(diff); + return 0; + } + diff_offset += (size_t)written; + } else { // INSERT + int written = snprintf(diff_content + diff_offset, remaining, "1 %zu %zu %s ", + action.line_original, action.line_changed, current_tree_file->content[action.line_changed]); + if (written < 0 || (size_t)written >= remaining) { + perror("ERROR: buffer overflow in diff content!"); + free(diff_content); + free(current_tree_content); + free(base_tree_content); + free(base_tree_file->content[0]); + free(base_tree_file->content); + free(base_tree_file); + free(current_tree_file->content[0]); + free(current_tree_file->content); + free(current_tree_file); + free(diff->actions); + free(diff); + return 0; + } + diff_offset += (size_t)written; + } + } + + // Generate hash from the actual diff content + object_hash(TreeDiffObject, diff_content, hash); + + // Create directory structure + char dir_path[PATH_MAX]; + char file_path[PATH_MAX]; + snprintf(dir_path, sizeof(dir_path), "%s/.merk/objects/%.2s", root, hash); + if (mkdir(dir_path, 0755) != 0 && errno != EEXIST) { + perror("ERROR: failed to create directory!"); + free(diff_content); + free(current_tree_content); + free(base_tree_content); + free(base_tree_file->content[0]); + free(base_tree_file->content); + free(base_tree_file); + free(current_tree_file->content[0]); + free(current_tree_file->content); + free(current_tree_file); + free(diff->actions); + free(diff); + return 0; + } + snprintf(file_path, sizeof(file_path), "%s/%s", dir_path, hash+2); + + // Open file for writing + FILE* fp = fopen(file_path, "wb"); + if (!fp) { + perror("ERROR: cannot open file for writing!"); + free(diff_content); + free(current_tree_content); + free(base_tree_content); + free(base_tree_file->content[0]); + free(base_tree_file->content); + free(base_tree_file); + free(current_tree_file->content[0]); + free(current_tree_file->content); + free(current_tree_file); + free(diff->actions); + free(diff); + return 0; + } + + // Compress the diff content + uLong originalLen = strlen(diff_content) + 1; + uLong compressedLen = compressBound(originalLen); + Bytef* compressed = malloc(compressedLen); + if (!compressed) { + perror("ERROR: memory allocation failed for compression!"); + fclose(fp); + free(diff_content); + free(current_tree_content); + free(base_tree_content); + free(base_tree_file->content[0]); + free(base_tree_file->content); + free(base_tree_file); + free(current_tree_file->content[0]); + free(current_tree_file->content); + free(current_tree_file); + free(diff->actions); + free(diff); + return 0; + } + + if (compress(compressed, &compressedLen, (const Bytef*)diff_content, originalLen) != Z_OK) { + perror("ERROR: compression failed!"); + free(compressed); + fclose(fp); + free(diff_content); + free(current_tree_content); + free(base_tree_content); + free(base_tree_file->content[0]); + free(base_tree_file->content); + free(base_tree_file); + free(current_tree_file->content[0]); + free(current_tree_file->content); + free(current_tree_file); + free(diff->actions); + free(diff); + return 0; + } + + // Write length prefix and compressed data + fprintf(fp, "%lu ", (unsigned long)originalLen); + fwrite(compressed, 1, compressedLen, fp); + fclose(fp); + + // Set file permissions + chmod(file_path, S_IRUSR | S_IRGRP | S_IROTH); + + // Cleanup + free(compressed); + free(diff_content); + free(current_tree_content); + free(base_tree_content); + free(base_tree_file->content[0]); + free(base_tree_file->content); + free(base_tree_file); + free(current_tree_file->content[0]); + free(current_tree_file->content); + free(current_tree_file); + free(diff->actions); + free(diff); + + return 1; }