#include #include #include struct datever { int year; int month; int day; int revision; }; static int must(const char* msg, int err); static int is_dirty(git_repository* repo); static struct datever find_latest(git_repository* repo); static struct datever datever_init(); static struct datever datever_today(); static struct datever datever_from_time(time_t time); static int datever_is_valid(const struct datever* ver); static int datever_compare(const struct datever* lhs, const struct datever* rhs); int main() { git_libgit2_init(); git_repository* repo = NULL; must("open repository", git_repository_open_ext(&repo, NULL, GIT_REPOSITORY_OPEN_FROM_ENV, NULL)); const struct datever ver = find_latest(repo); printf("%d%02d%02d.%d\n", ver.year, ver.month, ver.day, ver.revision); git_libgit2_shutdown(); } /// If [[err]] is considered erroneous, print [[msg]] and the last libgit2 error to standard error and then terminate /// current process. int must(const char* const msg, const int err) { if (0 > err) { const git_error* const e = git_error_last(); if (NULL != msg) dprintf(2, "%s: ", msg); dprintf(2, "%s\n", e->message); exit(1); } return err; } int is_dirty(git_repository* repo) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-field-initializers" git_status_options statopts = GIT_STATUS_OPTIONS_INIT; #pragma GCC diagnostic pop statopts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; statopts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED; git_status_list* status; must("retrieve status", git_status_list_new(&status, repo, &statopts)); const int count = git_status_list_entrycount(status); git_status_list_free(status); return count > 0; } struct datever find_latest(git_repository* const repo) { git_revwalk* walker = NULL; must("walk revisions", git_revwalk_new(&walker, repo)); must("find HEAD", git_revwalk_push_head(walker)); git_oid oid; git_commit* commit; struct datever prev = is_dirty(repo) ? datever_today() : datever_init(); struct datever ver = datever_init(); while (0 == git_revwalk_next(&oid, walker)) { must("find commit", git_commit_lookup(&commit, repo, &oid)); const git_signature* author = git_commit_author(commit); ver = datever_from_time(author->when.time); if (datever_is_valid(&prev)) { if (0 == datever_compare(&prev, &ver)) ver.revision += prev.revision; else break; } prev = ver; git_commit_free(commit); } git_commit_free(commit); return prev; } /// Initialize empty, and thus invalid, version. struct datever datever_init() { return (struct datever){ .year = -1, .month = -1, .day = -1, .revision = -1, }; } struct datever datever_today() { return datever_from_time(time(NULL)); } struct datever datever_from_time(const time_t time) { const struct tm* tm = gmtime(&time); return (struct datever){ .year = tm->tm_year + 1900, .month = tm->tm_mon + 1, .day = tm->tm_mday, .revision = 1, }; } int datever_is_valid(const struct datever* const ver) { return 0 <= ver->year && 0 <= ver->month && 0 <= ver->day && 0 <= ver->revision; } /// Compares two versions date-wise ignoring revision count. Returns similarly to strcmp(3). int datever_compare(const struct datever* const lhs, const struct datever* const rhs) { if (lhs->year > rhs->year) return -1; if (lhs->year < rhs->year) return 1; if (lhs->month > rhs->month) return -1; if (lhs->month < rhs->month) return 1; if (lhs->day > rhs->day) return -1; if (lhs->day < rhs->day) return 1; return 0; }