From d71c0c725d7b5ddfc5b788d328a5fc7a27739662 Mon Sep 17 00:00:00 2001 From: Benjamin Close Date: Tue, 25 Nov 2008 06:25:35 -0800 Subject: [PATCH 1/5] Add support for sorting by Age in the repolist Signed-off-by: Lars Hjemli --- cgit.c | 2 ++ cgit.h | 1 + ui-repolist.c | 39 ++++++++++++++++++++++++++++++++++++--- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/cgit.c b/cgit.c index c82587b..e09c86e 100644 --- a/cgit.c +++ b/cgit.c @@ -154,6 +154,8 @@ static void querystring_cb(const char *name, const char *value) ctx.qry.name = xstrdup(value); } else if (!strcmp(name, "mimetype")) { ctx.qry.mimetype = xstrdup(value); + } else if (!strcmp(name, "s")){ + ctx.qry.sort = xstrdup(value); } } diff --git a/cgit.h b/cgit.h index 91db98a..ea90833 100644 --- a/cgit.h +++ b/cgit.h @@ -121,6 +121,7 @@ struct cgit_query { char *url; int ofs; int nohead; + char *sort; }; struct cgit_config { diff --git a/ui-repolist.c b/ui-repolist.c index c23232c..312a7ee 100644 --- a/ui-repolist.c +++ b/ui-repolist.c @@ -75,7 +75,7 @@ void print_header(int columns) "Name" "Description" "Owner" - "Idle"); + "Idle"); if (ctx.cfg.enable_index_links) html("Links"); html("\n"); @@ -92,6 +92,35 @@ void print_pager(int items, int pagelen, char *search) html(""); } +static int cgit_reposort_modtime(const void *a, const void *b) +{ + const struct cgit_repo *r1 = a; + const struct cgit_repo *r2 = b; + char *path; + struct stat s; + time_t t1, t2; + path = fmt("%s/%s", r1->path, ctx.cfg.agefile); + if (stat(path, &s) == 0) { + t1 = read_agefile(path); + } else { + path = fmt("%s/refs/heads/%s", r1->path, r1->defbranch); + if (stat(path, &s) != 0) + return 0; + t1 =s.st_mtime; + } + + path = fmt("%s/%s", r2->path, ctx.cfg.agefile); + if (stat(path, &s) == 0) { + t2 = read_agefile(path); + } else { + path = fmt("%s/refs/heads/%s", r2->path, r2->defbranch); + if (stat(path, &s) != 0) + return 0; + t2 =s.st_mtime; + } + return t2-t1; +} + void cgit_print_repolist() { int i, columns = 4, hits = 0, header = 0; @@ -108,6 +137,9 @@ void cgit_print_repolist() if (ctx.cfg.index_header) html_include(ctx.cfg.index_header); + if(ctx.qry.sort) + qsort(cgit_repolist.repos,cgit_repolist.count,sizeof(struct cgit_repo),cgit_reposort_modtime); + html(""); for (i=0; igroup != NULL) || + if (!ctx.qry.sort && + ((last_group == NULL && ctx.repo->group != NULL) || (last_group != NULL && ctx.repo->group == NULL) || (last_group != NULL && ctx.repo->group != NULL && - strcmp(ctx.repo->group, last_group))) { + strcmp(ctx.repo->group, last_group)))) { htmlf("", title); +} + void print_header(int columns) { - html("" - "" - "" - "" - ""); + html(""); + print_sort_header("Name", "name"); + print_sort_header("Description", "desc"); + print_sort_header("Owner", "owner"); + print_sort_header("Idle", "idle"); if (ctx.cfg.enable_index_links) html(""); html("\n"); @@ -101,7 +111,42 @@ void print_pager(int items, int pagelen, char *search) html(""); } -static int cgit_reposort_modtime(const void *a, const void *b) +static int cmp(const char *s1, const char *s2) +{ + if (s1 && s2) + return strcmp(s1, s2); + if (s1 && !s2) + return 1; + if (s2 && !s1) + return -1; + return 0; +} + +static int sort_name(const void *a, const void *b) +{ + const struct cgit_repo *r1 = a; + const struct cgit_repo *r2 = b; + + return cmp(r1->name, r2->name); +} + +static int sort_desc(const void *a, const void *b) +{ + const struct cgit_repo *r1 = a; + const struct cgit_repo *r2 = b; + + return cmp(r1->desc, r2->desc); +} + +static int sort_owner(const void *a, const void *b) +{ + const struct cgit_repo *r1 = a; + const struct cgit_repo *r2 = b; + + return cmp(r1->owner, r2->owner); +} + +static int sort_idle(const void *a, const void *b) { const struct cgit_repo *r1 = a; const struct cgit_repo *r2 = b; @@ -113,10 +158,39 @@ static int cgit_reposort_modtime(const void *a, const void *b) return t2 - t1; } +struct sortcolumn { + const char *name; + int (*fn)(const void *a, const void *b); +}; + +struct sortcolumn sortcolumn[] = { + {"name", sort_name}, + {"desc", sort_desc}, + {"owner", sort_owner}, + {"idle", sort_idle}, + {NULL, NULL} +}; + +int sort_repolist(char *field) +{ + struct sortcolumn *column; + + for (column = &sortcolumn[0]; column->name; column++) { + if (strcmp(field, column->name)) + continue; + qsort(cgit_repolist.repos, cgit_repolist.count, + sizeof(struct cgit_repo), column->fn); + return 1; + } + return 0; +} + + void cgit_print_repolist() { int i, columns = 4, hits = 0, header = 0; char *last_group = NULL; + int sorted = 0; if (ctx.cfg.enable_index_links) columns++; @@ -130,7 +204,7 @@ void cgit_print_repolist() html_include(ctx.cfg.index_header); if(ctx.qry.sort) - qsort(cgit_repolist.repos,cgit_repolist.count,sizeof(struct cgit_repo),cgit_reposort_modtime); + sorted = sort_repolist(ctx.qry.sort); html("
", columns); html_txt(ctx.repo->group); From cbac02c8b056e47b3e0092949c480c7ec64a3e0f Mon Sep 17 00:00:00 2001 From: Lars Hjemli Date: Sat, 29 Nov 2008 13:33:02 +0100 Subject: [PATCH 2/5] ui-repolist: extract get_repo_modtime() from print_modtime() The new function is then used by both print_modtime() and cgit_reposort_modtime(). Signed-off-by: Lars Hjemli --- ui-repolist.c | 46 +++++++++++++++++++--------------------------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/ui-repolist.c b/ui-repolist.c index 312a7ee..436a774 100644 --- a/ui-repolist.c +++ b/ui-repolist.c @@ -28,21 +28,30 @@ time_t read_agefile(char *path) return 0; } -static void print_modtime(struct cgit_repo *repo) +static int get_repo_modtime(const struct cgit_repo *repo, time_t *mtime) { char *path; struct stat s; path = fmt("%s/%s", repo->path, ctx.cfg.agefile); if (stat(path, &s) == 0) { - cgit_print_age(read_agefile(path), -1, NULL); - return; + *mtime = read_agefile(path); + return 1; } path = fmt("%s/refs/heads/%s", repo->path, repo->defbranch); - if (stat(path, &s) != 0) - return; - cgit_print_age(s.st_mtime, -1, NULL); + if (stat(path, &s) == 0) { + *mtime = s.st_mtime; + return 1; + } + return 0; +} + +static void print_modtime(struct cgit_repo *repo) +{ + time_t t; + if (get_repo_modtime(repo, &t)) + cgit_print_age(t, -1, NULL); } int is_match(struct cgit_repo *repo) @@ -96,29 +105,12 @@ static int cgit_reposort_modtime(const void *a, const void *b) { const struct cgit_repo *r1 = a; const struct cgit_repo *r2 = b; - char *path; - struct stat s; time_t t1, t2; - path = fmt("%s/%s", r1->path, ctx.cfg.agefile); - if (stat(path, &s) == 0) { - t1 = read_agefile(path); - } else { - path = fmt("%s/refs/heads/%s", r1->path, r1->defbranch); - if (stat(path, &s) != 0) - return 0; - t1 =s.st_mtime; - } - path = fmt("%s/%s", r2->path, ctx.cfg.agefile); - if (stat(path, &s) == 0) { - t2 = read_agefile(path); - } else { - path = fmt("%s/refs/heads/%s", r2->path, r2->defbranch); - if (stat(path, &s) != 0) - return 0; - t2 =s.st_mtime; - } - return t2-t1; + t1 = t2 = 0; + get_repo_modtime(r1, &t1); + get_repo_modtime(r2, &t2); + return t2 - t1; } void cgit_print_repolist() From f250c1ca2ea7f35d65f639e42e8b8f0657515e5d Mon Sep 17 00:00:00 2001 From: Lars Hjemli Date: Sat, 29 Nov 2008 14:08:51 +0100 Subject: [PATCH 3/5] ui-repolist: add support for sorting any column Signed-off-by: Lars Hjemli --- ui-repolist.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 83 insertions(+), 9 deletions(-) diff --git a/ui-repolist.c b/ui-repolist.c index 436a774..0de328b 100644 --- a/ui-repolist.c +++ b/ui-repolist.c @@ -78,13 +78,23 @@ int is_in_url(struct cgit_repo *repo) return 0; } +void print_sort_header(const char *title, const char *sort) +{ + htmlf("%s
NameDescriptionOwnerIdle
Links
"); for (i=0; igroup != NULL) || (last_group != NULL && ctx.repo->group == NULL) || (last_group != NULL && ctx.repo->group != NULL && @@ -156,7 +230,7 @@ void cgit_print_repolist() last_group = ctx.repo->group; } htmlf("
", - ctx.repo->group ? "sublevel-repo" : "toplevel-repo"); + !sorted && ctx.repo->group ? "sublevel-repo" : "toplevel-repo"); cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL); html(""); html_link_open(cgit_repourl(ctx.repo->url), NULL, NULL); From 54272e60965ec6a98b49cbf67d72a4b1f5adc55b Mon Sep 17 00:00:00 2001 From: Lars Hjemli Date: Sat, 29 Nov 2008 14:27:35 +0100 Subject: [PATCH 4/5] ui-repolist: sort null values last When sorting on e.g. owner, it's not interesting to get all repos without owner at the top of the list. Signed-off-by: Lars Hjemli --- ui-repolist.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-repolist.c b/ui-repolist.c index 0de328b..cf27cb3 100644 --- a/ui-repolist.c +++ b/ui-repolist.c @@ -116,9 +116,9 @@ static int cmp(const char *s1, const char *s2) if (s1 && s2) return strcmp(s1, s2); if (s1 && !s2) - return 1; - if (s2 && !s1) return -1; + if (s2 && !s1) + return 1; return 0; } From 8813170390f3c3a0f4743afbc92ede42953fa3b0 Mon Sep 17 00:00:00 2001 From: Lars Hjemli Date: Sat, 29 Nov 2008 16:46:37 +0100 Subject: [PATCH 5/5] ui-repolist: implement lazy caching of repo->mtime When sorting the list of repositories by their last modification time, cgit would (in the worst case) invoke fstat(3) four times and open(3) twice for each callback from qsort(3). This obviously scales very badly. Now, the calculated modtime for each repo is saved in repo->mtime, thus keeping the number of stat/open invocations identical for sorted and unsorted repo-listings. Signed-off-by: Lars Hjemli --- cgit.h | 1 + shared.c | 1 + ui-repolist.c | 16 ++++++++++++---- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/cgit.h b/cgit.h index ea90833..c99d337 100644 --- a/cgit.h +++ b/cgit.h @@ -61,6 +61,7 @@ struct cgit_repo { int snapshots; int enable_log_filecount; int enable_log_linecount; + time_t mtime; }; struct cgit_repolist { diff --git a/shared.c b/shared.c index f5875e4..89d1bab 100644 --- a/shared.c +++ b/shared.c @@ -60,6 +60,7 @@ struct cgit_repo *cgit_add_repo(const char *url) ret->enable_log_linecount = ctx.cfg.enable_log_linecount; ret->module_link = ctx.cfg.module_link; ret->readme = NULL; + ret->mtime = -1; return ret; } diff --git a/ui-repolist.c b/ui-repolist.c index cf27cb3..aa743bf 100644 --- a/ui-repolist.c +++ b/ui-repolist.c @@ -32,19 +32,27 @@ static int get_repo_modtime(const struct cgit_repo *repo, time_t *mtime) { char *path; struct stat s; + struct cgit_repo *r = (struct cgit_repo *)repo; + if (repo->mtime != -1) { + *mtime = repo->mtime; + return 1; + } path = fmt("%s/%s", repo->path, ctx.cfg.agefile); if (stat(path, &s) == 0) { *mtime = read_agefile(path); + r->mtime = *mtime; return 1; } path = fmt("%s/refs/heads/%s", repo->path, repo->defbranch); - if (stat(path, &s) == 0) { + if (stat(path, &s) == 0) *mtime = s.st_mtime; - return 1; - } - return 0; + else + *mtime = 0; + + r->mtime = *mtime; + return (r->mtime != 0); } static void print_modtime(struct cgit_repo *repo)