feat(tree): added save_tree_diff
This commit is contained in:
parent
68885cb20f
commit
2773588023
2 changed files with 401 additions and 32 deletions
|
|
@ -6,6 +6,8 @@
|
||||||
#include "utilities.h"
|
#include "utilities.h"
|
||||||
#include "hash.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
|
#endif // TREE_H
|
||||||
|
|
|
||||||
429
src/tree.c
429
src/tree.c
|
|
@ -1,7 +1,12 @@
|
||||||
#include "tree.h"
|
#include "tree.h"
|
||||||
|
|
||||||
void snapshot_tree(FileInfoBuffer* tree) {
|
void snapshot_tree(FileInfoBuffer* tree, char* hash) {
|
||||||
FileInfoBuffer* files = file_info_buffer_new();
|
FileInfoBuffer* files = file_info_buffer_new();
|
||||||
|
if (!files) {
|
||||||
|
perror("ERROR: failed to create file info buffer!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
char relative[PATH_MAX] = "";
|
char relative[PATH_MAX] = "";
|
||||||
char* root = find_root(relative);
|
char* root = find_root(relative);
|
||||||
if (!root) {
|
if (!root) {
|
||||||
|
|
@ -13,58 +18,420 @@ void snapshot_tree(FileInfoBuffer* tree) {
|
||||||
walk(root, relative, relative, files, 1, root);
|
walk(root, relative, relative, files, 1, root);
|
||||||
file_info_buffer_sort(files);
|
file_info_buffer_sort(files);
|
||||||
|
|
||||||
for (int i = 0; i < files->len; i++) {
|
size_t buffer_size = 0;
|
||||||
printf("%s\n", ((FileInfo*)files->items + i)->name);
|
|
||||||
}
|
|
||||||
|
|
||||||
char tmp[1024];
|
|
||||||
size_t offset = 0;
|
|
||||||
|
|
||||||
offset += snprintf(tmp, sizeof(tmp), "%zu ", files->len);
|
|
||||||
|
|
||||||
for (size_t idx = 0; idx < files->len; idx++) {
|
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];
|
buffer_size += snprintf(NULL, 0, "%o %s %s\n", file_info->mode, file_info->name, file_info->hash);
|
||||||
snprintf(base_file, sizeof(base_file), "%s_0", ((FileInfo*)files->items + idx)->name);
|
}
|
||||||
|
buffer_size += 1;
|
||||||
|
|
||||||
char file_hash[41];
|
char* tmp = calloc(buffer_size, sizeof(char));
|
||||||
object_hash(FileObject, base_file, file_hash);
|
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) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
offset += (size_t)written;
|
offset += (size_t)written;
|
||||||
}
|
}
|
||||||
|
|
||||||
char hash[41];
|
|
||||||
object_hash(TreeObject, tmp, hash);
|
object_hash(TreeObject, tmp, hash);
|
||||||
|
|
||||||
char dir_path[PATH_MAX];
|
char dir_path[PATH_MAX];
|
||||||
char file_path[PATH_MAX];
|
char file_path[PATH_MAX];
|
||||||
|
|
||||||
snprintf(dir_path, sizeof(dir_path), "%s/.merk/objects/%.2s", root, hash);
|
snprintf(dir_path, sizeof(dir_path), "%s/.merk/objects/%.2s", root, hash);
|
||||||
mkdir(dir_path, 0755);
|
mkdir(dir_path, 0755);
|
||||||
|
|
||||||
snprintf(file_path, sizeof(file_path), "%s/%s", dir_path, hash+2);
|
snprintf(file_path, sizeof(file_path), "%s/%s", dir_path, hash+2);
|
||||||
|
|
||||||
FILE* fp = fopen(file_path, "wb");
|
FILE* fp = fopen(file_path, "wb");
|
||||||
if (!fp) { perror("ERROR: cannot open path in snapshot_tree!\n"); return; }
|
if (!fp) {
|
||||||
|
perror("ERROR: cannot open path in snapshot_tree!");
|
||||||
|
free(tmp);
|
||||||
uLong originalLen = strlen(tmp) + 1;
|
file_info_buffer_free(files);
|
||||||
|
free(root);
|
||||||
uLong compressedLen = compressBound(originalLen);
|
|
||||||
Bytef compressed[compressedLen];
|
|
||||||
|
|
||||||
if (compress(compressed, &compressedLen, (const Bytef*)tmp, originalLen) != Z_OK) {
|
|
||||||
perror("ERROR: compression failed in snapshot_tree!");
|
|
||||||
return;
|
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);
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue