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);
+ } else if (!strcmp(name, "showmsg")) {
+ ctx.qry.showmsg = atoi(value);
}
}
int snapshots;
int enable_log_filecount;
int enable_log_linecount;
+ time_t mtime;
};
struct cgit_repolist {
char *url;
int ofs;
int nohead;
+ char *sort;
+ int showmsg;
};
struct cgit_config {
extern int cgit_parse_snapshots_mask(const char *str);
-/* libgit.a either links against or compiles its own implementation of
- * strcasestr(), and we'd like to reuse it. Simply re-declaring it
- * seems to do the trick.
- */
-extern char *strcasestr(const char *haystack, const char *needle);
-
#endif /* CGIT_H */
{
struct commitinfo *info;
char *tmp;
+ int cols = 2;
info = cgit_parse_commit(commit);
- html("<tr><td>");
+ htmlf("<tr%s><td>",
+ ctx.qry.showmsg ? " class='logheader'" : "");
tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
tmp = cgit_pageurl(ctx.repo->url, "commit", tmp);
html_link_open(tmp, NULL, NULL);
cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
html_link_close();
- html("</td><td>");
+ htmlf("</td><td%s>",
+ ctx.qry.showmsg ? " class='logsubject'" : "");
cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
sha1_to_hex(commit->object.sha1));
html("</td><td>");
}
}
html("</td></tr>\n");
+ if (ctx.qry.showmsg) {
+ if (ctx.repo->enable_log_filecount) {
+ cols++;
+ if (ctx.repo->enable_log_linecount)
+ cols++;
+ }
+ htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>",
+ cols);
+ html_txt(info->msg);
+ html("</td></tr>\n");
+ }
cgit_free_commitinfo(info);
}
+static const char *disambiguate_ref(const char *ref)
+{
+ unsigned char sha1[20];
+ const char *longref;
+
+ longref = fmt("refs/heads/%s", ref);
+ if (get_sha1(longref, sha1) == 0)
+ return longref;
+
+ return ref;
+}
void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern,
char *path, int pager)
{
struct rev_info rev;
struct commit *commit;
- const char *argv[] = {NULL, tip, NULL, NULL, NULL};
+ const char *argv[] = {NULL, NULL, NULL, NULL, NULL};
int argc = 2;
int i, columns = 3;
if (!tip)
- argv[1] = ctx.qry.head;
+ tip = ctx.qry.head;
+
+ argv[1] = disambiguate_ref(tip);
if (grep && pattern && (!strcmp(grep, "grep") ||
!strcmp(grep, "author") ||
html("<table class='list nowrap'>");
html("<tr class='nohover'><th class='left'>Age</th>"
- "<th class='left'>Commit message</th>"
- "<th class='left'>Author</th>");
+ "<th class='left'>Commit message");
+ if (pager) {
+ html(" (");
+ cgit_log_link("toggle", NULL, NULL, ctx.qry.head, ctx.qry.sha1,
+ ctx.qry.path, ctx.qry.ofs, ctx.qry.grep,
+ ctx.qry.search, ctx.qry.showmsg ? 0 : 1);
+ html(")");
+ }
+ html("</th><th class='left'>Author</th>");
if (ctx.repo->enable_log_filecount) {
html("<th class='left'>Files</th>");
columns++;
cgit_log_link("[prev]", NULL, NULL, ctx.qry.head,
ctx.qry.sha1, ctx.qry.path,
ofs - cnt, ctx.qry.grep,
- ctx.qry.search);
+ ctx.qry.search, ctx.qry.showmsg);
html(" ");
}
if ((commit = get_revision(&rev)) != NULL) {
cgit_log_link("[next]", NULL, NULL, ctx.qry.head,
ctx.qry.sha1, ctx.qry.path,
ofs + cnt, ctx.qry.grep,
- ctx.qry.search);
+ ctx.qry.search, ctx.qry.showmsg);
}
html("</div>");
} else if ((commit = get_revision(&rev)) != NULL) {
html("<tr class='nohover'><td colspan='3'>");
cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, NULL, 0,
- NULL, NULL);
+ NULL, NULL, ctx.qry.showmsg);
html("</td></tr>\n");
}
}
if (!info)
return 1;
html("<tr><td>");
- cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL);
+ cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL,
+ ctx.qry.showmsg);
html("</td><td>");
if (ref->object->type == OBJ_COMMIT) {
static void print_tag_header()
{
html("<tr class='nohover'><th class='left'>Tag</th>"
- "<th class='left'>Reference</th>"
+ "<th class='left'>Download</th>"
"<th class='left'>Author</th>"
"<th class='left' colspan='2'>Age</th></tr>\n");
header = 1;
}
+static void print_tag_downloads(const struct cgit_repo *repo, const char *ref)
+{
+ const struct cgit_snapshot_format* f;
+ char *filename;
+ const char *basename;
+
+ if (!ref || strlen(ref) < 2)
+ return;
+
+ basename = cgit_repobasename(repo->url);
+ if (prefixcmp(ref, basename) != 0) {
+ if ((ref[0] == 'v' || ref[0] == 'V') && isdigit(ref[1]))
+ ref++;
+ if (isdigit(ref[0]))
+ ref = xstrdup(fmt("%s-%s", basename, ref));
+ }
+
+ for (f = cgit_snapshot_formats; f->suffix; f++) {
+ if (!(repo->snapshots & f->bit))
+ continue;
+ filename = fmt("%s%s", ref, f->suffix);
+ cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
+ html(" ");
+ }
+}
static int print_tag(struct refinfo *ref)
{
struct tag *tag;
html("<tr><td>");
cgit_tag_link(name, NULL, NULL, ctx.qry.head, name);
html("</td><td>");
- cgit_object_link(tag->tagged);
+ if (ctx.repo->snapshots && (tag->tagged->type == OBJ_COMMIT))
+ print_tag_downloads(ctx.repo, name);
+ else
+ cgit_object_link(tag->tagged);
html("</td><td>");
if (info->tagger)
html(info->tagger);
html("<tr><td>");
html_txt(name);
html("</td><td>");
- cgit_object_link(ref->object);
+ if (ctx.repo->snapshots && (tag->tagged->type == OBJ_COMMIT))
+ print_tag_downloads(ctx.repo, name);
+ else
+ cgit_object_link(ref->object);
html("</td></tr>\n");
}
return 0;
* (see COPYING for full license text)
*/
+/* This is needed for strcasestr to be defined by <string.h> */
+#define _GNU_SOURCE 1
+#include <string.h>
+
#include <time.h>
#include "cgit.h"
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;
+ 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) {
- cgit_print_age(read_agefile(path), -1, NULL);
- return;
+ *mtime = read_agefile(path);
+ r->mtime = *mtime;
+ 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;
+ else
+ *mtime = 0;
+
+ r->mtime = *mtime;
+ return (r->mtime != 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)
return 0;
}
+void print_sort_header(const char *title, const char *sort)
+{
+ htmlf("<th class='left'><a href='./?s=%s", sort);
+ if (ctx.qry.search) {
+ html("&q=");
+ html_url_arg(ctx.qry.search);
+ }
+ htmlf("'>%s</a></th>", title);
+}
+
void print_header(int columns)
{
- html("<tr class='nohover'>"
- "<th class='left'>Name</th>"
- "<th class='left'>Description</th>"
- "<th class='left'>Owner</th>"
- "<th class='left'>Idle</th>");
+ html("<tr class='nohover'>");
+ 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("<th class='left'>Links</th>");
html("</tr>\n");
html("</div>");
}
+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;
+ time_t t1, t2;
+
+ t1 = t2 = 0;
+ get_repo_modtime(r1, &t1);
+ get_repo_modtime(r2, &t2);
+ 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++;
if (ctx.cfg.index_header)
html_include(ctx.cfg.index_header);
+ if(ctx.qry.sort)
+ sorted = sort_repolist(ctx.qry.sort);
+
html("<table summary='repository list' class='list nowrap'>");
for (i=0; i<cgit_repolist.count; i++) {
ctx.repo = &cgit_repolist.repos[i];
continue;
if (!header++)
print_header(columns);
- if ((last_group == NULL && ctx.repo->group != NULL) ||
+ if (!sorted &&
+ ((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("<tr class='nohover'><td colspan='%d' class='repogroup'>",
columns);
html_txt(ctx.repo->group);
last_group = ctx.repo->group;
}
htmlf("<tr><td class='%s'>",
- 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("</td><td>");
html_link_open(cgit_repourl(ctx.repo->url), NULL, NULL);
html("<td>");
cgit_summary_link("summary", NULL, "button", NULL);
cgit_log_link("log", NULL, "button", NULL, NULL, NULL,
- 0, NULL, NULL);
+ 0, NULL, NULL, ctx.qry.showmsg);
cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL);
html("</td>");
}
}
void cgit_log_link(char *name, char *title, char *class, char *head,
- char *rev, char *path, int ofs, char *grep, char *pattern)
+ char *rev, char *path, int ofs, char *grep, char *pattern,
+ int showmsg)
{
char *delim;
html(delim);
html("ofs=");
htmlf("%d", ofs);
+ delim = "&";
+ }
+ if (showmsg) {
+ html(delim);
+ html("showmsg=1");
}
html("'>");
html_txt(name);
void cgit_object_link(struct object *obj)
{
- char *page, *rev, *name;
+ char *page, *shortrev, *fullrev, *name;
+ fullrev = sha1_to_hex(obj->sha1);
+ shortrev = xstrdup(fullrev);
+ shortrev[10] = '\0';
if (obj->type == OBJ_COMMIT) {
- cgit_commit_link(fmt("commit %s", sha1_to_hex(obj->sha1)), NULL, NULL,
- ctx.qry.head, sha1_to_hex(obj->sha1));
+ cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL,
+ ctx.qry.head, fullrev);
return;
} else if (obj->type == OBJ_TREE)
page = "tree";
page = "tag";
else
page = "blob";
- rev = sha1_to_hex(obj->sha1);
- name = fmt("%s %s", typename(obj->type), rev);
- reporevlink(page, name, NULL, NULL, ctx.qry.head, rev, NULL);
+ name = fmt("%s %s...", typename(obj->type), shortrev);
+ reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL);
}
void cgit_print_date(time_t secs, char *format, int local_time)
html_hidden("id", ctx.qry.sha1);
if (ctx.qry.sha2)
html_hidden("id2", ctx.qry.sha2);
+ if (ctx.qry.showmsg)
+ html_hidden("showmsg", "1");
if (incl_search) {
if (ctx.qry.grep)
cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head,
ctx->qry.sha1, NULL);
cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head,
- NULL, NULL, 0, NULL, NULL);
+ NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg);
cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head,
ctx->qry.sha1, NULL);
cgit_commit_link("commit", NULL, hc(cmd, "commit"),
continue;
filename = fmt("%s-%s%s", cgit_repobasename(repo), hex,
f->suffix);
- cgit_snapshot_link(filename, NULL, NULL, (char *)head,
- (char *)hex, filename);
+ cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
html("<br/>");
}
}