stagit

Personal Stagit Fork
commit 003fd6f581ae90d236b76931c1e366ee2ff224ae
parent 742c5e64e2e55700884bc58a30a31615c63e5acf
Author: maydayv7 <maydayv7@gmail.com>
Date:   Sat,  3 Jan 2026 11:45:31 +0530

feat: Sort repositories by last commit time on `index` page

Diffstat:
MREADME.md | 3++-
Mstagit-index.c | 172+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
2 files changed, 109 insertions(+), 66 deletions(-)

diff --git a/README.md b/README.md @@ -6,13 +6,14 @@ It generates static HTML pages for a git repository. ## Changes -This is my personal fork of `stagit` +This is my personal [fork](https://git.codemadness.org/stagit/) of `stagit`. - Modified styling - Added `-n` flag to set `sitename` for header button - Pages are now styled by the `style.css` present at root - Added [md4c](https://github.com/mity/md4c) to parse Markdown files - Added [Chroma](https://github.com/alecthomas/chroma) for code syntax highlighting +- Sort repositories by last commit time on `index` page ## Usage diff --git a/stagit-index.c b/stagit-index.c @@ -8,14 +8,16 @@ #include <git2.h> -static git_repository *repo; static char *sitename = "Git"; - static const char *relpath = ""; -static char description[255] = "Repositories"; -static char *name = ""; -static char owner[255]; +struct repoinfo { + char *name; + char *strippedname; + char *description; + char *owner; + git_time time; +}; /* Handle read or write errors for a FILE * stream */ void checkfileerror(FILE *fp, const char *name, int mode) { @@ -103,7 +105,7 @@ void writeheader(FILE *fp) { "initial-scale=1\" />\n" "<title>", fp); - xmlencode(fp, description, strlen(description)); + xmlencode(fp, sitename, strlen(sitename)); fprintf(fp, "</title>\n<link rel=\"icon\" type=\"image/png\" " "href=\"%sfavicon.png\" />\n", @@ -133,50 +135,58 @@ void writefooter(FILE *fp) { fputs("</tbody>\n</table>\n</div>\n</div>\n</body>\n</html>\n", fp); } -int writelog(FILE *fp) { - git_commit *commit = NULL; - const git_signature *author; +int getlastcommit(git_repository *repo, git_time *t) { git_revwalk *w = NULL; git_oid id; - char *stripped_name = NULL, *p; - int ret = 0; - - git_revwalk_new(&w, repo); - git_revwalk_push_head(w); + git_commit *c = NULL; + const git_signature *author; + int ret = -1; - if (git_revwalk_next(&id, w) || git_commit_lookup(&commit, repo, &id)) { - ret = -1; + if (git_revwalk_new(&w, repo)) + return -1; + if (git_revwalk_push_head(w)) goto err; - } + if (git_revwalk_next(&id, w)) + goto err; + if (git_commit_lookup(&c, repo, &id)) + goto err; + + author = git_commit_author(c); + *t = author->when; + ret = 0; - author = git_commit_author(commit); +err: + git_commit_free(c); + git_revwalk_free(w); + return ret; +} - /* strip .git suffix */ - if (!(stripped_name = strdup(name))) - err(1, "strdup"); - if ((p = strrchr(stripped_name, '.'))) - if (!strcmp(p, ".git")) - *p = '\0'; +int repocompare(const void *a, const void *b) { + const struct repoinfo *ra = a; + const struct repoinfo *rb = b; + + /* sort by time descending (newest first) */ + if (ra->time.time > rb->time.time) + return -1; + if (ra->time.time < rb->time.time) + return 1; + /* fallback to name */ + return strcmp(ra->name, rb->name); +} +void printrepo(FILE *fp, struct repoinfo *ri) { fputs("<tr><td><a href=\"", fp); - percentencode(fp, stripped_name, strlen(stripped_name)); + percentencode(fp, ri->strippedname, strlen(ri->strippedname)); fputs("/log.html\">", fp); - xmlencode(fp, stripped_name, strlen(stripped_name)); + xmlencode(fp, ri->strippedname, strlen(ri->strippedname)); fputs("</a></td><td>", fp); - xmlencode(fp, description, strlen(description)); + xmlencode(fp, ri->description, strlen(ri->description)); fputs("</td><td>", fp); - xmlencode(fp, owner, strlen(owner)); + xmlencode(fp, ri->owner, strlen(ri->owner)); fputs("</td><td>", fp); - if (author) - printtimeshort(fp, &(author->when)); - fputs("</td></tr>", fp); - - git_commit_free(commit); -err: - git_revwalk_free(w); - free(stripped_name); - - return ret; + if (ri->time.time) + printtimeshort(fp, &(ri->time)); + fputs("</td></tr>\n", fp); } void usage(char *argv0) { @@ -185,9 +195,13 @@ void usage(char *argv0) { } int main(int argc, char *argv[]) { - FILE *fp; + git_repository *repo = NULL; + struct repoinfo *repos = NULL; + size_t nrepos = 0; char path[PATH_MAX], repodirabs[PATH_MAX + 1]; - const char *repodir; + char description[255], owner[255]; + char *name, *p; + FILE *fp; int i, ret = 0; for (i = 1; i < argc; i++) { @@ -218,8 +232,6 @@ int main(int argc, char *argv[]) { err(1, "pledge"); #endif - writeheader(stdout); - for (i = 1; i < argc; i++) { if (argv[i][0] == '-') { if (argv[i][1] == 'n') @@ -227,60 +239,90 @@ int main(int argc, char *argv[]) { continue; } - repodir = argv[i]; - if (!realpath(repodir, repodirabs)) - err(1, "realpath"); + if (!realpath(argv[i], repodirabs)) { + fprintf(stderr, "realpath error for %s\n", argv[i]); + continue; + } - if (git_repository_open_ext(&repo, repodir, GIT_REPOSITORY_OPEN_NO_SEARCH, + if (git_repository_open_ext(&repo, argv[i], GIT_REPOSITORY_OPEN_NO_SEARCH, NULL)) { - fprintf(stderr, "%s: cannot open repository\n", argv[0]); + fprintf(stderr, "%s: cannot open repository\n", argv[i]); ret = 1; continue; } - /* use directory name as name */ + repos = realloc(repos, (nrepos + 1) * sizeof(*repos)); + if (!repos) + err(1, "realloc"); + + /* set defaults */ + memset(&repos[nrepos], 0, sizeof(struct repoinfo)); + repos[nrepos].name = strdup(argv[i]); + description[0] = '\0'; + owner[0] = '\0'; + + /* calculate stripped name */ if ((name = strrchr(repodirabs, '/'))) name++; else name = ""; - - /* read description or .git/description */ - joinpath(path, sizeof(path), repodir, "description"); + if (!(repos[nrepos].strippedname = strdup(name))) + err(1, "strdup"); + if ((p = strrchr(repos[nrepos].strippedname, '.'))) + if (!strcmp(p, ".git")) + *p = '\0'; + + /* description */ + joinpath(path, sizeof(path), argv[i], "description"); if (!(fp = fopen(path, "r"))) { - joinpath(path, sizeof(path), repodir, ".git/description"); + joinpath(path, sizeof(path), argv[i], ".git/description"); fp = fopen(path, "r"); } - description[0] = '\0'; if (fp) { if (!fgets(description, sizeof(description), fp)) description[0] = '\0'; - checkfileerror(fp, "description", 'r'); fclose(fp); } + repos[nrepos].description = strdup(description); - /* read owner or .git/owner */ - joinpath(path, sizeof(path), repodir, "owner"); + /* owner */ + joinpath(path, sizeof(path), argv[i], "owner"); if (!(fp = fopen(path, "r"))) { - joinpath(path, sizeof(path), repodir, ".git/owner"); + joinpath(path, sizeof(path), argv[i], ".git/owner"); fp = fopen(path, "r"); } - owner[0] = '\0'; if (fp) { if (!fgets(owner, sizeof(owner), fp)) owner[0] = '\0'; - checkfileerror(fp, "owner", 'r'); - fclose(fp); owner[strcspn(owner, "\n")] = '\0'; + fclose(fp); } - writelog(stdout); + repos[nrepos].owner = strdup(owner); + + /* last commit */ + getlastcommit(repo, &repos[nrepos].time); + + git_repository_free(repo); + repo = NULL; + nrepos++; + } + + /* sort by last commit time */ + qsort(repos, nrepos, sizeof(struct repoinfo), repocompare); + + /* output */ + writeheader(stdout); + for (i = 0; i < nrepos; i++) { + printrepo(stdout, &repos[i]); + free(repos[i].name); + free(repos[i].strippedname); + free(repos[i].description); + free(repos[i].owner); } writefooter(stdout); + free(repos); - /* cleanup */ - git_repository_free(repo); git_libgit2_shutdown(); - checkfileerror(stdout, "<stdout>", 'w'); - return ret; }