author | Lars Hjemli <hjemli@gmail.com> | 2007-05-22 21:25:25 (UTC) |
---|---|---|
committer | Lars Hjemli <hjemli@gmail.com> | 2007-05-22 21:25:25 (UTC) |
commit | 57f6a8bf0de6c112cabc1d8e20ade2698bd886b7 (patch) (side-by-side diff) | |
tree | 281f1b4c6716b7632efe46607fc951d888e2fb5f | |
parent | 237ef7b06d461ddf27b21cd13bfcb50befa4a916 (diff) | |
download | cgit-57f6a8bf0de6c112cabc1d8e20ade2698bd886b7.zip cgit-57f6a8bf0de6c112cabc1d8e20ade2698bd886b7.tar.gz cgit-57f6a8bf0de6c112cabc1d8e20ade2698bd886b7.tar.bz2 |
Show time since last change on index page
When creating the index page, an optional file can be scanned per repository
to obtain a timestamp for last modification within the repo. If such a file
cannot be found, st_mtime for repo.defbranch is used instead.
This information is then printed in a new column, "Idle", using the new
function cgit_print_age().
The new parameter "repo.agefile" can be used to specify (globally) a relative
path to scan (default value is "info/web/last-modified").
The content of the "last-modified" file can be generated by the post-receive
hook with a command like this:
git-for-each-ref --format="%(committerdate)" --sort=-committerdate \
--count=1 > $GIT_DIR/info/web/last-modified
Signed-off-by: Lars Hjemli <hjemli@gmail.com>
-rw-r--r-- | cgit.h | 1 | ||||
-rw-r--r-- | shared.c | 3 | ||||
-rw-r--r-- | ui-repolist.c | 40 |
3 files changed, 43 insertions, 1 deletions
@@ -17,192 +17,193 @@ #include <archive.h> #include <xdiff/xdiff.h> /* * The valid cgit repo-commands */ #define CMD_LOG 1 #define CMD_COMMIT 2 #define CMD_DIFF 3 #define CMD_TREE 4 #define CMD_VIEW 5 #define CMD_BLOB 6 #define CMD_SNAPSHOT 7 /* * Dateformats used on misc. pages */ #define FMT_LONGDATE "%Y-%m-%d %H:%M:%S" #define FMT_SHORTDATE "%Y-%m-%d" /* * Limits used for relative dates */ #define TM_MIN 60 #define TM_HOUR (TM_MIN * 60) #define TM_DAY (TM_HOUR * 24) #define TM_WEEK (TM_DAY * 7) #define TM_YEAR (TM_DAY * 365) #define TM_MONTH (TM_YEAR / 12.0) typedef void (*configfn)(const char *name, const char *value); typedef void (*filepair_fn)(struct diff_filepair *pair); typedef void (*linediff_fn)(char *line, int len); struct cacheitem { char *name; struct stat st; int ttl; int fd; }; struct repoinfo { char *url; char *name; char *path; char *desc; char *owner; char *defbranch; char *group; char *module_link; int snapshots; int enable_log_filecount; int enable_log_linecount; }; struct repolist { int length; int count; struct repoinfo *repos; }; struct commitinfo { struct commit *commit; char *author; char *author_email; unsigned long author_date; char *committer; char *committer_email; unsigned long committer_date; char *subject; char *msg; }; struct taginfo { char *tagger; char *tagger_email; int tagger_date; char *msg; }; extern const char cgit_version[]; extern struct repolist cgit_repolist; extern struct repoinfo *cgit_repo; extern int cgit_cmd; extern char *cgit_root_title; extern char *cgit_css; extern char *cgit_logo; extern char *cgit_index_header; extern char *cgit_logo_link; extern char *cgit_module_link; +extern char *cgit_agefile; extern char *cgit_virtual_root; extern char *cgit_script_name; extern char *cgit_cache_root; extern char *cgit_repo_group; extern int cgit_nocache; extern int cgit_snapshots; extern int cgit_enable_log_filecount; extern int cgit_enable_log_linecount; extern int cgit_max_lock_attempts; extern int cgit_cache_root_ttl; extern int cgit_cache_repo_ttl; extern int cgit_cache_dynamic_ttl; extern int cgit_cache_static_ttl; extern int cgit_cache_max_create_time; extern int cgit_max_msg_len; extern int cgit_max_repodesc_len; extern int cgit_max_commit_count; extern int cgit_query_has_symref; extern int cgit_query_has_sha1; extern char *cgit_querystring; extern char *cgit_query_repo; extern char *cgit_query_page; extern char *cgit_query_search; extern char *cgit_query_head; extern char *cgit_query_sha1; extern char *cgit_query_sha2; extern char *cgit_query_path; extern char *cgit_query_name; extern int cgit_query_ofs; extern int htmlfd; extern int cgit_get_cmd_index(const char *cmd); extern struct repoinfo *cgit_get_repoinfo(const char *url); extern void cgit_global_config_cb(const char *name, const char *value); extern void cgit_repo_config_cb(const char *name, const char *value); extern void cgit_querystring_cb(const char *name, const char *value); extern int chk_zero(int result, char *msg); extern int chk_positive(int result, char *msg); extern int hextoint(char c); extern void *cgit_free_commitinfo(struct commitinfo *info); extern int cgit_diff_files(const unsigned char *old_sha1, const unsigned char *new_sha1, linediff_fn fn); extern void cgit_diff_tree(const unsigned char *old_sha1, const unsigned char *new_sha1, filepair_fn fn); extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); extern char *fmt(const char *format,...); extern void html(const char *txt); extern void htmlf(const char *format,...); extern void html_txt(char *txt); extern void html_ntxt(int len, char *txt); extern void html_attr(char *txt); extern void html_hidden(char *name, char *value); extern void html_link_open(char *url, char *title, char *class); extern void html_link_close(void); extern void html_filemode(unsigned short mode); extern int html_include(const char *filename); extern int cgit_read_config(const char *filename, configfn fn); extern int cgit_parse_query(char *txt, configfn fn); extern struct commitinfo *cgit_parse_commit(struct commit *commit); extern struct taginfo *cgit_parse_tag(struct tag *tag); extern void cgit_parse_url(const char *url); extern char *cache_safe_filename(const char *unsafe); extern int cache_lock(struct cacheitem *item); extern int cache_unlock(struct cacheitem *item); extern int cache_cancel_lock(struct cacheitem *item); extern int cache_exist(struct cacheitem *item); extern int cache_expired(struct cacheitem *item); extern char *cgit_repourl(const char *reponame); extern char *cgit_pageurl(const char *reponame, const char *pagename, const char *query); extern void cgit_print_error(char *msg); extern void cgit_print_date(time_t secs, char *format); extern void cgit_print_age(time_t t, time_t max_relative, char *format); extern void cgit_print_docstart(char *title, struct cacheitem *item); extern void cgit_print_docend(); extern void cgit_print_pageheader(char *title, int show_search); extern void cgit_print_snapshot_start(const char *mimetype, @@ -1,260 +1,263 @@ /* shared.c: global vars + some callback functions * * Copyright (C) 2006 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" struct repolist cgit_repolist; struct repoinfo *cgit_repo; int cgit_cmd; char *cgit_root_title = "Git repository browser"; char *cgit_css = "/cgit.css"; char *cgit_logo = "/git-logo.png"; char *cgit_index_header = NULL; char *cgit_logo_link = "http://www.kernel.org/pub/software/scm/git/docs/"; char *cgit_module_link = "./?repo=%s&page=commit&id=%s"; +char *cgit_agefile = "info/web/last-modified"; char *cgit_virtual_root = NULL; char *cgit_script_name = CGIT_SCRIPT_NAME; char *cgit_cache_root = "/var/cache/cgit"; char *cgit_repo_group = NULL; int cgit_nocache = 0; int cgit_snapshots = 0; int cgit_enable_log_filecount = 0; int cgit_enable_log_linecount = 0; int cgit_max_lock_attempts = 5; int cgit_cache_root_ttl = 5; int cgit_cache_repo_ttl = 5; int cgit_cache_dynamic_ttl = 5; int cgit_cache_static_ttl = -1; int cgit_cache_max_create_time = 5; int cgit_max_msg_len = 60; int cgit_max_repodesc_len = 60; int cgit_max_commit_count = 50; int cgit_query_has_symref = 0; int cgit_query_has_sha1 = 0; char *cgit_querystring = NULL; char *cgit_query_repo = NULL; char *cgit_query_page = NULL; char *cgit_query_head = NULL; char *cgit_query_search = NULL; char *cgit_query_sha1 = NULL; char *cgit_query_sha2 = NULL; char *cgit_query_path = NULL; char *cgit_query_name = NULL; int cgit_query_ofs = 0; int htmlfd = 0; int cgit_get_cmd_index(const char *cmd) { static char *cmds[] = {"log", "commit", "diff", "tree", "view", "blob", "snapshot", NULL}; int i; for(i = 0; cmds[i]; i++) if (!strcmp(cmd, cmds[i])) return i + 1; return 0; } int chk_zero(int result, char *msg) { if (result != 0) die("%s: %s", msg, strerror(errno)); return result; } int chk_positive(int result, char *msg) { if (result <= 0) die("%s: %s", msg, strerror(errno)); return result; } struct repoinfo *add_repo(const char *url) { struct repoinfo *ret; if (++cgit_repolist.count > cgit_repolist.length) { if (cgit_repolist.length == 0) cgit_repolist.length = 8; else cgit_repolist.length *= 2; cgit_repolist.repos = xrealloc(cgit_repolist.repos, cgit_repolist.length * sizeof(struct repoinfo)); } ret = &cgit_repolist.repos[cgit_repolist.count-1]; ret->url = xstrdup(url); ret->name = ret->url; ret->path = NULL; ret->desc = NULL; ret->owner = NULL; ret->group = cgit_repo_group; ret->defbranch = "master"; ret->snapshots = cgit_snapshots; ret->enable_log_filecount = cgit_enable_log_filecount; ret->enable_log_linecount = cgit_enable_log_linecount; ret->module_link = cgit_module_link; return ret; } struct repoinfo *cgit_get_repoinfo(const char *url) { int i; struct repoinfo *repo; for (i=0; i<cgit_repolist.count; i++) { repo = &cgit_repolist.repos[i]; if (!strcmp(repo->url, url)) return repo; } return NULL; } void cgit_global_config_cb(const char *name, const char *value) { if (!strcmp(name, "root-title")) cgit_root_title = xstrdup(value); else if (!strcmp(name, "css")) cgit_css = xstrdup(value); else if (!strcmp(name, "logo")) cgit_logo = xstrdup(value); else if (!strcmp(name, "index-header")) cgit_index_header = xstrdup(value); else if (!strcmp(name, "logo-link")) cgit_logo_link = xstrdup(value); else if (!strcmp(name, "module-link")) cgit_module_link = xstrdup(value); else if (!strcmp(name, "virtual-root")) cgit_virtual_root = xstrdup(value); else if (!strcmp(name, "nocache")) cgit_nocache = atoi(value); else if (!strcmp(name, "snapshots")) cgit_snapshots = atoi(value); else if (!strcmp(name, "enable-log-filecount")) cgit_enable_log_filecount = atoi(value); else if (!strcmp(name, "enable-log-linecount")) cgit_enable_log_linecount = atoi(value); else if (!strcmp(name, "cache-root")) cgit_cache_root = xstrdup(value); else if (!strcmp(name, "cache-root-ttl")) cgit_cache_root_ttl = atoi(value); else if (!strcmp(name, "cache-repo-ttl")) cgit_cache_repo_ttl = atoi(value); else if (!strcmp(name, "cache-static-ttl")) cgit_cache_static_ttl = atoi(value); else if (!strcmp(name, "cache-dynamic-ttl")) cgit_cache_dynamic_ttl = atoi(value); else if (!strcmp(name, "max-message-length")) cgit_max_msg_len = atoi(value); else if (!strcmp(name, "max-repodesc-length")) cgit_max_repodesc_len = atoi(value); else if (!strcmp(name, "max-commit-count")) cgit_max_commit_count = atoi(value); + else if (!strcmp(name, "agefile")) + cgit_agefile = xstrdup(value); else if (!strcmp(name, "repo.group")) cgit_repo_group = xstrdup(value); else if (!strcmp(name, "repo.url")) cgit_repo = add_repo(value); else if (!strcmp(name, "repo.name")) cgit_repo->name = xstrdup(value); else if (cgit_repo && !strcmp(name, "repo.path")) cgit_repo->path = xstrdup(value); else if (cgit_repo && !strcmp(name, "repo.desc")) cgit_repo->desc = xstrdup(value); else if (cgit_repo && !strcmp(name, "repo.owner")) cgit_repo->owner = xstrdup(value); else if (cgit_repo && !strcmp(name, "repo.defbranch")) cgit_repo->defbranch = xstrdup(value); else if (cgit_repo && !strcmp(name, "repo.snapshots")) cgit_repo->snapshots = cgit_snapshots * atoi(value); else if (cgit_repo && !strcmp(name, "repo.enable-log-filecount")) cgit_repo->enable_log_filecount = cgit_enable_log_filecount * atoi(value); else if (cgit_repo && !strcmp(name, "repo.enable-log-linecount")) cgit_repo->enable_log_linecount = cgit_enable_log_linecount * atoi(value); else if (cgit_repo && !strcmp(name, "repo.module-link")) cgit_repo->module_link= xstrdup(value); else if (!strcmp(name, "include")) cgit_read_config(value, cgit_global_config_cb); } void cgit_querystring_cb(const char *name, const char *value) { if (!strcmp(name,"r")) { cgit_query_repo = xstrdup(value); cgit_repo = cgit_get_repoinfo(value); } else if (!strcmp(name, "p")) { cgit_query_page = xstrdup(value); cgit_cmd = cgit_get_cmd_index(value); } else if (!strcmp(name, "url")) { cgit_parse_url(value); } else if (!strcmp(name, "q")) { cgit_query_search = xstrdup(value); } else if (!strcmp(name, "h")) { cgit_query_head = xstrdup(value); cgit_query_has_symref = 1; } else if (!strcmp(name, "id")) { cgit_query_sha1 = xstrdup(value); cgit_query_has_sha1 = 1; } else if (!strcmp(name, "id2")) { cgit_query_sha2 = xstrdup(value); cgit_query_has_sha1 = 1; } else if (!strcmp(name, "ofs")) { cgit_query_ofs = atoi(value); } else if (!strcmp(name, "path")) { cgit_query_path = xstrdup(value); } else if (!strcmp(name, "name")) { cgit_query_name = xstrdup(value); } } void *cgit_free_commitinfo(struct commitinfo *info) { free(info->author); free(info->author_email); free(info->committer); free(info->committer_email); free(info->subject); free(info); return NULL; } int hextoint(char c) { if (c >= 'a' && c <= 'f') return 10 + c - 'a'; else if (c >= 'A' && c <= 'F') return 10 + c - 'A'; else if (c >= '0' && c <= '9') return c - '0'; else return -1; } void cgit_diff_tree_cb(struct diff_queue_struct *q, struct diff_options *options, void *data) { int i; for (i = 0; i < q->nr; i++) { if (q->queue[i]->status == 'U') continue; ((filepair_fn)data)(q->queue[i]); } } static int load_mmfile(mmfile_t *file, const unsigned char *sha1) { enum object_type type; if (is_null_sha1(sha1)) { diff --git a/ui-repolist.c b/ui-repolist.c index f5e087f..573c730 100644 --- a/ui-repolist.c +++ b/ui-repolist.c @@ -1,66 +1,104 @@ /* ui-repolist.c: functions for generating the repolist page * * Copyright (C) 2006 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" +#include <time.h> + + +time_t read_agefile(char *path) +{ + FILE *f; + static char buf[64], buf2[64]; + struct tm tm; + + if (!(f = fopen(path, "r"))) + return -1; + fgets(buf, sizeof(buf), f); + fclose(f); + if (parse_date(buf, buf2, sizeof(buf2))) + return strtoul(buf2, NULL, 10); + else + return 0; +} + +static void print_modtime(struct repoinfo *repo) +{ + char *path; + struct stat s; + + path = fmt("%s/%s", repo->path, cgit_agefile); + if (stat(path, &s) == 0) { + cgit_print_age(read_agefile(path), -1, NULL); + return; + } + + path = fmt("%s/refs/heads/%s", repo->path, repo->defbranch); + if (stat(path, &s) != 0) + return; + cgit_print_age(s.st_mtime, -1, NULL); +} void cgit_print_repolist(struct cacheitem *item) { struct repoinfo *repo; int i; char *last_group = NULL; cgit_print_docstart(cgit_root_title, item); cgit_print_pageheader(cgit_root_title, 0); html("<table class='list nowrap'>"); if (cgit_index_header) { - html("<tr class='nohover'><td colspan='4' class='include-block'>"); + html("<tr class='nohover'><td colspan='5' class='include-block'>"); html_include(cgit_index_header); html("</td></tr>"); } html("<tr class='nohover'>" "<th class='left'>Name</th>" "<th class='left'>Description</th>" "<th class='left'>Owner</th>" + "<th class='left'>Idle</th>" "<th>Links</th></tr>\n"); for (i=0; i<cgit_repolist.count; i++) { repo = &cgit_repolist.repos[i]; if ((last_group == NULL && repo->group != NULL) || (last_group != NULL && repo->group == NULL) || (last_group != NULL && repo->group!= NULL && strcmp(repo->group, last_group))) { html("<tr class='nohover'><td colspan='4' class='repogroup'>"); html_txt(repo->group); html("</td></tr>"); last_group = repo->group; } htmlf("<tr><td class='%s'>", repo->group ? "sublevel-repo" : "toplevel-repo"); html_link_open(cgit_repourl(repo->url), repo->desc, NULL); html_txt(repo->name); html_link_close(); html("</td><td>"); html_ntxt(cgit_max_repodesc_len, repo->desc); html("</td><td>"); html_txt(repo->owner); html("</td><td>"); + print_modtime(repo); + html("</td><td>"); html_link_open(cgit_repourl(repo->url), "Summary", "button"); html("S</a>"); html_link_open(cgit_pageurl(repo->name, "log", NULL), "Log", "button"); html("L</a>"); html_link_open(cgit_pageurl(repo->name, "tree", NULL), "Files", "button"); html("F</a>"); html("</td></tr>\n"); } html("</table>"); cgit_print_docend(); } |