#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; }