diff options
author | EuAndreh <eu@euandre.org> | 2021-08-01 20:01:11 -0300 |
---|---|---|
committer | EuAndreh <eu@euandre.org> | 2021-08-01 20:26:09 -0300 |
commit | 24d7071e146dc8085fd0870983bedd571f59868c (patch) | |
tree | cc7354269a8c24e4ba0f2729e8941f5e04ea4771 | |
parent | src/templates/: Point to the ../static folder for CSS and logo (diff) | |
download | gistatic-24d7071e146dc8085fd0870983bedd571f59868c.tar.gz gistatic-24d7071e146dc8085fd0870983bedd571f59868c.tar.xz |
src/gistatic.c: Support generating refs.html file
-rw-r--r-- | src/gistatic.c | 1023 | ||||
-rw-r--r-- | src/templates/refs.html | 2 |
2 files changed, 773 insertions, 252 deletions
diff --git a/src/gistatic.c b/src/gistatic.c index 3464e89..19d1654 100644 --- a/src/gistatic.c +++ b/src/gistatic.c @@ -46,15 +46,30 @@ static const int EXIT_USAGE = 2; static const char *const CATALOG_NAME = PROGNAME; static nl_catd catalog_descriptor = NULL; -#define MSG_DEFAULT_TITLE 1 -#define MSG_LOGO_ALT 2 -#define MSG_FOOTER_TEMPLATE 3 -#define MSG_LANG 4 -#define MSG_NAME 5 -#define MSG_DESCRIPTION 6 -#define MSG_LAST_COMMIT 7 -#define MSG_USAGE 8 -#define MSG_HELP 9 +#define MSG_DEFAULT_TITLE 1 +#define MSG_LOGO_ALT 2 +#define MSG_FOOTER_TEMPLATE 3 +#define MSG_LANG 4 +#define MSG_NAME 5 +#define MSG_DESCRIPTION 6 +#define MSG_LAST_COMMIT 7 +#define MSG_USAGE 8 +#define MSG_HELP 9 +#define MSG_COMMIT_FEED 10 +#define MSG_TAGS_FEED 11 +#define MSG_NAV_FILES 12 +#define MSG_NAV_LOG 13 +#define MSG_NAV_REFS 14 +#define MSG_THEAD_BRANCH 15 +#define MSG_THEAD_COMMITMSG 16 +#define MSG_THEAD_AUTHOR 17 +#define MSG_THEAD_DATE 18 +#define MSG_THEAD_TAG 19 +#define MSG_THEAD_DOWNLOAD 20 +#define MSG_MISSING_URL 21 +#define MSG_MISSING_ARGS 22 +#define MSG_INCOMPATIBLE_OPTIONS 23 +#define MSG_ERR_NONDIRECT_REF 24 static const char *const MSGS[] = { "", @@ -67,6 +82,21 @@ static const char *const MSGS[] = { [MSG_LAST_COMMIT]="Last commit", [MSG_USAGE]="Usage: -h -o -i\n-z -x...\n", [MSG_HELP]="help: ...\n", + [MSG_COMMIT_FEED]="commit feed", + [MSG_TAGS_FEED]="tags feed", + [MSG_NAV_FILES]="files", + [MSG_NAV_LOG]="log", + [MSG_NAV_REFS]="refs", + [MSG_THEAD_BRANCH]="Branch", + [MSG_THEAD_COMMITMSG]="Commit message", + [MSG_THEAD_AUTHOR]="Author", + [MSG_THEAD_DATE]="Date", + [MSG_THEAD_TAG]="Tag", + [MSG_THEAD_DOWNLOAD]="Download", + [MSG_MISSING_URL]="Missing '-u CLONE_URL'", + [MSG_MISSING_ARGS]="Missing [PATH | [PATHS]]", + [MSG_INCOMPATIBLE_OPTIONS]="Incompatible options -u and -i", + [MSG_ERR_NONDIRECT_REF]="Git reference is not direct", NULL }; @@ -163,25 +193,50 @@ static const char *const STYLE_STR = "" " margin: 0 auto 0 auto;\n" "}\n" "\n" - "img.logo {\n" - " height: 3em;\n" - " width: 3em;\n" + ".logo {\n" + " height: 6em;\n" + " width: 6em;\n" "}\n" "\n" - ".idx-header {\n" + ".header-horizontal-grouping {\n" " display: flex;\n" " align-items: center;\n" " margin-top: 1em;\n" " margin-bottom: 1em;\n" "}\n" "\n" - ".idx-header-description {\n" + ".header-description {\n" " margin-left: 1em;\n" "}\n" "\n" + "nav ul {\n" + " display: flex;\n" + " list-style-type: none;\n" + " margin-bottom: 0;\n" + "}\n" + "\n" + "nav li {\n" + " margin-left: 10px;\n" + "}\n" + "\n" + "nav a {\n" + " padding: 2px 8px 0px 8px;\n" + " text-decoration: none;\n" + " color: black;\n" + "}\n" + "\n" + "nav a:hover {\n" + " text-decoration: underline;\n" + "}\n" + "\n" + ".selected-nav-item {\n" + " background-color: hsl(0, 0%, 87%);\n" + "}\n" + "\n" "hr {\n" + " margin-top: 0;\n" " border: 0;\n" - " border-top: 3px solid hsl(0, 0%, 67%);\n" + " border-top: 3px solid hsl(0, 0%, 87%);\n" "}\n" "\n" "table {\n" @@ -194,7 +249,7 @@ static const char *const STYLE_STR = "" "}\n" "\n" "tbody tr:hover {\n" - " background-color: hsl(0, 0%, 93%);" + " background-color: hsl(0, 0%, 93%);\n" "}\n" "\n" "td {\n" @@ -208,12 +263,65 @@ static const char *const STYLE_STR = "" ""; +#define DESCRIPTION_MAXLENGTH 81 static bool verbose = false; static const char *const GIT_SUFFIX = ".git"; static const char *const PROJECT_HOMEPAGE_LINK = "<a href=\"https://euandreh.xyz/" PROGNAME "\">" PROGNAME "</a>."; +static void logerr(const char *const s, const char *const msg, int lineno) { + fprintf( + stderr, + "%s:%s:%d: %s: %s\n", + PROGNAME, + __FILE__, + lineno, + s, + msg + ); +} + +static void logerrs( + const char *const pre, + const char *const mid, + const char *const post, + const char *const msg, + int lineno +) { + fprintf( + stderr, + "%s:%s:%d: %s%s%s: %s\n", + PROGNAME, + __FILE__, + lineno, + pre, + mid, + post, + msg + ); +} + +static void logerrl( + const char *const pre, + const size_t mid, + const char *const post, + const char *const msg, + int lineno +) { + fprintf( + stderr, + "%s:%s:%d: %s%ld%s: %s\n", + PROGNAME, + __FILE__, + lineno, + pre, + mid, + post, + msg + ); +} + static const char *_(int msg_id) { if (!catalog_descriptor || catalog_descriptor == (nl_catd)-1) { return MSGS[msg_id]; @@ -222,15 +330,7 @@ static const char *_(int msg_id) { const char *const ret = catgets(catalog_descriptor, NL_SETD, msg_id, MSGS[msg_id]); if (errno && verbose) { - fprintf( - stderr, - "%s:%s:%d: catgets(%d): %s\n", - PROGNAME, - __FILE__, - __LINE__, - msg_id, - strerror(errno) - ); + logerrl("catgets(", msg_id, ")", strerror(errno), __LINE__); } return ret; } @@ -251,14 +351,7 @@ static void test_underscore() { static int print_msg(FILE *const fd, const char *const msg) { if (fprintf(fd, "%s", msg) < 0) { - fprintf( - stderr, - "%s:%s:%d: fprintf(): %s\n", - PROGNAME, - __FILE__, - __LINE__, - strerror(errno) - ); + logerr("fprintf()", strerror(errno), __LINE__); return -1; } return 0; @@ -284,14 +377,7 @@ static char *remove_suffix(char *const s, const char *const suffix) { char *const name = strdup(s); if (!name) { - fprintf( - stderr, - "%s:%s:%d: strdup(\"s\"): %s\n", - PROGNAME, - __FILE__, - __LINE__, - strerror(errno) - ); + logerrs("strdup(\"", s, "\")", strerror(errno), __LINE__); return NULL; } @@ -415,15 +501,7 @@ static char *strjoin(const char *const s1, const char *const s2) { const size_t size = strlen(s1) + strlen(s2) + sizeof('\0'); char *const s = malloc(size); if (!s) { - fprintf( - stderr, - "%s:%s:%d: malloc(%ld): %s\n", - PROGNAME, - __FILE__, - __LINE__, - size, - strerror(errno) - ); + logerrl("malloc(", size, ")", strerror(errno), __LINE__); return NULL; } @@ -486,17 +564,9 @@ static void test_strjoin() { #endif static char *formatted_date(const time_t time_sec) { - struct tm *time_utc = gmtime(&time_sec); + const struct tm *const time_utc = gmtime(&time_sec); if (!time_utc) { - fprintf( - stderr, - "%s:%s:%d: gmtime(%ld): %s\n", - PROGNAME, - __FILE__, - __LINE__, - time_sec, - strerror(errno) - ); + logerrl("gmtime(", time_sec, ")", strerror(errno), __LINE__); return NULL; } @@ -504,15 +574,7 @@ static char *formatted_date(const time_t time_sec) { const size_t size = (strlen("XXX-XX-XX XX:XX") + sizeof('\0')) * 2; char *const formatted = malloc(size); if (!formatted) { - fprintf( - stderr, - "%s:%s:%d: malloc(%ld): %s\n", - PROGNAME, - __FILE__, - __LINE__, - size, - strerror(errno) - ); + logerrl("malloc(", size, ")", strerror(errno), __LINE__); return NULL; } @@ -628,15 +690,7 @@ static char *escape_html(const char *s) { const size_t size = escaped_size + sizeof('\0'); char *const escaped = malloc(size); if (!escaped) { - fprintf( - stderr, - "%s:%s:%d: malloc(%ld): %s\n", - PROGNAME, - __FILE__, - __LINE__, - size, - strerror(errno) - ); + logerrl("malloc(", size, ")", strerror(errno), __LINE__); return NULL; } escaped[0] = '\0'; @@ -759,7 +813,7 @@ static void test_escape_html() { } #endif -static int write_header(FILE *const fd, const char *const idx_title) { +static int index_write_header(FILE *const fd, const char *const idx_title) { const int e = fprintf( fd, "" @@ -774,12 +828,12 @@ static int write_header(FILE *const fd, const char *const idx_title) { " </head>\n" " <body>\n" " <header>\n" - " <h1 class=\"idx-header\">\n" + " <div class=\"header-horizontal-grouping\">\n" " <img alt=\"%s\" class=\"logo\" src=\"logo.svg\" />\n" - " <span class=\"idx-header-description\">\n" + " <h1 class=\"header-description\">\n" " %s\n" - " </span>\n" - " </h1>\n" + " </h1>\n" + " </div>\n" " <hr />\n" " </header>\n" " <main>\n" @@ -807,36 +861,31 @@ static int write_header(FILE *const fd, const char *const idx_title) { _(MSG_LAST_COMMIT) ); if (e < 0) { - fprintf( - stderr, - "%s:%s:%d: fprintf(): %s\n", - PROGNAME, - __FILE__, - __LINE__, - strerror(errno) - ); + logerr("fprintf()", strerror(errno), __LINE__); return -1; } return 0; } -static int write_footer(FILE *const fd) { - const size_t footer_size = strlen(_(MSG_FOOTER_TEMPLATE)) - strlen("%s") +static char *footer_signature_string() { + const size_t signature_size = strlen(_(MSG_FOOTER_TEMPLATE)) - strlen("%s") + strlen(PROJECT_HOMEPAGE_LINK) + sizeof('\0'); - char *const footer_text = malloc(footer_size); - if (!footer_text) { - fprintf( - stderr, - "%s:%s:%d: malloc(%ld): %s\n", - PROGNAME, - __FILE__, - __LINE__, - footer_size, - strerror(errno) - ); + char *const signature_text = malloc(signature_size); + if (!signature_text) { + logerrl("malloc(", signature_size, ")", strerror(errno), + __LINE__); + return NULL; + } + + sprintf(signature_text, _(MSG_FOOTER_TEMPLATE), PROJECT_HOMEPAGE_LINK); + return signature_text; +} + +static int index_write_footer(FILE *const fd) { + char *const signature_text = footer_signature_string(); + if (!signature_text) { return -1; } - sprintf(footer_text, _(MSG_FOOTER_TEMPLATE), PROJECT_HOMEPAGE_LINK); const int e = fprintf( fd, @@ -852,25 +901,18 @@ static int write_footer(FILE *const fd) { " </footer>\n" " </body>\n" "</html>\n", - footer_text + signature_text ); - free(footer_text); + free(signature_text); if (e < 0) { - fprintf( - stderr, - "%s:%s:%d: fprintf(): %s\n", - PROGNAME, - __FILE__, - __LINE__, - strerror(errno) - ); + logerr("fprintf()", strerror(errno), __LINE__); return -1; } return 0; } -static char *last_commit_date(struct git_repository *repo) { +static char *last_commit_date(struct git_repository *const repo) { struct git_commit *commit = NULL; struct git_revwalk *walker = NULL; struct git_oid oid; @@ -884,14 +926,7 @@ static char *last_commit_date(struct git_repository *repo) { ) { char *const default_return = strdup(""); if (!default_return) { - fprintf( - stderr, - "%s:%s:%d: strdup(\"\"): %s\n", - PROGNAME, - __FILE__, - __LINE__, - strerror(errno) - ); + logerr("strdup(\"\")", strerror(errno), __LINE__); } ret = default_return; goto cleanup; @@ -949,14 +984,7 @@ static void test_last_commit_date() { static int write_logo(FILE *const fd) { if (fprintf(fd, "%s", LOGO_STR) < 0) { - fprintf( - stderr, - "%s:%s:%d: fprintf(): %s\n", - PROGNAME, - __FILE__, - __LINE__, - strerror(errno) - ); + logerr("fprintf()", strerror(errno), __LINE__); return -1; } return 0; @@ -964,22 +992,14 @@ static int write_logo(FILE *const fd) { static int write_style(FILE *const fd) { if (fprintf(fd, "%s", STYLE_STR) < 0) { - fprintf( - stderr, - "%s:%s:%d: fprintf(): %s\n", - PROGNAME, - __FILE__, - __LINE__, - strerror(errno) - ); + logerr("fprintf()", strerror(errno), __LINE__); return -1; } return 0; } -static int write_index_row(FILE *const fd, char *const repopath) { +static int index_write_row(FILE *const fd, char *const repopath) { int ret = 0; - char description[81] = ""; int e; struct git_repository *repo = NULL; @@ -1011,29 +1031,16 @@ static int write_index_row(FILE *const fd, char *const repopath) { goto cleanup; } + char description[DESCRIPTION_MAXLENGTH] = ""; FILE *const f = fopen(description_path, "r"); if (f) { if (!fgets(description, sizeof(description), f)) { - fprintf( - stderr, - "%s:%s:%d: fgets(\"%s\"): %s\n", - PROGNAME, - __FILE__, - __LINE__, - description_path, - strerror(errno) - ); + logerrs("fgets(\"", description_path, "\")", + strerror(errno), __LINE__); } if (fclose(f)) { - fprintf( - stderr, - "%s:%s:%d: fclose(\"%s\"): %s\n", - PROGNAME, - __FILE__, - __LINE__, - description_path, - strerror(errno) - ); + logerrs("fclose(\"", description_path, "\")", + strerror(errno), __LINE__); ret = -1; goto cleanup; } @@ -1070,14 +1077,7 @@ static int write_index_row(FILE *const fd, char *const repopath) { encoded_date ); if (e < 0) { - fprintf( - stderr, - "%s:%s:%d: fprintf(): %s\n", - PROGNAME, - __FILE__, - __LINE__, - strerror(errno) - ); + logerr("fprintf()", strerror(errno), __LINE__); ret = -1; goto cleanup; } @@ -1093,9 +1093,9 @@ cleanup: return ret; } -static int write_index( +static int index_write( const char *const outdir, - const char *const idx_description, + const char *const title, const int repoc, char *const repov[] ) { @@ -1117,51 +1117,30 @@ static int write_index( } if (!(index_fd = fopen(index_path, "w"))) { - fprintf( - stderr, - "%s:%s:%d: fopen(\"%s\"): %s\n", - PROGNAME, - __FILE__, - __LINE__, - index_path, - strerror(errno) - ); + logerrs("fopen(\"", index_path, "\")", strerror(errno), + __LINE__); ret = -1; goto cleanup; } if (!(logo_fd = fopen(logo_path, "w"))) { - fprintf( - stderr, - "%s:%s:%d: fopen(\"%s\"): %s\n", - PROGNAME, - __FILE__, - __LINE__, - logo_path, - strerror(errno) - ); + logerrs("fopen(\"", logo_path, "\")", strerror(errno), + __LINE__); ret = -1; goto cleanup; } if (!(style_fd = fopen(style_path, "w"))) { - fprintf( - stderr, - "%s:%s:%d: fopen(\"%s\"): %s\n", - PROGNAME, - __FILE__, - __LINE__, - style_path, - strerror(errno) - ); + logerrs("fopen(\"", style_path, "\")", strerror(errno), + __LINE__); ret = -1; goto cleanup; } - if (write_header(index_fd, idx_description)) { + if (index_write_header(index_fd, title)) { ret = -1; goto cleanup; } for (int i = 0; i < repoc; i++) { - const int e = write_index_row(index_fd, repov[i]); + const int e = index_write_row(index_fd, repov[i]); if (e == -1) { ret = -1; goto cleanup; @@ -1172,7 +1151,7 @@ static int write_index( } if ( - write_footer(index_fd) + index_write_footer(index_fd) || write_logo(logo_fd) || write_style(style_fd) ) { @@ -1182,39 +1161,18 @@ static int write_index( cleanup: if (style_fd && fclose(style_fd)) { - fprintf( - stderr, - "%s:%s:%d: fclose(\"%s\"): %s\n", - PROGNAME, - __FILE__, - __LINE__, - style_path, - strerror(errno) - ); + logerrs("fclose(\"", style_path, "\")", strerror(errno), + __LINE__); ret = -1; } if (logo_fd && fclose(logo_fd)) { - fprintf( - stderr, - "%s:%s:%d: fclose(\"%s\"): %s\n", - PROGNAME, - __FILE__, - __LINE__, - logo_path, - strerror(errno) - ); + logerrs("fclose(\"", logo_path, "\")", strerror(errno), + __LINE__); ret = -1; } if (index_fd && fclose(index_fd)) { - fprintf( - stderr, - "%s:%s:%d: fclose(\"%s\"): %s\n", - PROGNAME, - __FILE__, - __LINE__, - index_path, - strerror(errno) - ); + logerrs("fclose(\"", index_path, "\")", strerror(errno), + __LINE__); ret = -1; } free(style_path); @@ -1223,6 +1181,527 @@ cleanup: return ret; } +static int repo_write_refs( + const char *const outdir, + struct git_repository *const repo, + const char *const project_name, + const char *const description, + const char *const clone_url +) { + int ret = 0; + char *signature_text = NULL; + char *refs_path = NULL; + FILE *refs_fd = NULL; + struct git_branch_iterator *branch_iter = NULL; + struct git_reference_iterator *ref_iter = NULL; + int e; + + + if ( + !(signature_text = footer_signature_string()) + || !(refs_path = strjoin(outdir, "/refs.html")) + ) { + ret = -1; + goto cleanup; + } + + if (!(refs_fd = fopen(refs_path, "w"))) { + logerrs("fopen(\"", refs_path, "\")", strerror(errno), + __LINE__); + ret = -1; + goto cleanup; + } + + if ( + git_branch_iterator_new(&branch_iter, repo, GIT_BRANCH_LOCAL) + || git_reference_iterator_new(&ref_iter, repo) + ) { + const git_error *const error = git_error_last(); + assert(error); + const char *const fn = branch_iter == NULL + ? "git_branch_iterator_new()" + : "git_reference_iterator_new()"; + logerr(fn, error->message, __LINE__); + ret = -1; + goto cleanup; + } + + + e = fprintf( + refs_fd, + "" + "<!DOCTYPE html>\n" + "<html lang=\"%s\">\n" + " <head>\n" + " <meta charset=\"UTF-8\" />\n" + " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n" + " <link rel=\"icon\" type=\"image/svg+xml\" href=\"logo.svg\" />\n" + " <link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\" />\n" + " <link rel=\"alternate\" type=\"application/atom+xml\" href=\"commits.xml\" title=\"%s - %s\" hreflang=\"%s\" />\n" + " <link rel=\"alternate\" type=\"application/atom+xml\" href=\"tags.xml\" title=\"%s - %s\" hreflang=\"%s\" />\n" + " <title>%s</title>\n" + " </head>\n" + " <body>\n" + " <header>\n" + " <div class=\"header-horizontal-grouping\">\n" + " <img alt=\"%s\" class=\"logo\" src=\"logo.svg\" />\n" + " <div class=\"header-description\">\n" + " <h1>\n" + " %s\n" + " </h1>\n" + " <h2>\n" + " %s\n" + " </h2>\n" + " <code>\n" + " git clone %s\n" + " </code>\n" + " </div>\n" + " </div>\n" + " <nav>\n" + " <ul>\n" + " <li>\n" + " <a href=\"files.html\">\n" + " %s\n" + " </a>\n" + " </li>\n" + " <li>\n" + " <a href=\"log.html\">\n" + " %s\n" + " </a>\n" + " </li>\n" + " <li class=\"selected-nav-item\">\n" + " <a href=\"refs.html\">\n" + " %s\n" + " </a>\n" + " </li>\n" + " </ul>\n" + " </nav>\n" + " <hr />\n" + " </header>\n" + " <main>\n" + " <table>\n" + " <thead>\n" + " <tr>\n" + " <td>\n" + " %s\n" + " </td>\n" + " <td>\n" + " %s\n" + " </td>\n" + " <td>\n" + " %s\n" + " </td>\n" + " <td>\n" + " %s\n" + " </td>\n" + " </tr>\n" + " </thead>\n" + " <tbody>\n", + _(MSG_LANG), + project_name, + _(MSG_COMMIT_FEED), + _(MSG_LANG), + project_name, + _(MSG_TAGS_FEED), + _(MSG_LANG), + project_name, + _(MSG_LOGO_ALT), + project_name, + description, + clone_url, + _(MSG_NAV_FILES), + _(MSG_NAV_LOG), + _(MSG_NAV_REFS), + _(MSG_THEAD_BRANCH), + _(MSG_THEAD_COMMITMSG), + _(MSG_THEAD_AUTHOR), + _(MSG_THEAD_DATE) + ); + if (e < 0) { + logerr("fprintf()", strerror(errno), __LINE__); + ret = -1; + goto cleanup; + } + + struct git_reference *ref; + git_branch_t _btype; + while (!(e = git_branch_next(&ref, &_btype, branch_iter))) { + const char *const name = git_reference_shorthand(ref); + assert(name); + + const struct git_oid *const oid = git_reference_target(ref); + if (!oid) { + logerrs("git_reference_target(\"", name, "\")", + _(MSG_ERR_NONDIRECT_REF), __LINE__); + git_reference_free(ref); + ret = -1; + goto cleanup; + } + struct git_commit *commit; + if (git_commit_lookup(&commit, repo, oid)) { + const git_error *const error = git_error_last(); + assert(error); + logerr("git_commit_lookup()", error->message, __LINE__); + git_reference_free(ref); + ret = -1; + goto cleanup; + } + + const char *const sha = git_oid_tostr_s(oid); + assert(sha); + + const char *const summary = git_commit_summary(commit); + assert(summary); + const struct git_signature *const author = + git_commit_author(commit); + assert(author); + char *const date = formatted_date(author->when.time); + if (!date) { + git_commit_free(commit); + git_reference_free(ref); + ret = -1; + goto cleanup; + } + + e = fprintf( + refs_fd, + "" + " <tr>\n" + " <td>\n" + " <a href=\"log/%s.html\">\n" + " %s\n" + " <a>\n" + " </td>\n" + " <td>\n" + " <a href=\"commit/%s.html\">\n" + " %s\n" + " </a>\n" + " </td>\n" + " <td>\n" + " %s\n" + " </td>\n" + " <td>\n" + " %s\n" + " </td>\n" + " </tr>\n" + "", + name, + name, + sha, + summary, + author->name, + date + ); + if (e < 0) { + logerr("fprintf()", strerror(errno), __LINE__); + free(date); + git_reference_free(ref); + ret = -1; + goto cleanup; + } + free(date); + git_commit_free(commit); + git_reference_free(ref); + } + if (e != GIT_ITEROVER) { + ret = -1; + goto cleanup; + } + + e = fprintf( + refs_fd, + "" + " </tbody>\n" + " </table>\n" + " <table>\n" + " <thead>\n" + " <tr>\n" + " <td>\n" + " %s\n" + " </td>\n" + " <td>\n" + " %s\n" + " </td>\n" + " <td>\n" + " %s\n" + " </td>\n" + " <td>\n" + " %s\n" + " </td>\n" + " <td>\n" + " %s\n" + " </td>\n" + " </tr>\n" + " </thead>\n" + " <tbody>\n", + _(MSG_THEAD_TAG), + _(MSG_THEAD_COMMITMSG), + _(MSG_THEAD_DOWNLOAD), + _(MSG_THEAD_AUTHOR), + _(MSG_THEAD_DATE) + ); + if (e < 0) { + logerr("fprintf()", strerror(errno), __LINE__); + ret = -1; + goto cleanup; + } + while (!(e = git_reference_next(&ref, ref_iter))) { + if (!git_reference_is_tag(ref)) { + git_reference_free(ref); + continue; + } + const char *const name = git_reference_shorthand(ref); + assert(name); + + const struct git_oid *const oid = git_reference_target(ref); + if (!oid) { + logerrs("git_reference_target(\"", name, "\")", + _(MSG_ERR_NONDIRECT_REF), __LINE__); + git_reference_free(ref); + ret = -1; + goto cleanup; + } + struct git_commit *commit; + if (git_commit_lookup(&commit, repo, oid)) { + const git_error *const error = git_error_last(); + assert(error); + logerr("git_commit_lookup()", error->message, __LINE__); + git_reference_free(ref); + ret = -1; + goto cleanup; + } + + const char *const sha = git_oid_tostr_s(oid); + assert(sha); + + const char *const summary = git_commit_summary(commit); + assert(summary); + const struct git_signature *const author = + git_commit_author(commit); + assert(author); + char *const date = formatted_date(author->when.time); + if (!date) { + git_commit_free(commit); + git_reference_free(ref); + ret = -1; + goto cleanup; + } + + e = fprintf( + refs_fd, + "" + " <tr>\n" + " <td>\n" + " <a href=\"log/%s.html\">\n" + " %s\n" + " <a>\n" + " </td>\n" + " <td>\n" + " <a href=\"commit/%s.html\">\n" + " %s\n" + " </a>\n" + " </td>\n" + " <td>\n" + " <a href=\"snapshots/%s-%s.tar.xz\">%s-%s.tar.xz</a>\n" + " (<a href=\"snapshots/%s-%s.tar.xz.asc\">sig</a>)\n" + " </td>\n" + " <td>\n" + " %s\n" + " </td>\n" + " <td>\n" + " %s\n" + " </td>\n" + " </tr>\n" + "", + name, + name, + sha, + summary, + project_name, + name, + project_name, + name, + project_name, + name, + author->name, + date + ); + if (e < 0) { + logerr("fprintf()", strerror(errno), __LINE__); + free(date); + git_reference_free(ref); + ret = -1; + goto cleanup; + } + free(date); + git_commit_free(commit); + git_reference_free(ref); + } + if (e != GIT_ITEROVER) { + ret = -1; + goto cleanup; + } + + e = fprintf( + refs_fd, + "" + " </tbody>\n" + " </table>\n" + " </main>\n" + " <footer>\n" + " <hr />\n" + " <p>\n" + " %s\n" + " </p>\n" + " </footer>\n" + " </body>\n" + "</html>\n", + signature_text + ); + if (e < 0) { + logerr("fprintf()", strerror(errno), __LINE__); + ret = -1; + goto cleanup; + } + + +cleanup: + git_branch_iterator_free(branch_iter); + git_reference_iterator_free(ref_iter); + if (refs_fd && fclose(refs_fd)) { + logerrs("fclose(\"", refs_path, "\")", strerror(errno), + __LINE__); + ret = -1; + } + free(refs_path); + free(signature_text); + return ret; +} + +static int repo_write( + const char *const outdir, + char *const reporelpath, + const char *const clone_url +) { + int ret = 0; + int e; + + char *repopath = NULL; + char *description_path = NULL; + char *logo_path = NULL; + char *style_path = NULL; + char *name = NULL; + char *encoded_name = NULL; + char *encoded_description = NULL; + FILE *logo_fd = NULL; + FILE *style_fd = NULL; + struct git_repository *repo = NULL; + + if (!(repopath = realpath(reporelpath, NULL))) { + logerrs("realpath(\"", reporelpath, "\")", strerror(errno), + __LINE__); + ret = -1; + goto cleanup; + } + + if ( + !(description_path = strjoin(repopath, "/description")) + || !(logo_path = strjoin(outdir, "/logo.svg")) + || !(style_path = strjoin(outdir, "/style.css")) + || !(name = remove_suffix(basename(repopath), GIT_SUFFIX)) + ) { + ret = -1; + goto cleanup; + } + + if (!(logo_fd = fopen(logo_path, "w"))) { + logerrs("fopen(\"", logo_path, "\")", strerror(errno), + __LINE__); + ret = -1; + goto cleanup; + } + if (!(style_fd = fopen(style_path, "w"))) { + logerrs("fopen(\"", style_path, "\")", strerror(errno), + __LINE__); + ret = -1; + goto cleanup; + } + + if ( + write_logo(logo_fd) + || write_style(style_fd) + ) { + ret = -1; + goto cleanup; + } + + e = git_repository_open_ext( + &repo, + repopath, + GIT_REPOSITORY_OPEN_NO_SEARCH, + NULL + ); + if (e) { + const git_error *const error = git_error_last(); + assert(error); + logerr("git_repository_open_ext()", error->message, __LINE__); + ret = -1; + goto cleanup; + } + + char description[DESCRIPTION_MAXLENGTH] = ""; + FILE *f = fopen(description_path, "r"); + if (f) { + if (!fgets(description, sizeof(description), f)) { + logerrs("fget(\"", description_path, "\")", + strerror(errno), __LINE__); + } + if (fclose(f)) { + logerrs("fclose(\"", description_path, "\")", + strerror(errno), __LINE__); + ret = -1; + goto cleanup; + } + } + + if ( + !(encoded_name = escape_html(name)) + || !(encoded_description = escape_html(description)) + ) { + ret = -1; + goto cleanup; + } + + if (repo_write_refs(outdir, repo, encoded_name, encoded_description, + clone_url)) { + ret = -1; + goto cleanup; + } + +cleanup: + free(encoded_description); + free(encoded_name); + if (repo) { + git_repository_free(repo); + } + if (style_fd && fclose(style_fd)) { + logerrs("fclose(\"", style_path, "\")", strerror(errno), + __LINE__); + ret = -1; + } + if (logo_fd && fclose(logo_fd)) { + logerrs("fclose(\"", style_path, "\")", strerror(errno), + __LINE__); + ret = -1; + } + free(name); + free(style_path); + free(logo_path); + free(description_path); + free(repopath); + return ret; +} + #ifdef TEST static void unit_tests(){ dump_translatable_strings(); @@ -1244,21 +1723,25 @@ int main(int argc, char *argv[]) { return EXIT_SUCCESS; #endif + int ret = EXIT_SUCCESS; + bool cleanup_libgit = false; catalog_descriptor = catopen(CATALOG_NAME, NL_CAT_LOCALE); if (argc < 2) { if (print_usage(stderr)) { - return EXIT_ERROR; + ret = EXIT_ERROR; + goto cleanup; } - return EXIT_USAGE; + ret = EXIT_USAGE; + goto cleanup; } - int ret = 0; int flag; bool index = false; const char *idx_title = _(MSG_DEFAULT_TITLE); const char *outdir = "."; - while ((flag = getopt(argc, argv, "o:t:ivhV")) != -1) { + const char *clone_url = NULL; + while ((flag = getopt(argc, argv, "o:t:u:ivhV")) != -1) { switch (flag) { case 'i': index = true; @@ -1272,53 +1755,91 @@ int main(int argc, char *argv[]) { case 'v': verbose = true; break; + case 'u': + clone_url = optarg; + break; case 'h': if (print_help(stdout)) { - return EXIT_ERROR; + ret = EXIT_ERROR; } - return EXIT_SUCCESS; + goto cleanup; case 'V': if (print_version(stdout)) { - return EXIT_ERROR; + ret = EXIT_ERROR; } - return EXIT_SUCCESS; + goto cleanup; default: if (print_usage(stderr)) { - return EXIT_ERROR; + ret = EXIT_ERROR; } - return EXIT_USAGE; + goto cleanup; } } - git_libgit2_init(); + if (!index && !clone_url) { + if (fprintf(stderr, "%s\n", _(MSG_MISSING_URL)) < 0 + || print_usage(stderr)) { + ret = EXIT_ERROR; + goto cleanup; + } + ret = EXIT_USAGE; + goto cleanup; + } + if (index && clone_url) { + if (fprintf(stderr, "%s\n", _(MSG_INCOMPATIBLE_OPTIONS)) < 0 + || print_usage(stderr)) { + ret = EXIT_ERROR; + goto cleanup; + } + ret = EXIT_USAGE; + goto cleanup; + } + if (optind == argc) { + if (fprintf(stderr, "%s\n", _(MSG_MISSING_ARGS)) < 0 + || print_usage(stderr)) { + ret = EXIT_ERROR; + goto cleanup; + } + ret = EXIT_USAGE; + goto cleanup; + } + errno = 0; if ( mkdir(outdir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) && errno != EEXIST ) { - fprintf( - stderr, - "%s:%s:%d: mkdir(\"%s\"): %s\n", - PROGNAME, - __FILE__, - __LINE__, - outdir, - strerror(errno) - ); - ret = 1; + logerrs("mkdir(\"", outdir, "\")", strerror(errno), __LINE__); + ret = EXIT_ERROR; goto cleanup; } + git_libgit2_init(); + cleanup_libgit = true; + if (index) { - if (write_index(outdir, idx_title, + if (index_write(outdir, idx_title, argc - optind, argv + optind)) { - ret = 1; + ret = EXIT_ERROR; + goto cleanup; + } + } else { + if (repo_write(outdir, argv[optind], clone_url)) { + ret = EXIT_ERROR; goto cleanup; } } cleanup: - git_libgit2_shutdown(); + if (cleanup_libgit) { + git_libgit2_shutdown(); + } + if (catalog_descriptor && catalog_descriptor != (nl_catd)-1) { + if (catclose(catalog_descriptor)) { + logerr("catclose()", strerror(errno), __LINE__); + ret = EXIT_ERROR; + } + } return ret; } diff --git a/src/templates/refs.html b/src/templates/refs.html index 2f992f7..7d1e1c1 100644 --- a/src/templates/refs.html +++ b/src/templates/refs.html @@ -4,7 +4,7 @@ <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> - <link rel="icon" type="image/svg+xml" href=../static/logo.svg" /> + <link rel="icon" type="image/svg+xml" href="../static/logo.svg" /> <link rel="stylesheet" type="text/css" href="../static/styles.css" /> <link rel="alternate" type="application/atom+xml" href="$PREFIX/commits.xml" title="$PROJECT_NAME - commit feed" hreflang="en" /> <link rel="alternate" type="application/atom+xml" href="$PREFIX/tags.xml" title="$PROJECT_NAME - tags feed" hreflang="en" /> |