#include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "git.h" #include "linked_list.h" #include "macros.h" #include "misc.h" int find_ignore_file(const char *path) { DIR *root = opendir(path); struct dirent entry, *result; while (readdir_r(root, &entry, &result) == 0 && result != NULL) { /* Is this entry a regular file, and is it the ignore file? */ if (entry.d_type == DT_REG && strcasecmp(entry.d_name, "pullreqd-ignore") == 0) { return 1; } } return 0; } void cleanup_git_repo(git_repo *repo) { if (repo->name != NULL) { free(repo->name); } if (repo->repo != NULL) { git_repository_free(repo->repo); } } void cleanup_git_repos(git_repo **repos) { for (int i = 0; repos[i] != NULL; i++) { cleanup_git_repo(repos[i]); repos[i] = NULL; } free(repos); } void cleanup_git(git_repo **repos) { cleanup_git_repos(repos); git_libgit2_shutdown(); } file *create_file(const char *root, const char *filename) { const size_t path_len = format_len("%s/%s", root, filename); char *path = calloc(path_len+1, sizeof(char)); long buf_size = 0; file *f = calloc(1, sizeof(file)); sprintf(path, "%s/%s", root, filename); f->name = (char *)filename; f->buf = read_file(path, &buf_size); return f; } void free_file(file *file) { if (file->name != NULL) { free(file->name); } if (file->buf != NULL) { free(file->buf); } } void free_files(file **files) { for (int i = 0; files[i] != NULL; i++) { free_file(files[i]); files[i] = NULL; } free(files); } static void free_git_branch(git_branch *branch) { if (branch->name != NULL) { free(branch->name); branch->name = NULL; } if (branch->repo != NULL) { free(branch->repo); branch->repo = NULL; } } static void free_comment(comment *comment) { if (comment->author != NULL) { free(comment->author); comment->author = NULL; } if (comment->desc != NULL) { free(comment->desc); comment->desc = NULL; } comment->reply = NULL; } static void free_comments(comment **comments) { for (int i = 0; comments[i] != NULL; ++i) { free_comment(comments[i]); comments[i] = NULL; } free(comments); } void cleanup_pull_request(pull_request *pr) { if (pr != NULL) { if (pr->title != NULL) { free(pr->title); pr->title = NULL; } if (pr->desc != NULL) { free(pr->desc); pr->desc = NULL; } if (pr->author != NULL) { free(pr->author); pr->author = NULL; } if (pr->comments != NULL) { free_comments(pr->comments); pr->comments = NULL; } if (pr->merge_branch != NULL) { free(pr->merge_branch); pr->merge_branch = NULL; } if (!pr->pr_type) { if (pr->patches != NULL) { free_files(pr->patches); pr->patches = NULL; } } else { if (pr->branch != NULL) { free_git_branch(pr->branch); pr->branch = NULL; } } free(pr); } } static linked_list *parse_dsv_str(const char *str, const char *delm) { linked_list *tail = NULL; for (; !is_empty(str); str = find_delm((char *)str, delm, 1)) { /* Get the length of the token. */ size_t tok_len = strcspn(str, delm); /* Create the token. */ char *tok = calloc(tok_len+1, sizeof(char)); /* Get the token from the string. */ memcpy(tok, str, tok_len); /* Add the token to the list.. */ tail = add_node(&tail, tok); } return tail; } int parse_remote_branch(void *ctx, void *ret, const keyword *key, keyword_val val) { if (key->type != TYPE_STRING) { if (key->offsets[0] == offsetof(pull_request, branch)) { if (key->offsets[1] == offsetof(git_branch, name) || key->offsets[1] == offsetof(git_branch, repo)) { pull_request *pr = (pull_request *)ret; if (pr->branch == NULL) { pr->branch = calloc(1, sizeof(git_branch)); } return 0; } } } return -1; } int parse_patch_list(void *ctx, void *ret, const keyword *key, keyword_val val) { /* Do we actually have a "patches" keyword? */ if (key->type == TYPE_STRING) { const char *root = (const char *)ctx; pull_request *pr = (pull_request *)ret; /* Is the supplied string non-empty? */ if (!is_empty(val.str)) { /* Parse the patch list. */ linked_list *patch_list = parse_dsv_str(val.str, " \t\v"); /* Get the number of patch files in the list. */ int num_patches = linked_list_size(patch_list); /* Create the patch list. */ pr->patches = calloc(num_patches+1, sizeof(file *)); /* Set the last entry to NULL, to denote the end of the list. */ pr->patches[num_patches--] = NULL; /* For each patch file in the list. */ for (linked_list *patch = patch_list; patch != NULL; patch = patch->prev, --num_patches) { char *patch_file = create_num_str((const char *)patch->data, num_patches); char *sanitized_patch_file = sanitize_str(patch_file); /* Create a new entry in the list. */ pr->patches[num_patches] = create_file(root, patch_file); pr->patches[num_patches]->name = (char *)patch->data; free(patch_file); free(sanitized_patch_file); } cleanup_linked_list(patch_list); return 1; } } return -1; } int parse_comment_reply(void *ctx, void *ret, const keyword *key, keyword_val val) { linked_list *comment_list = *(linked_list **)ctx; comment *comm = (comment *)ret; /* Do we already have a reply? */ if (comm->reply != NULL) { /* Check if this reply matches any other comment in the list. */ for (linked_list *node = get_tail(comment_list); node != NULL; node = node->prev) { comment *reply = (comment *)node->data; /* Do we have a match? */ if (comm->reply == reply) { return 1; } } } /* Do we have a "reply-to" keyword? */ if (key->type == TYPE_INT) { int found_reply = 0; /* Check if that comment already exists in the list. */ for (linked_list *node = get_tail(comment_list); node != NULL; node = node->prev) { comment *reply = (comment *)node->data; /* Do we have a match? */ if (reply->id == val.i) { /* Does this comment already have a reply? */ if (comm->reply != NULL) { /* Free the reply. */ free(comm->reply); } /* Set our reply comment to the matched comment. */ comm->reply = reply; found_reply = 1; break; } } /* Did we find a reply in the list? */ if (found_reply) { return 1; } } /* Do we not have a reply? */ if (comm->reply == NULL) { /* Create one. */ comm->reply = calloc(1, sizeof(comment)); } /* Which keyword do we have? */ switch (key->type) { /* reply-to */ case TYPE_INT : comm->reply->id = val.i; break; /* reply-author */ case TYPE_STRING: comm->reply->author = val.str; break; /* reply-date */ case TYPE_TIME : comm->reply->date = val.t; break; default : return -1; break; } return 1; } static int is_end_of_comment(void **ret, void *ctx, char *buf) { /* Do we have at least one blank line? */ if (strspn(buf, "\n") >= 1) { linked_list **comment_list = (linked_list **)ctx; if (comment_list != NULL) { /* Is our comment list NULL, or is there an existing entry? */ if (*comment_list == NULL || (*comment_list)->data == NULL) { add_node(comment_list, *ret); } *ret = calloc(1, sizeof(comment)); return 1; } else { return -1; } } return 0; } static int parse_comments_file(comment ***comments, char *buf) { if (comments == NULL) { return 9; } else { linked_list *comment_list = NULL; int ret = parse_key_value_file(calloc(1, sizeof(comment)), &comment_list, comment_keywords, buf, ":", is_end_of_comment); *comments = (comment **)linked_list_to_array(comment_list); cleanup_linked_list(comment_list); return ret; } } static int parse_info_file(pull_request *pr, char *buf, const char *root) { return parse_key_value_file(pr, (void *)root, info_keywords, buf, ":", NULL); } static int parse_comments_file_path(comment ***comments, const char *root) { file *f = create_file(root, "comments"); int ret = parse_comments_file(comments, f->buf); f->name = NULL; free_file(f); return ret; } static int parse_info_file_path(pull_request *pr, const char *root) { file *f = create_file(root, "info"); int ret = parse_info_file(pr, f->buf, root); f->name = NULL; free_file(f); return ret; } pull_request *get_pull_request(index_t *idx, const char *root, const char *repo) { /* Do we have a valid index? */ if (is_valid_index(idx)) { char *pr_dir; char *idx_str = index_to_str(idx); char *pr_root = (!is_empty(repo)) ? dir_path_name(root, (char *)repo) : (char *)root; /* Does the converted path of our index exist? */ if (index_path_exists(idx, (const char *)pr_root, &pr_dir) >= 0) { pull_request *pr = calloc(1, sizeof(pull_request)); if (!is_empty(repo)) { free(pr_root); } log(LOG_NOTICE, "Found PR #%s with path \"%s\".", idx_str, pr_dir); /* Did we successfully parse the info file? */ if (parse_info_file_path(pr, pr_dir) <= 1) { log(LOG_NOTICE, "Successfully parsed info file of PR #%s.", idx_str); /* Did we successfully parse the comments file? */ if (parse_comments_file_path(&pr->comments, pr_dir) <= 1) { log(LOG_NOTICE, "Successfully parsed comments file of PR #%s.", idx_str); } else { log(LOG_WARNING, "Failed to parse comments file of PR #%s.", idx_str); } free(pr_dir); return pr; } else { log(LOG_ERR, "Failed to parse info file of PR #%s.", idx_str); free(pr); free(pr_dir); return NULL; } } else { log(LOG_ERR, "No PR #%s found.", idx_str); if (!is_empty(repo)) { free(pr_root); } free(idx_str); return NULL; } } else { return NULL; } } int get_pr_type(void *ctx, void *data, const keyword *key, keyword_val *val) { pull_request *pr = (pull_request *)data; if (key->type == TYPE_INT && pr != NULL) { val->i = pr->pr_type; return 1; } else { return -1; } } int has_remote_branch(void *ctx, void *data, const keyword *key, keyword_val *val) { pull_request *pr = (pull_request *)data; return (pr->pr_type) ? 0 : -1; } int get_patch_list(void *ctx, void *data, const keyword *key, keyword_val *val) { pull_request *pr = (pull_request *)data; if (key->type == TYPE_STRING && !pr->pr_type && pr->patches != NULL) { int len = 0; char *tmp; for (int i = 0; pr->patches[i] != NULL; i++) { len += format_len("% *s", (int)strlen(pr->patches[i]->name)+(i > 0), pr->patches[i]->name); } val->str = calloc(len+1, sizeof(char)); tmp = val->str; for (int i = 0; pr->patches[i] != NULL; i++) { tmp += sprintf(tmp, "%*s", (int)strlen(pr->patches[i]->name)+(i > 0), pr->patches[i]->name); } return 2; } else { return -1; } } int create_info_file(pull_request *pr, const char *pr_root) { char *filename; char *file_buf; FILE *fp; /* Is the PR NULL? */ if (pr == NULL) { log(LOG_ERR,"Pull request is NULL."); return 0; } /* Is the PR root NULL? */ if (pr_root == NULL) { log(LOG_ERR,"Pull request root is NULL."); return 0; } filename = calloc(format_len("%s/info", pr_root), sizeof(char)); /* Create the contents of the new info file. */ file_buf = create_key_value_file(pr, (void *)pr_root, info_keywords, &(const delimiter){":", 0, 1}, &(const delimiter){"\n", 0, 0}); /* Append /info to the PR root. */ sprintf(filename, "%s/info", pr_root); /* Is there already an info file? */ if (access(filename, F_OK) == 0) { long size = 0; char *buf = read_file(filename, &size); log(LOG_NOTICE, "Info file already exists."); /* Did we read the file? */ if (buf != NULL) { /* Are the contents of the info file the same? */ if (strcmp(file_buf, buf) == 0) { log(LOG_NOTICE, "New info file is the same as the existing one."); free(filename); free(file_buf); free(buf); return 2; } free(buf); } else { log(LOG_ERR, "Couldn't open existing info file."); return 0; } } /* Open the info file. */ fp = fopen(filename, "w"); /* Did we fail to open the file? */ if (fp == NULL) { log(LOG_ERR, "Failed to open info file."); return 0; } fwrite(file_buf, sizeof(char), strlen(file_buf), fp); fclose(fp); free(filename); free(file_buf); return 1; } int get_comment_reply(void *ctx, void *data, const keyword *key, keyword_val *val) { comment *comm = (comment *)data; if (comm->reply != NULL) { /* Which keyword do we have? */ switch (key->type) { /* reply-to */ case TYPE_INT : val->i = comm->reply->id; break; /* reply-author */ case TYPE_STRING: val->str = comm->reply->author; break; /* reply-date */ case TYPE_TIME : val->t = comm->reply->date; break; default : return -1; break; } return 1; } else { return -1; } } int get_comment_len(comment *comment) { struct tm tm; int len = format_len("id: %i\n", comment->id); localtime_r(&comment->date, &tm); len += format_len("author: %s\n", comment->author); len += format_len("date: ") + strftime(NULL, -1, "%a %b %e %H:%M:%S %Y %z\n", &tm); /* Is this comment a reply? */ if (comment->reply != NULL) { localtime_r(&comment->reply->date, &tm); len += format_len("reply-to: %i\n", comment->reply->id); len += format_len("reply-author: %s\n", comment->reply->author); len += format_len("reply-date: ") + strftime(NULL, -1, "%a %b %e %H:%M:%S %Y %z\n", &tm); } len += format_len("description: %s\n\n", comment->desc); return len; } int add_comment(comment *comment, const char *pr_root) { int len; int buf_len; int j = 0; char *filename; char *file_buf; FILE *fp; struct tm tm; /* Is the comment NULL? */ if (comment == NULL) { log(LOG_ERR, "Comment is NULL."); return 0; } /* Is the PR directory NULL? */ if (pr_root == NULL) { log(LOG_ERR,"Pull request root is NULL."); return 0; } localtime_r(&comment->date, &tm); len = strlen(pr_root) + strlen("/comments"); buf_len = get_comment_len(comment); filename = calloc(len+1, sizeof(char)); file_buf = calloc(buf_len+1, sizeof(char)); j = sprintf(file_buf, "id: %i\n", comment->id); j += sprintf(file_buf+j, "author: %s\n", comment->author); j += sprintf(file_buf+j, "date: "); j += strftime(file_buf+j, -1, "%a %b %e %H:%M:%S %Y %z\n", &tm); /* Is this comment a reply? */ if (comment->reply != NULL) { localtime_r(&comment->reply->date, &tm); j += sprintf(file_buf+j, "reply-to: %i\n", comment->reply->id); j += sprintf(file_buf+j, "reply-author: %s\n", comment->reply->author); j += sprintf(file_buf+j, "reply-date: "); j += strftime(file_buf+j, -1, "%a %b %e %H:%M:%S %Y %z\n", &tm); } j += sprintf(file_buf+j, "description: %s\n\n", comment->desc); /* Append /comments to the PR root. */ sprintf(filename, "%s/comments", pr_root); /* Open the comments file. */ fp = fopen(filename, "a"); /* Did we fail to open the file? */ if (fp == NULL) { log(LOG_ERR, "Failed to open comments file."); return 0; } fwrite(file_buf, sizeof(char), buf_len, fp); fclose(fp); return 1; } file **get_branch_commits(git_branch *br) { return NULL; } git_repo *get_repo(git_repo **repos, const char *name) { if (!is_empty(name)) { for (int i = 0; repos[i] != NULL; ++i) { if (!is_empty(repos[i]->name) && !strcmp(repos[i]->name, name)) { return repos[i]; } } } return NULL; } int create_pull_request_branch(pull_request *pr, index_t *idx, git_repo *repo) { int ret = 0; /* Is this a NULL PR? */ if (pr == NULL) { log(LOG_ERR, "Pull Request is NULL."); return -1; } else { /* Prepend "pr" to the index path to create the PR branch index. */ char *pr_branch_index = make_index_path("pr", idx, 0); /* Is the PR branch index empty? */ if (is_empty(pr_branch_index)) { log(LOG_ERR, "Empty PR branch index."); return -1; } else { /* Create the PR branch by just using the PR branch index. */ char *pr_branch = pr_branch_index; /* Are we using a remote branch? */ if (pr->pr_type) { /* Append the branch name to the PR branch index, if it isn't empty. */ pr_branch = dir_path_name((const char *)pr_branch_index, pr->branch->name); /* Free the PR branch index */ free(pr_branch_index); } /* Is repo non-NULL, and the git repo non-NULL? */ if (repo != NULL && repo->repo != NULL) { /* Are we using a remote branch? */ if (pr->pr_type) { git_branch *branch = pr->branch; /* Is the remote repo's URL non-empty? */ if (!is_empty(branch->repo)) { git_remote *remote; /* Did we successfully create the anonymous remote? */ if (!git_remote_create_anonymous(&remote, repo->repo, branch->repo)) { char *branch_name = branch->name; char *refspec; const git_strarray refspecs = {&refspec, 1}; /* Is the branch name empty? */ if (is_empty(branch->name)) { git_buf default_branch = {0}; log(LOG_WARNING, "Empty remote repo branch name, using default remote branch instead."); /* Did we successfully get the default remote branch? */ if (!git_remote_default_branch(&default_branch, remote)) { branch_name = default_branch.ptr; } else { log(LOG_ERR, "Failed to get default branch of remote repo \"%s\".", branch->repo); free(pr_branch); return -1; } } refspec = calloc(format_len("%s:%s", branch_name, pr_branch)+1, sizeof(char)); sprintf(refspec, "%s:%s", branch_name, pr_branch); /* Did we successfully fetch the remote repo? */ if (!git_remote_fetch(remote, &refspecs, NULL, NULL)) { log(LOG_NOTICE, "Successfully fetched branch \"%s\" of remote repo \"%s\" into branch \"%s\".", branch_name, branch->repo, pr_branch); ret = 0; } else { log(LOG_ERR, "Failed to fetch remote repo \"%s\".", branch->repo); ret = -1; } /* Did we actually use the default remote branch? */ if (branch_name != branch->name) { free(branch_name); } free(refspec); free(pr_branch); return ret; } else { log(LOG_ERR, "Failed to create anonymous remote for remote repo \"%s\".", branch->repo); free(pr_branch); return -1; } } } } else { log(LOG_ERR, "Git repo is NULL."); free(pr_branch); return -1; } } } } int create_pull_request_dir(pull_request *pr, index_t *idx, const char *root, const char *repo) { int ret = 0; struct stat st; file **commits; char *pr_dir; char *pr_root = (!is_empty(repo)) ? dir_path_name(root, (char *)repo) : (char *)root; char *reason; /* Is this a NULL PR? */ if (pr == NULL) { log(LOG_ERR, "Pull Request is NULL."); return -1; } /* Use the index as the directory path. */ pr_dir = make_index_path((const char *)pr_root, idx, 0); /* Free the PR root, if the supplied repo isn't empty. */ if (!is_empty(repo)) { free(pr_root); } /* Did we fail to make an indexed path? */ if (pr_dir == NULL) { /* Is the PR title empty? */ if (is_empty(pr->title)) { log(LOG_ERR, "Empty PR title."); return -1; } /* Use the title of the PR as the directory path. */ pr_dir = sanitized_dir_path_name(root, pr->title); } /* Is there no existing directory? */ switch (is_dir(pr_dir)) { /* PR root exists, and is a directory. */ case 1 : break; /* PR root exists, but isn't a directory. */ case 0 : log(LOG_ERR, "PR root \"%s\" exists, but isn't a directory.", pr_dir); free(pr_dir); return -1; break; /* PR root doesn't exist. */ default : mkdirp(pr_dir, 0755); break; } /* Did we fail to create the info file? */ if (!create_info_file(pr, pr_dir)) { log(LOG_ERR, "Failed to create info file."); return -1; } /* Get the patch files. */ commits = (pr->pr_type) ? get_branch_commits(pr->branch) : pr->patches; /* Is the patch file array non-NULL? */ if (commits != NULL) { /* Make patch files for each commit of PR. */ for (int i = 0; commits[i] != NULL; i++) { FILE *fp; /*char *filename = dir_path_name(root, sanitize_str(create_num_str(commits[i]->name, i)));*/ char *filename = create_num_str(commits[i]->name, i); char *file = sanitized_dir_path_name(pr_dir, filename); /* Does this patch file exists? */ if (access(file, F_OK) == 0) { long size = 0; char *buf = read_file(file, &size); log(LOG_NOTICE, "Patch file %s already exists.", filename); /* Did we read the file? */ if (buf != NULL) { /* Are the contents of the patch file the same? */ if (strcmp(commits[i]->buf, buf) == 0) { /* Skip creating this patch file. */ log(LOG_NOTICE, "Patch file %s is the same as the new one.", filename); free(buf); free(filename); free(file); continue; } free(buf); } else { log(LOG_WARNING, "Couldn't open patch file %s.", filename); free(filename); free(file); continue; } } /* Create the format patch file for this commit. */ fp = fopen(file, "w"); fwrite(commits[i]->buf, sizeof(char), strlen(commits[i]->buf), fp); fclose(fp); free(filename); free(file); } if (commits != NULL && pr->pr_type) { free_files(commits); } } else { log(LOG_WARNING, "Patch file array is NULL."); } free(pr_dir); return ret; } git_repo **init_git(config *cfg) { git_repo **repos = NULL; linked_list *repo_list = NULL; DIR *root = opendir(cfg->git_root); struct dirent entry, *result; log(LOG_INFO, "Initializing libgit2."); int ret = git_libgit2_init(); log(LOG_INFO, "Searching \"%s\" for repositories.", cfg->git_root); /* Find all git repos in the git root. */ while (readdir_r(root, &entry, &result) == 0 && result != NULL) { /*log(LOG_DEBUG, "entry.d_name: %s, result->d_name: %s", entry.d_name, result->d_name);*/ /* Is this entry a directory? */ if (entry.d_type == DT_DIR) { /* Is the entry neither ".", nor ".."? */ if (strcmp(entry.d_name, ".") && strcmp(entry.d_name, "..")) { char *repo_dir = calloc(format_len("%s/%s", cfg->git_root, entry.d_name), sizeof(char)); /* Append the directory name to the git root. */ /* Could also do this: * memcpy(repo_dir, cfg->git_root, strlen(cfg->git_repo)); * strcat(repo_dir, entry.d_name); */ sprintf(repo_dir, "%s/%s", cfg->git_root, entry.d_name); /* Was no ignore file found? */ if (!find_ignore_file(repo_dir)) { git_repo *repo = calloc(1, sizeof(git_repo)); repo->name = make_str(entry.d_name); /* Did we fail to open the git repo? */ if (git_repository_open(&repo->repo, repo_dir)) { log(LOG_ERR, "Failed to open git repository %s, ignoring.", entry.d_name); } else { log(LOG_INFO, "Successfully opened git repository %s", entry.d_name); repo_list = add_node(&repo_list, repo); } } free(repo_dir); } } } /* Did we find any repos? */ if (repo_list != NULL) { log(LOG_INFO, "Found, and opened %i repositories.", linked_list_size(repo_list)); /* Create the git repo array from our linked list. */ repos = (git_repo **)linked_list_to_array(repo_list); cleanup_linked_list(repo_list); } else { log(LOG_ERR, "Couldn't find, and/or open any repositories."); } return repos; }