feat(utilities): added functions to save, read and apply diffs
This commit is contained in:
parent
e2d026502f
commit
cfb24bd3a2
2 changed files with 560 additions and 3 deletions
554
src/utilities.c
554
src/utilities.c
|
|
@ -590,3 +590,557 @@ int create_default_config_file(char* config_path) {
|
|||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int save_diff(ActionList* diff, char* path, char* root, size_t diff_id, char* basefile_hash, File* modified_file, int tree, char* hash) {
|
||||
size_t buffer_size = 41;
|
||||
|
||||
buffer_size += 1 + snprintf(NULL, 0, "%d", diff->len);
|
||||
|
||||
for (size_t idx = 0; idx < diff->len; idx++) {
|
||||
Action action = diff->actions[idx];
|
||||
if (action.type == DELETE) {
|
||||
// 4 = OPBIT SPACE ... SPACE ... SPACE
|
||||
buffer_size +=
|
||||
4 +
|
||||
snprintf(NULL, 0, "%d", action.line_original) +
|
||||
snprintf(NULL, 0, "%d", action.line_changed);
|
||||
continue;
|
||||
}
|
||||
// 6 = OPBIT SPACE ... SPACE ... SPACE ... SPACE ... SPACE
|
||||
buffer_size +=
|
||||
6 +
|
||||
snprintf(NULL, 0, "%d", action.line_original) +
|
||||
snprintf(NULL, 0, "%d", action.line_changed) +
|
||||
snprintf(NULL, 0, "%zu", strlen(modified_file->content[action.line_changed])) +
|
||||
strlen(modified_file->content[action.line_changed]);
|
||||
}
|
||||
|
||||
char tmp[buffer_size];
|
||||
size_t offset = 0;
|
||||
|
||||
offset += snprintf(tmp, sizeof(tmp), "%s %zu ", basefile_hash, diff->len);
|
||||
|
||||
for (size_t idx = 0; idx < diff->len; idx++) {
|
||||
Action action = diff->actions[idx];
|
||||
size_t remaining = sizeof(tmp) - offset;
|
||||
|
||||
if (action.type == DELETE) {
|
||||
offset += snprintf(tmp + offset,
|
||||
remaining,
|
||||
"0 %zu %zu ",
|
||||
action.line_original,
|
||||
action.line_changed
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
offset += snprintf(tmp + offset, remaining,
|
||||
"1 %zu %zu %zu %s ",
|
||||
action.line_original,
|
||||
action.line_changed,
|
||||
strlen(modified_file->content[action.line_changed]),
|
||||
modified_file->content[action.line_changed]
|
||||
);
|
||||
}
|
||||
|
||||
char id[2+snprintf(NULL, 0, "%d", diff_id)+strlen(path)];
|
||||
snprintf(id, sizeof(id), "%s %d", path, diff_id);
|
||||
if (tree) object_hash(TreeDiffObject, id, hash);
|
||||
else object_hash(FileDiffObject, id, 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);
|
||||
|
||||
if (access(file_path, F_OK) == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
FILE* fp = fopen(file_path, "wb");
|
||||
if (!fp) {
|
||||
perror("ERROR: cannot open path in save_diff!\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check if the file already exists
|
||||
|
||||
|
||||
// Ensure the directory structure exists
|
||||
if (mkdir_recursive(dir_path, 0755) < 0 && errno != EEXIST) {
|
||||
perror("ERROR: failed to create directory structure in save_diff!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
uLong originalLen = strlen(tmp) + 1;
|
||||
uLong compressedLen = compressBound(originalLen);
|
||||
Bytef* compressed = malloc(compressedLen);
|
||||
if (!compressed) {
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (compress(compressed, &compressedLen, (const Bytef*)tmp, originalLen) != Z_OK) {
|
||||
perror("ERROR: compression failed in save_diff!");
|
||||
free(compressed);
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
fprintf(fp, "%lu ", (unsigned long)originalLen);
|
||||
|
||||
fwrite(compressed, 1, compressedLen, fp);
|
||||
|
||||
fclose(fp);
|
||||
|
||||
chmod(file_path, S_IRUSR | S_IRGRP | S_IROTH);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int read_diff(char* path, char* root, size_t diff_id, char* basefile_hash, ActionList* diff_out) {
|
||||
size_t compressed_size;
|
||||
unsigned char* compressed_data = (unsigned char*)get_file_content_with_size(path, &compressed_size);
|
||||
|
||||
if (!compressed_data || compressed_size == 0) {
|
||||
free(compressed_data);
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t idx = 0;
|
||||
while (idx < compressed_size && IS_DIGIT(compressed_data[idx])) {
|
||||
idx++;
|
||||
}
|
||||
|
||||
if (idx == 0) {
|
||||
perror("ERROR: no length found at start of base file list!");
|
||||
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 original_size = strtol(size_str, &end, 10);
|
||||
if (end == size_str || *end != '\0') {
|
||||
perror("ERROR: invalid length in read_diff!");
|
||||
free(size_str);
|
||||
free(compressed_data);
|
||||
return 0;
|
||||
}
|
||||
free(size_str);
|
||||
|
||||
if (idx < compressed_size && compressed_data[idx] == ' ') {
|
||||
idx++;
|
||||
}
|
||||
|
||||
char* diff_list = calloc(original_size + 1, sizeof(char));
|
||||
if (!diff_list) {
|
||||
free(compressed_data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uLongf dest_len = (uLongf)original_size;
|
||||
|
||||
int result = uncompress((unsigned char*)diff_list, &dest_len, compressed_data + idx, compressed_size - idx);
|
||||
free(compressed_data);
|
||||
|
||||
if (result != Z_OK) {
|
||||
perror("ERROR: decompression of the diff failed!");
|
||||
free(diff_list);
|
||||
return 0;
|
||||
}
|
||||
|
||||
diff_list[dest_len] = '\0';
|
||||
|
||||
printf("Decompressed diff content: %s\n", diff_list);
|
||||
|
||||
idx = 0;
|
||||
strncpy(basefile_hash, diff_list, 40);
|
||||
basefile_hash[40] = '\0';
|
||||
idx += 41;
|
||||
|
||||
while (idx < dest_len && IS_DIGIT(diff_list[idx])) {
|
||||
idx++;
|
||||
}
|
||||
|
||||
if (idx == 41) {
|
||||
perror("ERROR: no length found at start of diff!");
|
||||
free(diff_list);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char* number_of_actions = calloc(idx + 1, sizeof(char));
|
||||
if (!number_of_actions) {
|
||||
free(diff_list);
|
||||
return 0;
|
||||
}
|
||||
|
||||
memcpy(number_of_actions, diff_list + 41, idx-41);
|
||||
number_of_actions[idx] = '\0';
|
||||
|
||||
long actions = strtol(number_of_actions, &end, 10);
|
||||
if (end == number_of_actions || *end != '\0') {
|
||||
perror("ERROR: invalid length in read_diff!");
|
||||
free(number_of_actions);
|
||||
free(diff_list);
|
||||
return 0;
|
||||
}
|
||||
|
||||
free(number_of_actions);
|
||||
|
||||
if (actions <= 0) {
|
||||
free(diff_list);
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t action_idx = 0;
|
||||
while (idx < dest_len && action_idx < (size_t)actions) {
|
||||
idx++;
|
||||
|
||||
if (diff_list[idx] == '0') {
|
||||
idx++;
|
||||
if (diff_list[idx] == ' ') idx++;
|
||||
else {
|
||||
perror("ERROR: expected space after opbit in read_diff!");
|
||||
free(diff_list);
|
||||
return 0;
|
||||
}
|
||||
size_t start_idx = idx;
|
||||
|
||||
while (idx < dest_len && IS_DIGIT(diff_list[idx])) {
|
||||
idx++;
|
||||
}
|
||||
if (idx == dest_len) {
|
||||
perror("ERROR: unexpected end of diff in read_diff!");
|
||||
free(diff_list);
|
||||
return 0;
|
||||
}
|
||||
char* line_original_str = calloc(idx + 1, sizeof(char));
|
||||
if (!line_original_str) {
|
||||
free(diff_list);
|
||||
return 0;
|
||||
}
|
||||
memcpy(line_original_str, diff_list + start_idx, idx - start_idx);
|
||||
line_original_str[idx - start_idx] = '\0';
|
||||
char* end2;
|
||||
long line_original = strtol(line_original_str, &end2, 10);
|
||||
if (end2 == line_original_str || *end2 != '\0') {
|
||||
perror("ERROR: invalid line number in read_diff!");
|
||||
free(line_original_str);
|
||||
free(diff_list);
|
||||
return 0;
|
||||
}
|
||||
free(line_original_str);
|
||||
|
||||
if (diff_list[idx] == ' ') idx++;
|
||||
else {
|
||||
perror("ERROR: expected space after line original in read_diff!");
|
||||
free(diff_list);
|
||||
return 0;
|
||||
}
|
||||
start_idx = idx;
|
||||
while (idx < dest_len && IS_DIGIT(diff_list[idx])) {
|
||||
idx++;
|
||||
}
|
||||
if (idx == dest_len) {
|
||||
perror("ERROR: unexpected end of diff in read_diff!");
|
||||
free(diff_list);
|
||||
return 0;
|
||||
}
|
||||
char* line_changed_str = calloc(idx + 1, sizeof(char));
|
||||
if (!line_changed_str) {
|
||||
free(diff_list);
|
||||
return 0;
|
||||
}
|
||||
memcpy(line_changed_str, diff_list + start_idx, idx - start_idx);
|
||||
line_changed_str[idx - start_idx] = '\0';
|
||||
long line_changed = strtol(line_changed_str, &end2, 10);
|
||||
if (end2 == line_changed_str || *end2 != '\0') {
|
||||
perror("ERROR: invalid line number in read_diff!");
|
||||
free(line_changed_str);
|
||||
free(diff_list);
|
||||
return 0;
|
||||
}
|
||||
free(line_changed_str);
|
||||
|
||||
Action action = {.type=DELETE, .line_original=line_original, .line_changed=line_changed, .content=NULL};
|
||||
add_action(diff_out, action);
|
||||
action_idx++;
|
||||
}
|
||||
else if (diff_list[idx] == '1') {
|
||||
idx++;
|
||||
if (diff_list[idx] == ' ') idx++;
|
||||
else {
|
||||
perror("ERROR: expected space after opbit in read_diff!");
|
||||
free(diff_list);
|
||||
return 0;
|
||||
}
|
||||
size_t start_idx = idx;
|
||||
|
||||
while (idx < dest_len && IS_DIGIT(diff_list[idx])) {
|
||||
idx++;
|
||||
}
|
||||
if (idx == dest_len) {
|
||||
perror("ERROR: unexpected end of diff in read_diff!");
|
||||
free(diff_list);
|
||||
return 0;
|
||||
}
|
||||
char* line_original_str = calloc(idx + 1, sizeof(char));
|
||||
if (!line_original_str) {
|
||||
free(diff_list);
|
||||
return 0;
|
||||
}
|
||||
memcpy(line_original_str, diff_list + start_idx, idx - start_idx);
|
||||
line_original_str[idx - start_idx] = '\0';
|
||||
char* end2;
|
||||
long line_original = strtol(line_original_str, &end2, 10);
|
||||
if (end2 == line_original_str || *end2 != '\0') {
|
||||
perror("ERROR: invalid line number in read_diff!");
|
||||
free(line_original_str);
|
||||
free(diff_list);
|
||||
return 0;
|
||||
}
|
||||
free(line_original_str);
|
||||
|
||||
if (diff_list[idx] == ' ') idx++;
|
||||
else {
|
||||
perror("ERROR: expected space after line original in read_diff!");
|
||||
free(diff_list);
|
||||
return 0;
|
||||
}
|
||||
start_idx = idx;
|
||||
while (idx < dest_len && IS_DIGIT(diff_list[idx])) {
|
||||
idx++;
|
||||
}
|
||||
if (idx == dest_len) {
|
||||
perror("ERROR: unexpected end of diff in read_diff!");
|
||||
free(diff_list);
|
||||
return 0;
|
||||
}
|
||||
char* line_changed_str = calloc(idx + 1, sizeof(char));
|
||||
if (!line_changed_str) {
|
||||
free(diff_list);
|
||||
return 0;
|
||||
}
|
||||
memcpy(line_changed_str, diff_list + start_idx, idx - start_idx);
|
||||
line_changed_str[idx - start_idx] = '\0';
|
||||
long line_changed = strtol(line_changed_str, &end2, 10);
|
||||
if (end2 == line_changed_str || *end2 != '\0') {
|
||||
perror("ERROR: invalid line number in read_diff!");
|
||||
free(line_changed_str);
|
||||
free(diff_list);
|
||||
return 0;
|
||||
}
|
||||
free(line_changed_str);
|
||||
|
||||
if (diff_list[idx] == ' ') idx++;
|
||||
else {
|
||||
perror("ERROR: expected space after line changed in read_diff!");
|
||||
free(diff_list);
|
||||
return 0;
|
||||
}
|
||||
|
||||
start_idx = idx;
|
||||
while (idx < dest_len && IS_DIGIT(diff_list[idx])) {
|
||||
idx++;
|
||||
}
|
||||
if (idx == dest_len) {
|
||||
perror("ERROR: unexpected end of diff in read_diff!");
|
||||
free(diff_list);
|
||||
return 0;
|
||||
}
|
||||
char* content_len_str = calloc(idx + 1, sizeof(char));
|
||||
if (!content_len_str) {
|
||||
free(diff_list);
|
||||
return 0;
|
||||
}
|
||||
memcpy(content_len_str, diff_list + start_idx, idx - start_idx);
|
||||
content_len_str[idx - start_idx] = '\0';
|
||||
long content_len = strtol(content_len_str, &end2, 10);
|
||||
if (end2 == content_len_str || *end2 != '\0') {
|
||||
perror("ERROR: invalid line number in read_diff!");
|
||||
free(content_len_str);
|
||||
free(diff_list);
|
||||
return 0;
|
||||
}
|
||||
free(content_len_str);
|
||||
|
||||
if (diff_list[idx] == ' ') idx++;
|
||||
else {
|
||||
perror("ERROR: expected space after line original in read_diff!");
|
||||
free(diff_list);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (idx + content_len > dest_len) {
|
||||
perror("ERROR: content length exceeds diff length in read_diff!");
|
||||
free(diff_list);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char* content = calloc(content_len + 1, sizeof(char));
|
||||
if (!content) {
|
||||
free(diff_list);
|
||||
return 0;
|
||||
}
|
||||
memcpy(content, diff_list + idx, content_len);
|
||||
content[content_len] = '\0';
|
||||
|
||||
idx += content_len;
|
||||
|
||||
Action action = {.type=INSERT, .line_original=line_original, .line_changed=line_changed, .content=content};
|
||||
add_action(diff_out, action);
|
||||
action_idx++;
|
||||
}
|
||||
else {
|
||||
perror("ERROR: invalid opbit in read_diff!");
|
||||
free(diff_list);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
int save_file_diff(char* path, char* root, size_t diff_id, char* basefile_hash, char* hash) {
|
||||
char basefile_location[2+strlen(root)+strlen("/.merk/objects/")+strlen(basefile_hash)];
|
||||
snprintf(basefile_location, sizeof(basefile_location), "%s/.merk/objects/%.2s/%s", root, basefile_hash, basefile_hash+2);
|
||||
|
||||
size_t compressed_size;
|
||||
unsigned char* compressed_data = (unsigned char*)get_file_content_with_size(basefile_location, &compressed_size);
|
||||
if (!compressed_data) return 0;
|
||||
|
||||
size_t idx = 0;
|
||||
|
||||
while (idx < compressed_size && IS_DIGIT(compressed_data[idx])) {
|
||||
idx++;
|
||||
}
|
||||
|
||||
if (idx == 0) {
|
||||
perror("ERROR: no length found at start of save_file_diff!");
|
||||
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') {
|
||||
perror("ERROR: invalid length in base_file_list");
|
||||
free(size_str);
|
||||
free(compressed_data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t offset = strlen(size_str) + 1;
|
||||
free(size_str);
|
||||
|
||||
char* basefile_content = calloc(size+1, sizeof(char));
|
||||
if (!basefile_content) {
|
||||
free(compressed_data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uLongf dest_len = (uLongf)size;
|
||||
|
||||
int result = uncompress((unsigned char*)basefile_content, &dest_len, compressed_data+offset, compressed_size);
|
||||
free(compressed_data);
|
||||
|
||||
if (result != Z_OK) {
|
||||
perror("ERROR: decompression of the base file list failed!");
|
||||
free(basefile_content);
|
||||
return 0;
|
||||
}
|
||||
|
||||
basefile_content[dest_len] = '\0';
|
||||
|
||||
File* basefile = from_string(basefile_content);
|
||||
char* modified_file_path = realpath(path, NULL);
|
||||
File* modified_file = new_file(modified_file_path);
|
||||
|
||||
ActionList* diff = myers_diff(basefile, modified_file, 0, 0);
|
||||
if (diff->len == 0) {
|
||||
hash = NULL;
|
||||
free(basefile_content);
|
||||
return 1;
|
||||
}
|
||||
|
||||
save_diff(diff, path, root, diff_id, basefile_hash, modified_file, 0, hash);
|
||||
|
||||
free(basefile_content);
|
||||
return 1;
|
||||
}
|
||||
|
||||
File* apply_diff(File* basefile, ActionList* diff) {
|
||||
if (!basefile || !diff) return NULL;
|
||||
|
||||
File* copy = copy_file(basefile);
|
||||
if (!copy) return NULL;
|
||||
|
||||
for (size_t i = 0; i < diff->len; i++) {
|
||||
Action* action = &diff->actions[i];
|
||||
int success = 0;
|
||||
|
||||
switch (action->type) {
|
||||
case INSERT:
|
||||
success = insert_line(copy, action->content, action->line_changed);
|
||||
if (!success) {
|
||||
fprintf(stderr, "ERROR: Failed to insert line at index %zu\n", action->line_changed);
|
||||
free_file(copy);
|
||||
return NULL;
|
||||
}
|
||||
break;
|
||||
|
||||
case DELETE:
|
||||
success = delete_line(copy, action->line_changed);
|
||||
if (!success) {
|
||||
fprintf(stderr, "ERROR: Failed to delete line at index %zu\n", action->line_changed);
|
||||
free_file(copy);
|
||||
return NULL;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "ERROR: Unknown action type %d\n", action->type);
|
||||
free_file(copy);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
int compare_actions(const void* a, const void* b) {
|
||||
const Action* action1 = (const Action*)a;
|
||||
const Action* action2 = (const Action*)b;
|
||||
|
||||
if (action1->type != action2->type) {
|
||||
return (action1->type == DELETE) ? -1 : 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void sort_action_list(ActionList* actions) {
|
||||
if (!actions || actions->len <= 1) return;
|
||||
|
||||
qsort(actions->actions, actions->len, sizeof(Action), compare_actions);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue