aboutsummaryrefslogtreecommitdiff
path: root/src/gistatic.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/gistatic.c')
-rw-r--r--src/gistatic.c1023
1 files changed, 772 insertions, 251 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;
}