#include #include #include #include #include #include enum match_result { MATCH_RESULT_INIT, MATCH_RESULT_ROOT, MATCH_RESULT_COMMIT, MATCH_RESULT_ANYTHING, MATCH_RESULT_PKGVER, }; static inline size_t minimum(size_t lhs, size_t rhs); static char* relative_path_in_current_dir(git_repository* repo, const char* filename); static int try_git(int err, const char* msg); static int match_line(const git_diff_delta* delta, const git_diff_hunk* hunk, const git_diff_line* line, void* payload); /// Returns the smaller value from *lhs* and *rhs*. size_t minimum(const size_t lhs, const size_t rhs) { return lhs > rhs ? rhs : lhs; } /// Finds the relative path of *filename* from the workdir of *repo*. char* relative_path_in_current_dir(git_repository* repo, const char* filename) { const long maximum = pathconf(".", _PC_PATH_MAX); size_t len = 0 > maximum ? 1024 : maximum; char* buf = NULL; char* cwd = NULL; for (; NULL == cwd && 1024 * 50 > len; len *= 2) { if (NULL == (cwd = realloc(buf, len))) { if (NULL != buf) free(buf); return NULL; } buf = cwd; cwd = getcwd(buf, len); if (NULL == cwd && ERANGE != errno) { free(buf); return NULL; } } const char* workdir = git_repository_workdir(repo); const size_t prefix = minimum(strlen(cwd), strlen(workdir)); if (0 != strncmp(cwd, workdir, prefix)) { free(cwd); return NULL; } const size_t relative = strlen(cwd + prefix); if (NULL == (buf = malloc(relative + strlen(filename) + 2))) { free(cwd); return NULL; } strcpy(buf, cwd + prefix); if (0 < relative) strcat(buf, "/"); strcat(buf, filename); free(cwd); return buf; } /// If *err* is considered erroneous in context of libgit2 return code, print a message describing it, and prefix it /// with *msg* if it is not NULL. int try_git(const int err, const char* msg) { if (0 > err) { const git_error* e = git_error_last(); if (NULL != msg) dprintf(2, "%s: ", msg); dprintf(2, "%s\n", e->message); exit(1); } return err; } /// Callback for git_diff_foreach. Check each line reported by the diff looking for "pkgver" occurrance. If found, set /// *payload* to MATCH_RESULT_PKGVER, otherwise set it to MATCH_RESULT_ANYTHING, but only if it is not set to /// MATCH_RESULT_PKGVER already. int match_line(const git_diff_delta* delta, const git_diff_hunk* hunk, const git_diff_line* line, void* payload) { static const char* const PKGVER = "pkgver"; static const size_t PKGVER_LEN = 6; enum match_result* match = (enum match_result*) payload; switch (line->origin) { case GIT_DIFF_LINE_ADDITION: case GIT_DIFF_LINE_DELETION: if (PKGVER_LEN <= line->content_len && 0 == strncmp(line->content, PKGVER, PKGVER_LEN)) *match = MATCH_RESULT_PKGVER; else if (MATCH_RESULT_PKGVER != *match) *match = MATCH_RESULT_ANYTHING; break; default: } return 0; } int main(int argc, char* argv[]) { git_libgit2_init(); git_repository* repo = NULL; try_git(git_repository_open_ext(&repo, NULL, GIT_REPOSITORY_OPEN_FROM_ENV, NULL), "opening repo"); git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; diffopts.pathspec.strings = (char*[1]){relative_path_in_current_dir(repo, "PKGBUILD")}; diffopts.pathspec.count = 1; if (NULL == diffopts.pathspec.strings[0]) { dprintf(2, "finding PKGBUILD in repo\n"); exit(1); } git_pathspec* pathspec = NULL; try_git(git_pathspec_new(&pathspec, &diffopts.pathspec), "creating pathspec"); git_revwalk* walker = NULL; try_git(git_revwalk_new(&walker, repo), "creating revwalker"); try_git(git_revwalk_push_head(walker), "finding head"); git_oid oid; git_tree* earlier = NULL; git_tree* later = NULL; int relpkg = 1; enum match_result match = MATCH_RESULT_INIT; while (0 == git_revwalk_next(&oid, walker)) { git_commit* commit; try_git(git_commit_lookup(&commit, repo, &oid), "finding commit"); if (0 == git_commit_parentcount(commit)) match = MATCH_RESULT_ROOT; if (NULL != earlier) git_tree_free(earlier); earlier = later; try_git(git_commit_tree(&later, commit), "finding tree"); git_diff* diff; if (NULL == earlier) try_git(git_diff_tree_to_workdir_with_index(&diff, repo, later, &diffopts), "diff"); else try_git(git_diff_tree_to_tree(&diff, repo, later, earlier, &diffopts), "diff"); if (MATCH_RESULT_INIT != match) match = MATCH_RESULT_COMMIT; try_git(git_diff_foreach(diff, NULL, NULL, NULL, &match_line, &match), "searching history"); git_diff_free(diff); git_commit_free(commit); if (MATCH_RESULT_PKGVER == match) break; if (MATCH_RESULT_ANYTHING == match) relpkg++; } if (MATCH_RESULT_INIT == match) { dprintf(2, "PKGBUILD not found\n"); exit(1); } else printf("%d\n", relpkg); }