merk/src/utilities.c

409 lines
No EOL
11 KiB
C

#include "utilities.h"
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);
}
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;
}
// 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;
}