1146 lines
No EOL
34 KiB
C
1146 lines
No EOL
34 KiB
C
#include "utilities.h"
|
|
|
|
List* list_new(size_t item_size) {
|
|
List* list = calloc(1, sizeof(List));
|
|
if (!list) { perror("ERROR: memory allocation in new_list failed!"); return NULL; }
|
|
|
|
list->items = calloc(LIST_INIT_CAPACITY, item_size);
|
|
if (!list->items) { perror("ERROR: memory allocation in new_list failed!"); free(list); return NULL; }
|
|
|
|
list->len = 0;
|
|
list->capacity = LIST_INIT_CAPACITY;
|
|
list->item_size = item_size;
|
|
return list;
|
|
}
|
|
|
|
int list_push(List* list, void* item) {
|
|
if (!list || !item) return 0;
|
|
|
|
if (list->len == list->capacity) {
|
|
size_t new_capacity = list->capacity * 2;
|
|
void* new_items = realloc(list->items, new_capacity * list->item_size);
|
|
if (!new_items) { perror("ERROR: memory reallocation failed in list_push!"); return 0; }
|
|
|
|
list->items = new_items;
|
|
list->capacity = new_capacity;
|
|
}
|
|
|
|
char* dest = (char*)list->items + (list->len * list->item_size);
|
|
memcpy(dest, item, list->item_size);
|
|
list->len++;
|
|
|
|
return 1;
|
|
}
|
|
|
|
int list_remove(List* list, const void* key, int (*compare)(const void*, const void*)) {
|
|
if (!list || !key || !compare || list->len == 0) {
|
|
return 0;
|
|
}
|
|
|
|
for (size_t i = 0; i < list->len; i++) {
|
|
char* current_item = (char*)list->items + (i * list->item_size);
|
|
|
|
if (compare(key, current_item) == 0) {
|
|
for (size_t j = i; j < list->len - 1; j++) {
|
|
char* dest = (char*)list->items + (j * list->item_size);
|
|
char* src = (char*)list->items + ((j + 1) * list->item_size);
|
|
memcpy(dest, src, list->item_size);
|
|
}
|
|
|
|
list->len--;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void list_free(List* buffer) {
|
|
if (buffer) {
|
|
for (size_t i = 0; i < buffer->len; i++) {
|
|
free(buffer->items[i]);
|
|
}
|
|
free(buffer->items);
|
|
free(buffer);
|
|
}
|
|
}
|
|
|
|
void* binary_search(const void* key, const void* base, size_t num_elements, size_t element_size, int (*compare)(const void*, const void*)) {
|
|
if (!key || !base || !compare || num_elements == 0) {
|
|
return NULL;
|
|
}
|
|
|
|
size_t left = 0;
|
|
size_t right = num_elements - 1;
|
|
|
|
while (left <= right) {
|
|
size_t mid = left + (right - left) / 2;
|
|
|
|
const char* mid_element = (const char*)base + (mid * element_size);
|
|
|
|
int cmp = compare(key, mid_element);
|
|
|
|
if (cmp == 0) {
|
|
return (void*)mid_element;
|
|
}
|
|
else if (cmp < 0) {
|
|
if (mid == 0) break;
|
|
right = mid - 1;
|
|
}
|
|
else {
|
|
left = mid + 1;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void* list_binary_search(List* list, const void* key, int (*compare)(const void*, const void*)) {
|
|
if (!list || !key || !compare) {
|
|
return NULL;
|
|
}
|
|
|
|
return binary_search(key, list->items, list->len, list->item_size, compare);
|
|
}
|
|
|
|
StringBuffer* string_buffer_new() {
|
|
return list_new(sizeof(char*));
|
|
}
|
|
|
|
int string_buffer_push(StringBuffer* buffer, char* path) {
|
|
char* path_copy = strdup(path);
|
|
if (!path_copy) {
|
|
perror("ERROR: strdup failed in string_buffer_push!");
|
|
return 0;
|
|
}
|
|
return list_push(buffer, &path_copy);
|
|
}
|
|
|
|
int compare_strings(const void* a, const void* b) {
|
|
const char* s1 = *(const char**)a;
|
|
const char* s2 = *(const char**)b;
|
|
return strcmp(s1,s2);
|
|
}
|
|
|
|
void string_buffer_sort(StringBuffer* buffer) {
|
|
if (!buffer || buffer->len <= 1) {
|
|
return;
|
|
}
|
|
|
|
qsort(buffer->items, buffer->len, sizeof(char*), compare_strings);
|
|
}
|
|
|
|
char* string_buffer_search(StringBuffer* buffer, char* path) {
|
|
if (!buffer || !path) return NULL;
|
|
return (char*)list_binary_search(buffer, path, compare_strings);
|
|
}
|
|
|
|
FileInfoBuffer* file_info_buffer_new() {
|
|
return list_new(sizeof(FileInfo));
|
|
}
|
|
|
|
int file_info_buffer_push(FileInfoBuffer* buffer, FileInfo info) {
|
|
return list_push(buffer, &info);
|
|
}
|
|
|
|
void file_info_buffer_free(List* buffer) {
|
|
if (buffer) {
|
|
free(buffer->items);
|
|
free(buffer);
|
|
}
|
|
}
|
|
|
|
int compare_file_info(const void* a, const void* b) {
|
|
const FileInfo* info1 = (const FileInfo*)a;
|
|
const FileInfo* info2 = (const FileInfo*)b;
|
|
|
|
return strcmp(info1->name, info2->name);
|
|
}
|
|
|
|
void file_info_buffer_sort(FileInfoBuffer* buffer) {
|
|
if (!buffer || buffer->len <= 1) {
|
|
return;
|
|
}
|
|
|
|
qsort(buffer->items, buffer->len, sizeof(FileInfo), compare_file_info);
|
|
}
|
|
|
|
FileInfo* file_info_buffer_search(FileInfoBuffer* buffer, const char* filename) {
|
|
if (!buffer || !filename) return NULL;
|
|
|
|
FileInfo search_key = {.mode = 0, .name = (char*)filename};
|
|
|
|
return (FileInfo*)list_binary_search(buffer, &search_key, compare_file_info);
|
|
}
|
|
|
|
static int is_dot_or_dotdot(const char* s) {
|
|
return (s[0] == '.' && (s[1] == '\0' || (s[1] == '.' && s[2] == '\0')));
|
|
}
|
|
|
|
static char* join_path(const char* a, const char* b) {
|
|
size_t la = strlen(a);
|
|
size_t lb = strlen(b);
|
|
int need_sep = (la > 0 && a[la - 1] != '/');
|
|
|
|
char* s = calloc(la + (need_sep ? 1 : 0) + lb + 1, sizeof(char));
|
|
if (!s) {
|
|
perror("ERROR: calloc failed in join_path!");
|
|
exit(1);
|
|
}
|
|
|
|
memcpy(s, a, la);
|
|
size_t p = la;
|
|
if (need_sep) s[p++] = '/';
|
|
memcpy(s + p, b, lb + 1);
|
|
|
|
return s;
|
|
}
|
|
|
|
void normalize_path(char* path, char* rel) {
|
|
if (strlen(rel) == 0) return;
|
|
|
|
size_t path_len = strlen(path);
|
|
size_t rel_len = strlen(rel);
|
|
int consumers = rel_len / 3;
|
|
int counter = 0;
|
|
size_t latest = 0;
|
|
|
|
for (size_t idx = rel_len; idx < path_len; idx++) {
|
|
if (path[idx] == '/') {
|
|
counter++;
|
|
latest = idx;
|
|
if (counter == consumers) break;
|
|
}
|
|
if (path[idx] == '\0') break;
|
|
}
|
|
|
|
strcpy(path, path+latest+(counter > 0 ? 1 : 0));
|
|
}
|
|
|
|
// A function that returns the system path of the where the closest merk
|
|
// directory to the current working directory is located up in direction to
|
|
// the filesystem root
|
|
// Mutates relative in place to get the correct amount of ../ in relation to
|
|
// the directory which the software was called in
|
|
char* find_root(char* relative) {
|
|
char* current_dir = calloc(PATH_MAX, sizeof(char));
|
|
if (!current_dir) {
|
|
perror("ERROR: calloc in find_root failed!");
|
|
return NULL;
|
|
}
|
|
|
|
if (!getcwd(current_dir, PATH_MAX)) {
|
|
perror("ERROR: getcwd in find_root failed!");
|
|
free(current_dir);
|
|
return NULL;
|
|
}
|
|
|
|
char* search_dir = strdup(current_dir);
|
|
if (!search_dir) {
|
|
perror("ERROR: strdup in find_root failed!");
|
|
free(current_dir);
|
|
return NULL;
|
|
}
|
|
|
|
while (1) {
|
|
size_t dir_len = strlen(search_dir);
|
|
size_t merk_path_len = dir_len + strlen("/.merk") + 1;
|
|
char* merk_path = calloc(merk_path_len, sizeof(char));
|
|
if (!merk_path) {
|
|
perror("ERROR: calloc in find_root failed!");
|
|
free(current_dir);
|
|
free(search_dir);
|
|
return NULL;
|
|
}
|
|
|
|
snprintf(merk_path, merk_path_len, "%s/.merk", search_dir);
|
|
|
|
struct stat st;
|
|
if (stat(merk_path, &st) == 0 && S_ISDIR(st.st_mode)) {
|
|
free(current_dir);
|
|
free(merk_path);
|
|
char* result = strdup(search_dir);
|
|
free(search_dir);
|
|
return result;
|
|
}
|
|
|
|
free(merk_path);
|
|
|
|
if (strcmp(search_dir, "/") == 0) {
|
|
break;
|
|
}
|
|
|
|
char temp[PATH_MAX];
|
|
snprintf(temp, sizeof(temp), "../%s", relative);
|
|
strcpy(relative, temp);
|
|
|
|
char* parent = strrchr(search_dir, '/');
|
|
if (parent == search_dir) {
|
|
search_dir[1] = '\0';
|
|
} else if (parent) {
|
|
*parent = '\0';
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
free(current_dir);
|
|
free(search_dir);
|
|
printf("ERROR: you are in no merk repository!\nCreate a new repository with merk init\n");
|
|
return NULL;
|
|
}
|
|
|
|
void walk(char* base, char* base_rel, char* rel, FileInfoBuffer* file_infos, int full_repo_path, char* repo_root_path) {
|
|
DIR* dir = opendir(base);
|
|
if (!dir) {
|
|
return;
|
|
}
|
|
|
|
struct dirent* de;
|
|
while ((de = readdir(dir)) != NULL) {
|
|
if (is_dot_or_dotdot(de->d_name)) continue;
|
|
if (strcmp(de->d_name, ".merk") == 0) continue;
|
|
|
|
char* child_full = join_path(base, de->d_name);
|
|
if (!child_full) {
|
|
perror("ERROR: memory allocation failed in walk!");
|
|
closedir(dir);
|
|
exit(1);
|
|
}
|
|
|
|
struct stat st;
|
|
if (lstat(child_full, &st) != 0) {
|
|
free(child_full);
|
|
continue;
|
|
}
|
|
|
|
if (S_ISDIR(st.st_mode)) {
|
|
char* new_rel;
|
|
if (rel && rel[0]) {
|
|
new_rel = join_path(rel, de->d_name);
|
|
} else {
|
|
new_rel = strdup(de->d_name);
|
|
}
|
|
|
|
if (!new_rel) {
|
|
perror("ERROR: memory allocation failed in walk!");
|
|
free(child_full);
|
|
closedir(dir);
|
|
exit(1);
|
|
}
|
|
|
|
walk(child_full, base_rel, new_rel, file_infos, full_repo_path, repo_root_path);
|
|
free(new_rel);
|
|
} else {
|
|
char* relative_path;
|
|
if (rel && rel[0]) {
|
|
relative_path = join_path(rel, de->d_name);
|
|
} else {
|
|
relative_path = strdup(de->d_name);
|
|
}
|
|
|
|
if (!relative_path) {
|
|
perror("ERROR: memory allocation failed in walk!");
|
|
free(child_full);
|
|
closedir(dir);
|
|
exit(1);
|
|
}
|
|
|
|
if (!full_repo_path) {
|
|
normalize_path(relative_path, base_rel);
|
|
FileInfo info = (FileInfo){.mode=st.st_mode, .name=strdup(relative_path)};
|
|
file_info_buffer_push(file_infos, info);
|
|
free(relative_path);
|
|
}
|
|
else {
|
|
char* repo_path = get_repo_path(repo_root_path, relative_path);
|
|
FileInfo info = (FileInfo){.mode=st.st_mode, .name=strdup(repo_path)};
|
|
file_info_buffer_push(file_infos, info);
|
|
free(relative_path);
|
|
free(repo_path);
|
|
}
|
|
}
|
|
|
|
free(child_full);
|
|
}
|
|
|
|
closedir(dir);
|
|
}
|
|
|
|
// We assume that path is in the repo
|
|
char* get_repo_path(char* repo_root_path, char* path) {
|
|
char* real_path = realpath(path, NULL);
|
|
cut_path(repo_root_path, real_path);
|
|
|
|
size_t len = strlen(real_path);
|
|
if (len > 1 && real_path[len - 1] == '/') {
|
|
real_path[len - 1] = '\0';
|
|
}
|
|
|
|
return real_path;
|
|
}
|
|
|
|
int is_in_repo(char* repo_root_path, char* path) {
|
|
size_t root_len = strlen(repo_root_path);
|
|
char* path_base = strdup(path);
|
|
|
|
path_base[root_len] = '\0';
|
|
|
|
int cmp = strcmp(path_base, repo_root_path);
|
|
free(path_base);
|
|
|
|
return cmp;
|
|
}
|
|
|
|
void visualize_diff(File* old_version, File* new_version, ActionList* actions) {
|
|
int* deleted_lines = calloc(old_version->lines, sizeof(int));
|
|
int* inserted_lines = calloc(new_version->lines, sizeof(int));
|
|
|
|
if (!deleted_lines || !inserted_lines) {
|
|
free(deleted_lines);
|
|
free(inserted_lines);
|
|
return;
|
|
}
|
|
|
|
for (uint64_t i = 0; i < actions->len; i++) {
|
|
if (actions->actions[i].type == DELETE) {
|
|
deleted_lines[actions->actions[i].line_original] = 1;
|
|
} else if (actions->actions[i].type == INSERT) {
|
|
inserted_lines[actions->actions[i].line_changed] = 1;
|
|
}
|
|
}
|
|
|
|
uint64_t old_idx = 0, new_idx = 0;
|
|
|
|
while (old_idx < old_version->lines || new_idx < new_version->lines) {
|
|
// DELETE
|
|
if (old_idx < old_version->lines && deleted_lines[old_idx]) {
|
|
printf("%s%s%4ld | %s%s\n", RED_BG, BLACK_FG, old_idx+1, old_version->content[old_idx], RESET);
|
|
old_idx++;
|
|
}
|
|
// INSERT
|
|
else if (new_idx < new_version->lines && inserted_lines[new_idx]) {
|
|
printf("%s%s %4ld | %s%s\n", GREEN_BG, BLACK_FG, new_idx+1, new_version->content[new_idx], RESET);
|
|
new_idx++;
|
|
}
|
|
// STAYS
|
|
else if (old_idx < old_version->lines && new_idx < new_version->lines) {
|
|
printf("%4ld %4ld | %s\n", old_idx+1, new_idx+1, old_version->content[old_idx]);
|
|
old_idx++;
|
|
new_idx++;
|
|
}
|
|
// DELETE
|
|
else if (old_idx < old_version->lines) {
|
|
printf("%s%s%4ld | %s%s\n", RED_BG, BLACK_FG, old_idx+1, old_version->content[old_idx], RESET);
|
|
old_idx++;
|
|
}
|
|
// INSERT
|
|
else if (new_idx < new_version->lines) {
|
|
printf("%s%s %4ld | %s%s\n", GREEN_BG, BLACK_FG, new_idx+1, new_version->content[new_idx], RESET);
|
|
new_idx++;
|
|
}
|
|
}
|
|
|
|
free(deleted_lines);
|
|
free(inserted_lines);
|
|
}
|
|
|
|
// In this function we assume that base is a prefix of path
|
|
// Thus we just need the length of the base to jump ahead in the path
|
|
int cut_path(char* base, char* path) {
|
|
size_t base_len = strlen(base);
|
|
if (strlen(path) < base_len) return 0;
|
|
int add = strlen(path) != base_len ? 1 : 0;
|
|
|
|
strcpy(path, path+base_len+add);
|
|
|
|
if (add) {
|
|
char tmp[PATH_MAX];
|
|
snprintf(tmp, sizeof(tmp), "%s/", path);
|
|
strcpy(path, tmp);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
// In this function we assume that the end of the base and the beginning of the
|
|
// path are the same so we can concat them together at that part
|
|
// The path gets mutated in place
|
|
void combine_path(char* base, char* path) {
|
|
size_t base_len = 0;
|
|
|
|
for (size_t idx = strlen(base); idx > 0; idx--) {
|
|
if (base[idx] == '/') {
|
|
base_len = idx;
|
|
break;
|
|
}
|
|
}
|
|
|
|
char tmp[PATH_MAX];
|
|
snprintf(tmp, sizeof(tmp), "%*s/%s", base_len, base, path);
|
|
strcpy(path, tmp);
|
|
}
|
|
|
|
PathType get_path_type(const char* path) {
|
|
struct stat st;
|
|
int rc = stat(path, &st);
|
|
if (rc != 0) {
|
|
return (errno == ENOENT) ? PT_NOEXIST : PT_ERROR;
|
|
}
|
|
if (S_ISREG(st.st_mode)) return PT_FILE;
|
|
if (S_ISDIR(st.st_mode)) return PT_DIR;
|
|
return PT_OTHER;
|
|
}
|
|
|
|
char* get_file_content(char* path) {
|
|
if (!path) return NULL;
|
|
|
|
FILE* file = fopen(path, "rb");
|
|
if (!file) { perror("ERROR: could not open file in get_file_content!"); return NULL; }
|
|
if (fseek(file, 0, SEEK_END) != 0) { fclose(file); return NULL; }
|
|
long file_size = ftell(file);
|
|
if (file_size < 0) { perror("ERROR: file size is negative in get_file_content!"); fclose(file); return NULL; }
|
|
if (fseek(file, 0, SEEK_SET) != 0) { fclose(file); return NULL; }
|
|
size_t n = (size_t)file_size;
|
|
char* buf = (char*)calloc(n + 1, 1);
|
|
if (!buf) { fclose(file); return NULL; }
|
|
|
|
size_t got = fread(buf, 1, n, file);
|
|
fclose(file);
|
|
|
|
buf[got] = '\0';
|
|
|
|
return buf;
|
|
}
|
|
|
|
char* get_file_content_with_size(char* path, size_t* size) {
|
|
if (!path || !size) return NULL;
|
|
|
|
FILE* file = fopen(path, "rb");
|
|
if (!file) { perror("ERROR: could not open file in get_file_content_with_size!"); return NULL; }
|
|
if (fseek(file, 0, SEEK_END) != 0) { fclose(file); return NULL; }
|
|
long file_size = ftell(file);
|
|
if (file_size < 0) { perror("ERROR: file size is negative in get_file_content_with_size!"); fclose(file); return NULL; }
|
|
if (fseek(file, 0, SEEK_SET) != 0) { fclose(file); return NULL; }
|
|
size_t n = (size_t)file_size;
|
|
|
|
char* buf = (char*)malloc(n);
|
|
if (!buf) { fclose(file); return NULL; }
|
|
|
|
size_t got = fread(buf, 1, n, file);
|
|
fclose(file);
|
|
|
|
*size = got;
|
|
return buf;
|
|
}
|
|
|
|
// Create directory recursively
|
|
static int mkdir_recursive(const char *path, mode_t mode) {
|
|
char *path_copy = strdup(path);
|
|
if (!path_copy) return -1;
|
|
|
|
char *p = path_copy;
|
|
|
|
if (*p == '/') p++;
|
|
|
|
while (*p) {
|
|
while (*p && *p != '/') p++;
|
|
|
|
char saved = *p;
|
|
*p = '\0';
|
|
|
|
if (mkdir(path_copy, mode) == -1 && errno != EEXIST) {
|
|
free(path_copy);
|
|
return -1;
|
|
}
|
|
|
|
*p = saved;
|
|
if (*p) p++;
|
|
}
|
|
|
|
free(path_copy);
|
|
return 0;
|
|
}
|
|
|
|
// Create default config file
|
|
int create_default_config_file(char* config_path) {
|
|
char *dir_path = strdup(config_path);
|
|
if (!dir_path) return -1;
|
|
|
|
char *last_slash = strrchr(dir_path, '/');
|
|
if (last_slash) {
|
|
*last_slash = '\0';
|
|
if (mkdir_recursive(dir_path, 0755) < 0) {
|
|
perror("mkdir_recursive");
|
|
free(dir_path);
|
|
return -1;
|
|
}
|
|
}
|
|
free(dir_path);
|
|
|
|
FILE *fp = fopen(config_path, "w");
|
|
if (!fp) {
|
|
perror("fopen");
|
|
return -1;
|
|
}
|
|
|
|
fprintf(fp, "[user]\n");
|
|
fprintf(fp, "name = \"Your Name\"\n");
|
|
fprintf(fp, "email = \"your.email@example.com\"\n");
|
|
|
|
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);
|
|
} |