summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c6
-rw-r--r--cgit.css7
-rw-r--r--cgit.h3
-rw-r--r--cgitrc.5.txt15
-rw-r--r--shared.c1
-rw-r--r--ui-log.c103
6 files changed, 112 insertions, 23 deletions
diff --git a/cgit.c b/cgit.c
index 412fbf0..53ab68d 100644
--- a/cgit.c
+++ b/cgit.c
@@ -36,48 +36,50 @@ struct cgit_filter *new_filter(const char *cmd, int extra_args)
36 f = xmalloc(sizeof(struct cgit_filter)); 36 f = xmalloc(sizeof(struct cgit_filter));
37 f->cmd = xstrdup(cmd); 37 f->cmd = xstrdup(cmd);
38 f->argv = xmalloc((2 + extra_args) * sizeof(char *)); 38 f->argv = xmalloc((2 + extra_args) * sizeof(char *));
39 f->argv[0] = f->cmd; 39 f->argv[0] = f->cmd;
40 f->argv[1] = NULL; 40 f->argv[1] = NULL;
41 return f; 41 return f;
42} 42}
43 43
44static void process_cached_repolist(const char *path); 44static void process_cached_repolist(const char *path);
45 45
46void repo_config(struct cgit_repo *repo, const char *name, const char *value) 46void repo_config(struct cgit_repo *repo, const char *name, const char *value)
47{ 47{
48 if (!strcmp(name, "name")) 48 if (!strcmp(name, "name"))
49 repo->name = xstrdup(value); 49 repo->name = xstrdup(value);
50 else if (!strcmp(name, "clone-url")) 50 else if (!strcmp(name, "clone-url"))
51 repo->clone_url = xstrdup(value); 51 repo->clone_url = xstrdup(value);
52 else if (!strcmp(name, "desc")) 52 else if (!strcmp(name, "desc"))
53 repo->desc = xstrdup(value); 53 repo->desc = xstrdup(value);
54 else if (!strcmp(name, "owner")) 54 else if (!strcmp(name, "owner"))
55 repo->owner = xstrdup(value); 55 repo->owner = xstrdup(value);
56 else if (!strcmp(name, "defbranch")) 56 else if (!strcmp(name, "defbranch"))
57 repo->defbranch = xstrdup(value); 57 repo->defbranch = xstrdup(value);
58 else if (!strcmp(name, "snapshots")) 58 else if (!strcmp(name, "snapshots"))
59 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); 59 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value);
60 else if (!strcmp(name, "enable-commit-graph"))
61 repo->enable_commit_graph = ctx.cfg.enable_commit_graph * atoi(value);
60 else if (!strcmp(name, "enable-log-filecount")) 62 else if (!strcmp(name, "enable-log-filecount"))
61 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 63 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
62 else if (!strcmp(name, "enable-log-linecount")) 64 else if (!strcmp(name, "enable-log-linecount"))
63 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 65 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
64 else if (!strcmp(name, "enable-remote-branches")) 66 else if (!strcmp(name, "enable-remote-branches"))
65 repo->enable_remote_branches = atoi(value); 67 repo->enable_remote_branches = atoi(value);
66 else if (!strcmp(name, "enable-subject-links")) 68 else if (!strcmp(name, "enable-subject-links"))
67 repo->enable_subject_links = atoi(value); 69 repo->enable_subject_links = atoi(value);
68 else if (!strcmp(name, "max-stats")) 70 else if (!strcmp(name, "max-stats"))
69 repo->max_stats = cgit_find_stats_period(value, NULL); 71 repo->max_stats = cgit_find_stats_period(value, NULL);
70 else if (!strcmp(name, "module-link")) 72 else if (!strcmp(name, "module-link"))
71 repo->module_link= xstrdup(value); 73 repo->module_link= xstrdup(value);
72 else if (!strcmp(name, "section")) 74 else if (!strcmp(name, "section"))
73 repo->section = xstrdup(value); 75 repo->section = xstrdup(value);
74 else if (!strcmp(name, "readme") && value != NULL) { 76 else if (!strcmp(name, "readme") && value != NULL) {
75 repo->readme = xstrdup(value); 77 repo->readme = xstrdup(value);
76 } else if (ctx.cfg.enable_filter_overrides) { 78 } else if (ctx.cfg.enable_filter_overrides) {
77 if (!strcmp(name, "about-filter")) 79 if (!strcmp(name, "about-filter"))
78 repo->about_filter = new_filter(value, 0); 80 repo->about_filter = new_filter(value, 0);
79 else if (!strcmp(name, "commit-filter")) 81 else if (!strcmp(name, "commit-filter"))
80 repo->commit_filter = new_filter(value, 0); 82 repo->commit_filter = new_filter(value, 0);
81 else if (!strcmp(name, "source-filter")) 83 else if (!strcmp(name, "source-filter"))
82 repo->source_filter = new_filter(value, 1); 84 repo->source_filter = new_filter(value, 1);
83 } 85 }
@@ -120,48 +122,50 @@ void config_cb(const char *name, const char *value)
120 else if (!strcmp(name, "logo-link")) 122 else if (!strcmp(name, "logo-link"))
121 ctx.cfg.logo_link = xstrdup(value); 123 ctx.cfg.logo_link = xstrdup(value);
122 else if (!strcmp(name, "module-link")) 124 else if (!strcmp(name, "module-link"))
123 ctx.cfg.module_link = xstrdup(value); 125 ctx.cfg.module_link = xstrdup(value);
124 else if (!strcmp(name, "strict-export")) 126 else if (!strcmp(name, "strict-export"))
125 ctx.cfg.strict_export = xstrdup(value); 127 ctx.cfg.strict_export = xstrdup(value);
126 else if (!strcmp(name, "virtual-root")) { 128 else if (!strcmp(name, "virtual-root")) {
127 ctx.cfg.virtual_root = trim_end(value, '/'); 129 ctx.cfg.virtual_root = trim_end(value, '/');
128 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 130 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
129 ctx.cfg.virtual_root = ""; 131 ctx.cfg.virtual_root = "";
130 } else if (!strcmp(name, "nocache")) 132 } else if (!strcmp(name, "nocache"))
131 ctx.cfg.nocache = atoi(value); 133 ctx.cfg.nocache = atoi(value);
132 else if (!strcmp(name, "noplainemail")) 134 else if (!strcmp(name, "noplainemail"))
133 ctx.cfg.noplainemail = atoi(value); 135 ctx.cfg.noplainemail = atoi(value);
134 else if (!strcmp(name, "noheader")) 136 else if (!strcmp(name, "noheader"))
135 ctx.cfg.noheader = atoi(value); 137 ctx.cfg.noheader = atoi(value);
136 else if (!strcmp(name, "snapshots")) 138 else if (!strcmp(name, "snapshots"))
137 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 139 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
138 else if (!strcmp(name, "enable-filter-overrides")) 140 else if (!strcmp(name, "enable-filter-overrides"))
139 ctx.cfg.enable_filter_overrides = atoi(value); 141 ctx.cfg.enable_filter_overrides = atoi(value);
140 else if (!strcmp(name, "enable-gitweb-owner")) 142 else if (!strcmp(name, "enable-gitweb-owner"))
141 ctx.cfg.enable_gitweb_owner = atoi(value); 143 ctx.cfg.enable_gitweb_owner = atoi(value);
142 else if (!strcmp(name, "enable-index-links")) 144 else if (!strcmp(name, "enable-index-links"))
143 ctx.cfg.enable_index_links = atoi(value); 145 ctx.cfg.enable_index_links = atoi(value);
146 else if (!strcmp(name, "enable-commit-graph"))
147 ctx.cfg.enable_commit_graph = atoi(value);
144 else if (!strcmp(name, "enable-log-filecount")) 148 else if (!strcmp(name, "enable-log-filecount"))
145 ctx.cfg.enable_log_filecount = atoi(value); 149 ctx.cfg.enable_log_filecount = atoi(value);
146 else if (!strcmp(name, "enable-log-linecount")) 150 else if (!strcmp(name, "enable-log-linecount"))
147 ctx.cfg.enable_log_linecount = atoi(value); 151 ctx.cfg.enable_log_linecount = atoi(value);
148 else if (!strcmp(name, "enable-remote-branches")) 152 else if (!strcmp(name, "enable-remote-branches"))
149 ctx.cfg.enable_remote_branches = atoi(value); 153 ctx.cfg.enable_remote_branches = atoi(value);
150 else if (!strcmp(name, "enable-subject-links")) 154 else if (!strcmp(name, "enable-subject-links"))
151 ctx.cfg.enable_subject_links = atoi(value); 155 ctx.cfg.enable_subject_links = atoi(value);
152 else if (!strcmp(name, "enable-tree-linenumbers")) 156 else if (!strcmp(name, "enable-tree-linenumbers"))
153 ctx.cfg.enable_tree_linenumbers = atoi(value); 157 ctx.cfg.enable_tree_linenumbers = atoi(value);
154 else if (!strcmp(name, "max-stats")) 158 else if (!strcmp(name, "max-stats"))
155 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 159 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
156 else if (!strcmp(name, "cache-size")) 160 else if (!strcmp(name, "cache-size"))
157 ctx.cfg.cache_size = atoi(value); 161 ctx.cfg.cache_size = atoi(value);
158 else if (!strcmp(name, "cache-root")) 162 else if (!strcmp(name, "cache-root"))
159 ctx.cfg.cache_root = xstrdup(expand_macros(value)); 163 ctx.cfg.cache_root = xstrdup(expand_macros(value));
160 else if (!strcmp(name, "cache-root-ttl")) 164 else if (!strcmp(name, "cache-root-ttl"))
161 ctx.cfg.cache_root_ttl = atoi(value); 165 ctx.cfg.cache_root_ttl = atoi(value);
162 else if (!strcmp(name, "cache-repo-ttl")) 166 else if (!strcmp(name, "cache-repo-ttl"))
163 ctx.cfg.cache_repo_ttl = atoi(value); 167 ctx.cfg.cache_repo_ttl = atoi(value);
164 else if (!strcmp(name, "cache-scanrc-ttl")) 168 else if (!strcmp(name, "cache-scanrc-ttl"))
165 ctx.cfg.cache_scanrc_ttl = atoi(value); 169 ctx.cfg.cache_scanrc_ttl = atoi(value);
166 else if (!strcmp(name, "cache-static-ttl")) 170 else if (!strcmp(name, "cache-static-ttl"))
167 ctx.cfg.cache_static_ttl = atoi(value); 171 ctx.cfg.cache_static_ttl = atoi(value);
@@ -519,48 +523,50 @@ char *get_first_line(char *txt)
519} 523}
520 524
521void print_repo(FILE *f, struct cgit_repo *repo) 525void print_repo(FILE *f, struct cgit_repo *repo)
522{ 526{
523 fprintf(f, "repo.url=%s\n", repo->url); 527 fprintf(f, "repo.url=%s\n", repo->url);
524 fprintf(f, "repo.name=%s\n", repo->name); 528 fprintf(f, "repo.name=%s\n", repo->name);
525 fprintf(f, "repo.path=%s\n", repo->path); 529 fprintf(f, "repo.path=%s\n", repo->path);
526 if (repo->owner) 530 if (repo->owner)
527 fprintf(f, "repo.owner=%s\n", repo->owner); 531 fprintf(f, "repo.owner=%s\n", repo->owner);
528 if (repo->desc) { 532 if (repo->desc) {
529 char *tmp = get_first_line(repo->desc); 533 char *tmp = get_first_line(repo->desc);
530 fprintf(f, "repo.desc=%s\n", tmp); 534 fprintf(f, "repo.desc=%s\n", tmp);
531 free(tmp); 535 free(tmp);
532 } 536 }
533 if (repo->readme) 537 if (repo->readme)
534 fprintf(f, "repo.readme=%s\n", repo->readme); 538 fprintf(f, "repo.readme=%s\n", repo->readme);
535 if (repo->defbranch) 539 if (repo->defbranch)
536 fprintf(f, "repo.defbranch=%s\n", repo->defbranch); 540 fprintf(f, "repo.defbranch=%s\n", repo->defbranch);
537 if (repo->module_link) 541 if (repo->module_link)
538 fprintf(f, "repo.module-link=%s\n", repo->module_link); 542 fprintf(f, "repo.module-link=%s\n", repo->module_link);
539 if (repo->section) 543 if (repo->section)
540 fprintf(f, "repo.section=%s\n", repo->section); 544 fprintf(f, "repo.section=%s\n", repo->section);
541 if (repo->clone_url) 545 if (repo->clone_url)
542 fprintf(f, "repo.clone-url=%s\n", repo->clone_url); 546 fprintf(f, "repo.clone-url=%s\n", repo->clone_url);
547 fprintf(f, "repo.enable-commit-graph=%d\n",
548 repo->enable_commit_graph);
543 fprintf(f, "repo.enable-log-filecount=%d\n", 549 fprintf(f, "repo.enable-log-filecount=%d\n",
544 repo->enable_log_filecount); 550 repo->enable_log_filecount);
545 fprintf(f, "repo.enable-log-linecount=%d\n", 551 fprintf(f, "repo.enable-log-linecount=%d\n",
546 repo->enable_log_linecount); 552 repo->enable_log_linecount);
547 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter) 553 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter)
548 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd); 554 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd);
549 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter) 555 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter)
550 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd); 556 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd);
551 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter) 557 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter)
552 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd); 558 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd);
553 if (repo->snapshots != ctx.cfg.snapshots) { 559 if (repo->snapshots != ctx.cfg.snapshots) {
554 char *tmp = build_snapshot_setting(repo->snapshots); 560 char *tmp = build_snapshot_setting(repo->snapshots);
555 fprintf(f, "repo.snapshots=%s\n", tmp); 561 fprintf(f, "repo.snapshots=%s\n", tmp);
556 free(tmp); 562 free(tmp);
557 } 563 }
558 if (repo->max_stats != ctx.cfg.max_stats) 564 if (repo->max_stats != ctx.cfg.max_stats)
559 fprintf(f, "repo.max-stats=%s\n", 565 fprintf(f, "repo.max-stats=%s\n",
560 cgit_find_stats_periodname(repo->max_stats)); 566 cgit_find_stats_periodname(repo->max_stats));
561 fprintf(f, "\n"); 567 fprintf(f, "\n");
562} 568}
563 569
564void print_repolist(FILE *f, struct cgit_repolist *list, int start) 570void print_repolist(FILE *f, struct cgit_repolist *list, int start)
565{ 571{
566 int i; 572 int i;
diff --git a/cgit.css b/cgit.css
index 7a5f423..7600e84 100644
--- a/cgit.css
+++ b/cgit.css
@@ -132,48 +132,53 @@ table.list tr.logheader {
132 132
133table.list tr:hover { 133table.list tr:hover {
134 background: #eee; 134 background: #eee;
135} 135}
136 136
137table.list tr.nohover:hover { 137table.list tr.nohover:hover {
138 background: white; 138 background: white;
139} 139}
140 140
141table.list th { 141table.list th {
142 font-weight: bold; 142 font-weight: bold;
143 /* color: #888; 143 /* color: #888;
144 border-top: dashed 1px #888; 144 border-top: dashed 1px #888;
145 border-bottom: dashed 1px #888; 145 border-bottom: dashed 1px #888;
146 */ 146 */
147 padding: 0.1em 0.5em 0.05em 0.5em; 147 padding: 0.1em 0.5em 0.05em 0.5em;
148 vertical-align: baseline; 148 vertical-align: baseline;
149} 149}
150 150
151table.list td { 151table.list td {
152 border: none; 152 border: none;
153 padding: 0.1em 0.5em 0.1em 0.5em; 153 padding: 0.1em 0.5em 0.1em 0.5em;
154} 154}
155 155
156table.list td.commitgraph {
157 font-family: monospace;
158 white-space: pre;
159}
160
156table.list td.logsubject { 161table.list td.logsubject {
157 font-family: monospace; 162 font-family: monospace;
158 font-weight: bold; 163 font-weight: bold;
159} 164}
160 165
161table.list td.logmsg { 166table.list td.logmsg {
162 font-family: monospace; 167 font-family: monospace;
163 white-space: pre; 168 white-space: pre;
164 padding: 0 0.5em; 169 padding: 0 0.5em;
165} 170}
166 171
167table.list td a { 172table.list td a {
168 color: black; 173 color: black;
169} 174}
170 175
171table.list td a.ls-dir { 176table.list td a.ls-dir {
172 font-weight: bold; 177 font-weight: bold;
173 color: #00f; 178 color: #00f;
174} 179}
175 180
176table.list td a:hover { 181table.list td a:hover {
177 color: #00f; 182 color: #00f;
178} 183}
179 184
@@ -710,25 +715,25 @@ table.ssdiff td.hunk {
710 715
711table.ssdiff td.head { 716table.ssdiff td.head {
712 border-top: solid 1px #aaa; 717 border-top: solid 1px #aaa;
713 border-bottom: solid 1px #aaa; 718 border-bottom: solid 1px #aaa;
714} 719}
715 720
716table.ssdiff td.head div.head { 721table.ssdiff td.head div.head {
717 font-weight: bold; 722 font-weight: bold;
718 color: black; 723 color: black;
719} 724}
720 725
721table.ssdiff td.foot { 726table.ssdiff td.foot {
722 border-top: solid 1px #aaa; 727 border-top: solid 1px #aaa;
723 border-left: none; 728 border-left: none;
724 border-right: none; 729 border-right: none;
725 border-bottom: none; 730 border-bottom: none;
726} 731}
727 732
728table.ssdiff td.space { 733table.ssdiff td.space {
729 border: none; 734 border: none;
730} 735}
731 736
732table.ssdiff td.space div { 737table.ssdiff td.space div {
733 min-height: 3em; 738 min-height: 3em;
734} \ No newline at end of file 739}
diff --git a/cgit.h b/cgit.h
index f5f68ac..bed770b 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,46 +1,47 @@
1#ifndef CGIT_H 1#ifndef CGIT_H
2#define CGIT_H 2#define CGIT_H
3 3
4 4
5#include <git-compat-util.h> 5#include <git-compat-util.h>
6#include <cache.h> 6#include <cache.h>
7#include <grep.h> 7#include <grep.h>
8#include <object.h> 8#include <object.h>
9#include <tree.h> 9#include <tree.h>
10#include <commit.h> 10#include <commit.h>
11#include <tag.h> 11#include <tag.h>
12#include <diff.h> 12#include <diff.h>
13#include <diffcore.h> 13#include <diffcore.h>
14#include <refs.h> 14#include <refs.h>
15#include <revision.h> 15#include <revision.h>
16#include <log-tree.h> 16#include <log-tree.h>
17#include <archive.h> 17#include <archive.h>
18#include <string-list.h> 18#include <string-list.h>
19#include <xdiff-interface.h> 19#include <xdiff-interface.h>
20#include <xdiff/xdiff.h> 20#include <xdiff/xdiff.h>
21#include <utf8.h> 21#include <utf8.h>
22#include <notes.h> 22#include <notes.h>
23#include <graph.h>
23 24
24 25
25/* 26/*
26 * Dateformats used on misc. pages 27 * Dateformats used on misc. pages
27 */ 28 */
28#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)" 29#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)"
29#define FMT_SHORTDATE "%Y-%m-%d" 30#define FMT_SHORTDATE "%Y-%m-%d"
30#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ" 31#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ"
31 32
32 33
33/* 34/*
34 * Limits used for relative dates 35 * Limits used for relative dates
35 */ 36 */
36#define TM_MIN 60 37#define TM_MIN 60
37#define TM_HOUR (TM_MIN * 60) 38#define TM_HOUR (TM_MIN * 60)
38#define TM_DAY (TM_HOUR * 24) 39#define TM_DAY (TM_HOUR * 24)
39#define TM_WEEK (TM_DAY * 7) 40#define TM_WEEK (TM_DAY * 7)
40#define TM_YEAR (TM_DAY * 365) 41#define TM_YEAR (TM_DAY * 365)
41#define TM_MONTH (TM_YEAR / 12.0) 42#define TM_MONTH (TM_YEAR / 12.0)
42 43
43 44
44/* 45/*
45 * Default encoding 46 * Default encoding
46 */ 47 */
@@ -50,48 +51,49 @@ typedef void (*configfn)(const char *name, const char *value);
50typedef void (*filepair_fn)(struct diff_filepair *pair); 51typedef void (*filepair_fn)(struct diff_filepair *pair);
51typedef void (*linediff_fn)(char *line, int len); 52typedef void (*linediff_fn)(char *line, int len);
52 53
53struct cgit_filter { 54struct cgit_filter {
54 char *cmd; 55 char *cmd;
55 char **argv; 56 char **argv;
56 int old_stdout; 57 int old_stdout;
57 int pipe_fh[2]; 58 int pipe_fh[2];
58 int pid; 59 int pid;
59 int exitstatus; 60 int exitstatus;
60}; 61};
61 62
62struct cgit_repo { 63struct cgit_repo {
63 char *url; 64 char *url;
64 char *name; 65 char *name;
65 char *path; 66 char *path;
66 char *desc; 67 char *desc;
67 char *owner; 68 char *owner;
68 char *defbranch; 69 char *defbranch;
69 char *module_link; 70 char *module_link;
70 char *readme; 71 char *readme;
71 char *section; 72 char *section;
72 char *clone_url; 73 char *clone_url;
73 int snapshots; 74 int snapshots;
75 int enable_commit_graph;
74 int enable_log_filecount; 76 int enable_log_filecount;
75 int enable_log_linecount; 77 int enable_log_linecount;
76 int enable_remote_branches; 78 int enable_remote_branches;
77 int enable_subject_links; 79 int enable_subject_links;
78 int max_stats; 80 int max_stats;
79 time_t mtime; 81 time_t mtime;
80 struct cgit_filter *about_filter; 82 struct cgit_filter *about_filter;
81 struct cgit_filter *commit_filter; 83 struct cgit_filter *commit_filter;
82 struct cgit_filter *source_filter; 84 struct cgit_filter *source_filter;
83}; 85};
84 86
85typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name, 87typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name,
86 const char *value); 88 const char *value);
87 89
88struct cgit_repolist { 90struct cgit_repolist {
89 int length; 91 int length;
90 int count; 92 int count;
91 struct cgit_repo *repos; 93 struct cgit_repo *repos;
92}; 94};
93 95
94struct commitinfo { 96struct commitinfo {
95 struct commit *commit; 97 struct commit *commit;
96 char *author; 98 char *author;
97 char *author_email; 99 char *author_email;
@@ -167,48 +169,49 @@ struct cgit_config {
167 char *logo; 169 char *logo;
168 char *logo_link; 170 char *logo_link;
169 char *module_link; 171 char *module_link;
170 char *project_list; 172 char *project_list;
171 char *readme; 173 char *readme;
172 char *robots; 174 char *robots;
173 char *root_title; 175 char *root_title;
174 char *root_desc; 176 char *root_desc;
175 char *root_readme; 177 char *root_readme;
176 char *script_name; 178 char *script_name;
177 char *section; 179 char *section;
178 char *virtual_root; 180 char *virtual_root;
179 char *strict_export; 181 char *strict_export;
180 int cache_size; 182 int cache_size;
181 int cache_dynamic_ttl; 183 int cache_dynamic_ttl;
182 int cache_max_create_time; 184 int cache_max_create_time;
183 int cache_repo_ttl; 185 int cache_repo_ttl;
184 int cache_root_ttl; 186 int cache_root_ttl;
185 int cache_scanrc_ttl; 187 int cache_scanrc_ttl;
186 int cache_static_ttl; 188 int cache_static_ttl;
187 int embedded; 189 int embedded;
188 int enable_filter_overrides; 190 int enable_filter_overrides;
189 int enable_gitweb_owner; 191 int enable_gitweb_owner;
190 int enable_index_links; 192 int enable_index_links;
193 int enable_commit_graph;
191 int enable_log_filecount; 194 int enable_log_filecount;
192 int enable_log_linecount; 195 int enable_log_linecount;
193 int enable_remote_branches; 196 int enable_remote_branches;
194 int enable_subject_links; 197 int enable_subject_links;
195 int enable_tree_linenumbers; 198 int enable_tree_linenumbers;
196 int local_time; 199 int local_time;
197 int max_atom_items; 200 int max_atom_items;
198 int max_repo_count; 201 int max_repo_count;
199 int max_commit_count; 202 int max_commit_count;
200 int max_lock_attempts; 203 int max_lock_attempts;
201 int max_msg_len; 204 int max_msg_len;
202 int max_repodesc_len; 205 int max_repodesc_len;
203 int max_blob_size; 206 int max_blob_size;
204 int max_stats; 207 int max_stats;
205 int nocache; 208 int nocache;
206 int noplainemail; 209 int noplainemail;
207 int noheader; 210 int noheader;
208 int renamelimit; 211 int renamelimit;
209 int remove_suffix; 212 int remove_suffix;
210 int section_from_path; 213 int section_from_path;
211 int snapshots; 214 int snapshots;
212 int summary_branches; 215 int summary_branches;
213 int summary_log; 216 int summary_log;
214 int summary_tags; 217 int summary_tags;
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 75b6584..b45c46b 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -69,49 +69,54 @@ cache-static-ttl::
69 Number which specifies the time-to-live, in minutes, for the cached 69 Number which specifies the time-to-live, in minutes, for the cached
70 version of repository pages accessed with a fixed SHA1. Default value: 70 version of repository pages accessed with a fixed SHA1. Default value:
71 "5". 71 "5".
72 72
73clone-prefix:: 73clone-prefix::
74 Space-separated list of common prefixes which, when combined with a 74 Space-separated list of common prefixes which, when combined with a
75 repository url, generates valid clone urls for the repository. This 75 repository url, generates valid clone urls for the repository. This
76 setting is only used if `repo.clone-url` is unspecified. Default value: 76 setting is only used if `repo.clone-url` is unspecified. Default value:
77 none. 77 none.
78 78
79commit-filter:: 79commit-filter::
80 Specifies a command which will be invoked to format commit messages. 80 Specifies a command which will be invoked to format commit messages.
81 The command will get the message on its STDIN, and the STDOUT from the 81 The command will get the message on its STDIN, and the STDOUT from the
82 command will be included verbatim as the commit message, i.e. this can 82 command will be included verbatim as the commit message, i.e. this can
83 be used to implement bugtracker integration. Default value: none. 83 be used to implement bugtracker integration. Default value: none.
84 84
85css:: 85css::
86 Url which specifies the css document to include in all cgit pages. 86 Url which specifies the css document to include in all cgit pages.
87 Default value: "/cgit.css". 87 Default value: "/cgit.css".
88 88
89embedded:: 89embedded::
90 Flag which, when set to "1", will make cgit generate a html fragment 90 Flag which, when set to "1", will make cgit generate a html fragment
91 suitable for embedding in other html pages. Default value: none. See 91 suitable for embedding in other html pages. Default value: none. See
92 also: "noheader". 92 also: "noheader".
93 93
94enable-commit-graph::
95 Flag which, when set to "1", will make cgit print an ASCII-art commit
96 history graph to the left of the commit messages in the repository
97 log page. Default value: "0".
98
94enable-filter-overrides:: 99enable-filter-overrides::
95 Flag which, when set to "1", allows all filter settings to be 100 Flag which, when set to "1", allows all filter settings to be
96 overridden in repository-specific cgitrc files. Default value: none. 101 overridden in repository-specific cgitrc files. Default value: none.
97 102
98enable-gitweb-owner:: 103enable-gitweb-owner::
99 If set to "1" and scan-path is enabled, we first check each repository 104 If set to "1" and scan-path is enabled, we first check each repository
100 for the git config value "gitweb.owner" to determine the owner. 105 for the git config value "gitweb.owner" to determine the owner.
101 Default value: "1". See also: scan-path. 106 Default value: "1". See also: scan-path.
102 107
103enable-index-links:: 108enable-index-links::
104 Flag which, when set to "1", will make cgit generate extra links for 109 Flag which, when set to "1", will make cgit generate extra links for
105 each repo in the repository index (specifically, to the "summary", 110 each repo in the repository index (specifically, to the "summary",
106 "commit" and "tree" pages). Default value: "0". 111 "commit" and "tree" pages). Default value: "0".
107 112
108enable-log-filecount:: 113enable-log-filecount::
109 Flag which, when set to "1", will make cgit print the number of 114 Flag which, when set to "1", will make cgit print the number of
110 modified files for each commit on the repository log page. Default 115 modified files for each commit on the repository log page. Default
111 value: "0". 116 value: "0".
112 117
113enable-log-linecount:: 118enable-log-linecount::
114 Flag which, when set to "1", will make cgit print the number of added 119 Flag which, when set to "1", will make cgit print the number of added
115 and removed lines for each commit on the repository log page. Default 120 and removed lines for each commit on the repository log page. Default
116 value: "0". 121 value: "0".
117 122
@@ -333,48 +338,52 @@ virtual-root::
333 same kind of virtual urls, so this option will probably be deprecated. 338 same kind of virtual urls, so this option will probably be deprecated.
334 339
335REPOSITORY SETTINGS 340REPOSITORY SETTINGS
336------------------- 341-------------------
337repo.about-filter:: 342repo.about-filter::
338 Override the default about-filter. Default value: none. See also: 343 Override the default about-filter. Default value: none. See also:
339 "enable-filter-overrides". 344 "enable-filter-overrides".
340 345
341repo.clone-url:: 346repo.clone-url::
342 A list of space-separated urls which can be used to clone this repo. 347 A list of space-separated urls which can be used to clone this repo.
343 Default value: none. 348 Default value: none.
344 349
345repo.commit-filter:: 350repo.commit-filter::
346 Override the default commit-filter. Default value: none. See also: 351 Override the default commit-filter. Default value: none. See also:
347 "enable-filter-overrides". 352 "enable-filter-overrides".
348 353
349repo.defbranch:: 354repo.defbranch::
350 The name of the default branch for this repository. If no such branch 355 The name of the default branch for this repository. If no such branch
351 exists in the repository, the first branch name (when sorted) is used 356 exists in the repository, the first branch name (when sorted) is used
352 as default instead. Default value: "master". 357 as default instead. Default value: "master".
353 358
354repo.desc:: 359repo.desc::
355 The value to show as repository description. Default value: none. 360 The value to show as repository description. Default value: none.
356 361
362repo.enable-commit-graph::
363 A flag which can be used to disable the global setting
364 `enable-commit-graph'. Default value: none.
365
357repo.enable-log-filecount:: 366repo.enable-log-filecount::
358 A flag which can be used to disable the global setting 367 A flag which can be used to disable the global setting
359 `enable-log-filecount'. Default value: none. 368 `enable-log-filecount'. Default value: none.
360 369
361repo.enable-log-linecount:: 370repo.enable-log-linecount::
362 A flag which can be used to disable the global setting 371 A flag which can be used to disable the global setting
363 `enable-log-linecount'. Default value: none. 372 `enable-log-linecount'. Default value: none.
364 373
365repo.enable-remote-branches:: 374repo.enable-remote-branches::
366 Flag which, when set to "1", will make cgit display remote branches 375 Flag which, when set to "1", will make cgit display remote branches
367 in the summary and refs views. Default value: <enable-remote-branches>. 376 in the summary and refs views. Default value: <enable-remote-branches>.
368 377
369repo.enable-subject-links:: 378repo.enable-subject-links::
370 A flag which can be used to override the global setting 379 A flag which can be used to override the global setting
371 `enable-subject-links'. Default value: none. 380 `enable-subject-links'. Default value: none.
372 381
373repo.max-stats:: 382repo.max-stats::
374 Override the default maximum statistics period. Valid values are equal 383 Override the default maximum statistics period. Valid values are equal
375 to the values specified for the global "max-stats" setting. Default 384 to the values specified for the global "max-stats" setting. Default
376 value: none. 385 value: none.
377 386
378repo.name:: 387repo.name::
379 The value to show as repository name. Default value: <repo.url>. 388 The value to show as repository name. Default value: <repo.url>.
380 389
@@ -420,48 +429,52 @@ options are only acknowledged in repo-specific config files when
420 429
421Note: the "repo." prefix is dropped from the option names in repo-specific 430Note: the "repo." prefix is dropped from the option names in repo-specific
422config files, e.g. "repo.desc" becomes "desc". 431config files, e.g. "repo.desc" becomes "desc".
423 432
424 433
425EXAMPLE CGITRC FILE 434EXAMPLE CGITRC FILE
426------------------- 435-------------------
427 436
428.... 437....
429# Enable caching of up to 1000 output entriess 438# Enable caching of up to 1000 output entriess
430cache-size=1000 439cache-size=1000
431 440
432 441
433# Specify some default clone prefixes 442# Specify some default clone prefixes
434clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git 443clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git
435 444
436# Specify the css url 445# Specify the css url
437css=/css/cgit.css 446css=/css/cgit.css
438 447
439 448
440# Show extra links for each repository on the index page 449# Show extra links for each repository on the index page
441enable-index-links=1 450enable-index-links=1
442 451
443 452
453# Enable ASCII art commit history graph on the log pages
454enable-commit-graph=1
455
456
444# Show number of affected files per commit on the log pages 457# Show number of affected files per commit on the log pages
445enable-log-filecount=1 458enable-log-filecount=1
446 459
447 460
448# Show number of added/removed lines per commit on the log pages 461# Show number of added/removed lines per commit on the log pages
449enable-log-linecount=1 462enable-log-linecount=1
450 463
451 464
452# Add a cgit favicon 465# Add a cgit favicon
453favicon=/favicon.ico 466favicon=/favicon.ico
454 467
455 468
456# Use a custom logo 469# Use a custom logo
457logo=/img/mylogo.png 470logo=/img/mylogo.png
458 471
459 472
460# Enable statistics per week, month and quarter 473# Enable statistics per week, month and quarter
461max-stats=quarter 474max-stats=quarter
462 475
463 476
464# Set the title and heading of the repository index page 477# Set the title and heading of the repository index page
465root-title=foobar.com git repositories 478root-title=foobar.com git repositories
466 479
467 480
diff --git a/shared.c b/shared.c
index 765cd27..7ec2e19 100644
--- a/shared.c
+++ b/shared.c
@@ -35,48 +35,49 @@ int chk_non_negative(int result, char *msg)
35struct cgit_repo *cgit_add_repo(const char *url) 35struct cgit_repo *cgit_add_repo(const char *url)
36{ 36{
37 struct cgit_repo *ret; 37 struct cgit_repo *ret;
38 38
39 if (++cgit_repolist.count > cgit_repolist.length) { 39 if (++cgit_repolist.count > cgit_repolist.length) {
40 if (cgit_repolist.length == 0) 40 if (cgit_repolist.length == 0)
41 cgit_repolist.length = 8; 41 cgit_repolist.length = 8;
42 else 42 else
43 cgit_repolist.length *= 2; 43 cgit_repolist.length *= 2;
44 cgit_repolist.repos = xrealloc(cgit_repolist.repos, 44 cgit_repolist.repos = xrealloc(cgit_repolist.repos,
45 cgit_repolist.length * 45 cgit_repolist.length *
46 sizeof(struct cgit_repo)); 46 sizeof(struct cgit_repo));
47 } 47 }
48 48
49 ret = &cgit_repolist.repos[cgit_repolist.count-1]; 49 ret = &cgit_repolist.repos[cgit_repolist.count-1];
50 memset(ret, 0, sizeof(struct cgit_repo)); 50 memset(ret, 0, sizeof(struct cgit_repo));
51 ret->url = trim_end(url, '/'); 51 ret->url = trim_end(url, '/');
52 ret->name = ret->url; 52 ret->name = ret->url;
53 ret->path = NULL; 53 ret->path = NULL;
54 ret->desc = "[no description]"; 54 ret->desc = "[no description]";
55 ret->owner = NULL; 55 ret->owner = NULL;
56 ret->section = ctx.cfg.section; 56 ret->section = ctx.cfg.section;
57 ret->defbranch = "master"; 57 ret->defbranch = "master";
58 ret->snapshots = ctx.cfg.snapshots; 58 ret->snapshots = ctx.cfg.snapshots;
59 ret->enable_commit_graph = ctx.cfg.enable_commit_graph;
59 ret->enable_log_filecount = ctx.cfg.enable_log_filecount; 60 ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
60 ret->enable_log_linecount = ctx.cfg.enable_log_linecount; 61 ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
61 ret->enable_remote_branches = ctx.cfg.enable_remote_branches; 62 ret->enable_remote_branches = ctx.cfg.enable_remote_branches;
62 ret->enable_subject_links = ctx.cfg.enable_subject_links; 63 ret->enable_subject_links = ctx.cfg.enable_subject_links;
63 ret->max_stats = ctx.cfg.max_stats; 64 ret->max_stats = ctx.cfg.max_stats;
64 ret->module_link = ctx.cfg.module_link; 65 ret->module_link = ctx.cfg.module_link;
65 ret->readme = ctx.cfg.readme; 66 ret->readme = ctx.cfg.readme;
66 ret->mtime = -1; 67 ret->mtime = -1;
67 ret->about_filter = ctx.cfg.about_filter; 68 ret->about_filter = ctx.cfg.about_filter;
68 ret->commit_filter = ctx.cfg.commit_filter; 69 ret->commit_filter = ctx.cfg.commit_filter;
69 ret->source_filter = ctx.cfg.source_filter; 70 ret->source_filter = ctx.cfg.source_filter;
70 return ret; 71 return ret;
71} 72}
72 73
73struct cgit_repo *cgit_get_repoinfo(const char *url) 74struct cgit_repo *cgit_get_repoinfo(const char *url)
74{ 75{
75 int i; 76 int i;
76 struct cgit_repo *repo; 77 struct cgit_repo *repo;
77 78
78 for (i=0; i<cgit_repolist.count; i++) { 79 for (i=0; i<cgit_repolist.count; i++) {
79 repo = &cgit_repolist.repos[i]; 80 repo = &cgit_repolist.repos[i];
80 if (!strcmp(repo->url, url)) 81 if (!strcmp(repo->url, url))
81 return repo; 82 return repo;
82 } 83 }
diff --git a/ui-log.c b/ui-log.c
index 6d7fcae..0d86fd5 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -56,109 +56,164 @@ void show_commit_decorations(struct commit *commit)
56 strncpy(buf, deco->name + 15, sizeof(buf) - 1); 56 strncpy(buf, deco->name + 15, sizeof(buf) - 1);
57 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 57 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
58 } 58 }
59 else if (!prefixcmp(deco->name, "refs/tags/")) { 59 else if (!prefixcmp(deco->name, "refs/tags/")) {
60 strncpy(buf, deco->name + 10, sizeof(buf) - 1); 60 strncpy(buf, deco->name + 10, sizeof(buf) - 1);
61 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 61 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
62 } 62 }
63 else if (!prefixcmp(deco->name, "refs/remotes/")) { 63 else if (!prefixcmp(deco->name, "refs/remotes/")) {
64 strncpy(buf, deco->name + 13, sizeof(buf) - 1); 64 strncpy(buf, deco->name + 13, sizeof(buf) - 1);
65 cgit_log_link(buf, NULL, "remote-deco", NULL, 65 cgit_log_link(buf, NULL, "remote-deco", NULL,
66 sha1_to_hex(commit->object.sha1), 66 sha1_to_hex(commit->object.sha1),
67 ctx.qry.vpath, 0, NULL, NULL, 67 ctx.qry.vpath, 0, NULL, NULL,
68 ctx.qry.showmsg); 68 ctx.qry.showmsg);
69 } 69 }
70 else { 70 else {
71 strncpy(buf, deco->name, sizeof(buf) - 1); 71 strncpy(buf, deco->name, sizeof(buf) - 1);
72 cgit_commit_link(buf, NULL, "deco", ctx.qry.head, 72 cgit_commit_link(buf, NULL, "deco", ctx.qry.head,
73 sha1_to_hex(commit->object.sha1), 73 sha1_to_hex(commit->object.sha1),
74 ctx.qry.vpath, 0); 74 ctx.qry.vpath, 0);
75 } 75 }
76 deco = deco->next; 76 deco = deco->next;
77 } 77 }
78} 78}
79 79
80void print_commit(struct commit *commit) 80void print_commit(struct commit *commit, struct rev_info *revs)
81{ 81{
82 struct commitinfo *info; 82 struct commitinfo *info;
83 char *tmp; 83 char *tmp;
84 int cols = 2; 84 int cols = 2;
85 struct strbuf graphbuf = STRBUF_INIT;
86
87 if (ctx.repo->enable_log_filecount) {
88 cols++;
89 if (ctx.repo->enable_log_linecount)
90 cols++;
91 }
92
93 if (revs->graph) {
94 /* Advance graph until current commit */
95 while (!graph_next_line(revs->graph, &graphbuf)) {
96 /* Print graph segment in otherwise empty table row */
97 html("<tr class='nohover'><td/><td class='commitgraph'>");
98 html(graphbuf.buf);
99 htmlf("</td><td colspan='%d' /></tr>\n", cols);
100 strbuf_setlen(&graphbuf, 0);
101 }
102 /* Current commit's graph segment is now ready in graphbuf */
103 }
85 104
86 info = cgit_parse_commit(commit); 105 info = cgit_parse_commit(commit);
87 htmlf("<tr%s><td>", 106 htmlf("<tr%s><td>",
88 ctx.qry.showmsg ? " class='logheader'" : ""); 107 ctx.qry.showmsg ? " class='logheader'" : "");
89 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); 108 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
90 tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp); 109 tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp);
91 html_link_open(tmp, NULL, NULL); 110 html_link_open(tmp, NULL, NULL);
92 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); 111 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
93 html_link_close(); 112 html_link_close();
94 htmlf("</td><td%s>", 113 html("</td>");
95 ctx.qry.showmsg ? " class='logsubject'" : ""); 114
115 if (revs->graph) {
116 /* Print graph segment for current commit */
117 html("<td class='commitgraph'>");
118 html(graphbuf.buf);
119 html("</td>");
120 strbuf_setlen(&graphbuf, 0);
121 }
122
123 htmlf("<td%s>", ctx.qry.showmsg ? " class='logsubject'" : "");
96 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, 124 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
97 sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0); 125 sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0);
98 show_commit_decorations(commit); 126 show_commit_decorations(commit);
99 html("</td><td>"); 127 html("</td><td>");
100 html_txt(info->author); 128 html_txt(info->author);
101 if (ctx.repo->enable_log_filecount) { 129 if (ctx.repo->enable_log_filecount) {
102 files = 0; 130 files = 0;
103 add_lines = 0; 131 add_lines = 0;
104 rem_lines = 0; 132 rem_lines = 0;
105 cgit_diff_commit(commit, inspect_files, ctx.qry.vpath); 133 cgit_diff_commit(commit, inspect_files, ctx.qry.vpath);
106 html("</td><td>"); 134 html("</td><td>");
107 htmlf("%d", files); 135 htmlf("%d", files);
108 if (ctx.repo->enable_log_linecount) { 136 if (ctx.repo->enable_log_linecount) {
109 html("</td><td>"); 137 html("</td><td>");
110 htmlf("-%d/+%d", rem_lines, add_lines); 138 htmlf("-%d/+%d", rem_lines, add_lines);
111 } 139 }
112 } 140 }
113 html("</td></tr>\n"); 141 html("</td></tr>\n");
114 142
115 if (ctx.qry.showmsg) { /* Print message + notes in a second table row */ 143 if (revs->graph || ctx.qry.showmsg) { /* Print a second table row */
116 /* Concatenate commit message and notes in msgbuf */
117 struct strbuf msgbuf = STRBUF_INIT; 144 struct strbuf msgbuf = STRBUF_INIT;
118 if (info->msg && *(info->msg)) { 145 html("<tr class='nohover'><td/>"); /* Empty 'Age' column */
119 strbuf_addstr(&msgbuf, info->msg); 146
147 if (ctx.qry.showmsg) {
148 /* Concatenate commit message + notes in msgbuf */
149 if (info->msg && *(info->msg)) {
150 strbuf_addstr(&msgbuf, info->msg);
151 strbuf_addch(&msgbuf, '\n');
152 }
153 format_note(NULL, commit->object.sha1, &msgbuf,
154 PAGE_ENCODING,
155 NOTES_SHOW_HEADER | NOTES_INDENT);
120 strbuf_addch(&msgbuf, '\n'); 156 strbuf_addch(&msgbuf, '\n');
157 strbuf_ltrim(&msgbuf);
121 } 158 }
122 format_note(NULL, commit->object.sha1, &msgbuf, PAGE_ENCODING,
123 NOTES_SHOW_HEADER | NOTES_INDENT);
124 strbuf_addch(&msgbuf, '\n');
125 strbuf_ltrim(&msgbuf);
126 159
127 if (ctx.repo->enable_log_filecount) { 160 if (revs->graph) {
128 cols++; 161 int lines = 0;
129 if (ctx.repo->enable_log_linecount) 162
130 cols++; 163 /* Calculate graph padding */
164 if (ctx.qry.showmsg) {
165 /* Count #lines in commit message + notes */
166 const char *p = msgbuf.buf;
167 lines = 1;
168 while ((p = strchr(p, '\n'))) {
169 p++;
170 lines++;
171 }
172 }
173
174 /* Print graph padding */
175 html("<td class='commitgraph'>");
176 while (lines > 0 || !graph_is_commit_finished(revs->graph)) {
177 if (graphbuf.len)
178 html("\n");
179 strbuf_setlen(&graphbuf, 0);
180 graph_next_line(revs->graph, &graphbuf);
181 html(graphbuf.buf);
182 lines--;
183 }
184 html("</td>\n");
131 } 185 }
132 186
133 /* Create second table row containing msgbuf */ 187 /* Print msgbuf into remainder of table row */
134 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>", 188 htmlf("<td colspan='%d'%s>\n", cols,
135 cols); 189 ctx.qry.showmsg ? " class='logmsg'" : "");
136 html_txt(msgbuf.buf); 190 html_txt(msgbuf.buf);
137 html("</td></tr>\n"); 191 html("</td></tr>\n");
138 strbuf_release(&msgbuf); 192 strbuf_release(&msgbuf);
139 } 193 }
140 194
195 strbuf_release(&graphbuf);
141 cgit_free_commitinfo(info); 196 cgit_free_commitinfo(info);
142} 197}
143 198
144static const char *disambiguate_ref(const char *ref) 199static const char *disambiguate_ref(const char *ref)
145{ 200{
146 unsigned char sha1[20]; 201 unsigned char sha1[20];
147 const char *longref; 202 const char *longref;
148 203
149 longref = fmt("refs/heads/%s", ref); 204 longref = fmt("refs/heads/%s", ref);
150 if (get_sha1(longref, sha1) == 0) 205 if (get_sha1(longref, sha1) == 0)
151 return longref; 206 return longref;
152 207
153 return ref; 208 return ref;
154} 209}
155 210
156static char *next_token(char **src) 211static char *next_token(char **src)
157{ 212{
158 char *result; 213 char *result;
159 214
160 if (!src || !*src) 215 if (!src || !*src)
161 return NULL; 216 return NULL;
162 while (isspace(**src)) 217 while (isspace(**src))
163 (*src)++; 218 (*src)++;
164 if (!**src) 219 if (!**src)
@@ -195,107 +250,113 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
195 if (grep && pattern && *pattern) { 250 if (grep && pattern && *pattern) {
196 pattern = xstrdup(pattern); 251 pattern = xstrdup(pattern);
197 if (!strcmp(grep, "grep") || !strcmp(grep, "author") || 252 if (!strcmp(grep, "grep") || !strcmp(grep, "author") ||
198 !strcmp(grep, "committer")) { 253 !strcmp(grep, "committer")) {
199 arg = fmt("--%s=%s", grep, pattern); 254 arg = fmt("--%s=%s", grep, pattern);
200 vector_push(&vec, &arg, 0); 255 vector_push(&vec, &arg, 0);
201 } 256 }
202 if (!strcmp(grep, "range")) { 257 if (!strcmp(grep, "range")) {
203 /* Split the pattern at whitespace and add each token 258 /* Split the pattern at whitespace and add each token
204 * as a revision expression. Do not accept other 259 * as a revision expression. Do not accept other
205 * rev-list options. Also, replace the previously 260 * rev-list options. Also, replace the previously
206 * pushed tip (it's no longer relevant). 261 * pushed tip (it's no longer relevant).
207 */ 262 */
208 vec.count--; 263 vec.count--;
209 while ((arg = next_token(&pattern))) { 264 while ((arg = next_token(&pattern))) {
210 if (*arg == '-') { 265 if (*arg == '-') {
211 fprintf(stderr, "Bad range expr: %s\n", 266 fprintf(stderr, "Bad range expr: %s\n",
212 arg); 267 arg);
213 break; 268 break;
214 } 269 }
215 vector_push(&vec, &arg, 0); 270 vector_push(&vec, &arg, 0);
216 } 271 }
217 } 272 }
218 } 273 }
274 if (ctx.repo->enable_commit_graph) {
275 static const char *graph_arg = "--graph";
276 vector_push(&vec, &graph_arg, 0);
277 }
219 278
220 if (path) { 279 if (path) {
221 arg = "--"; 280 arg = "--";
222 vector_push(&vec, &arg, 0); 281 vector_push(&vec, &arg, 0);
223 vector_push(&vec, &path, 0); 282 vector_push(&vec, &path, 0);
224 } 283 }
225 284
226 /* Make sure the vector is NULL-terminated */ 285 /* Make sure the vector is NULL-terminated */
227 vector_push(&vec, NULL, 0); 286 vector_push(&vec, NULL, 0);
228 vec.count--; 287 vec.count--;
229 288
230 init_revisions(&rev, NULL); 289 init_revisions(&rev, NULL);
231 rev.abbrev = DEFAULT_ABBREV; 290 rev.abbrev = DEFAULT_ABBREV;
232 rev.commit_format = CMIT_FMT_DEFAULT; 291 rev.commit_format = CMIT_FMT_DEFAULT;
233 rev.verbose_header = 1; 292 rev.verbose_header = 1;
234 rev.show_root_diff = 0; 293 rev.show_root_diff = 0;
235 setup_revisions(vec.count, vec.data, &rev, NULL); 294 setup_revisions(vec.count, vec.data, &rev, NULL);
236 load_ref_decorations(DECORATE_FULL_REFS); 295 load_ref_decorations(DECORATE_FULL_REFS);
237 rev.show_decorations = 1; 296 rev.show_decorations = 1;
238 rev.grep_filter.regflags |= REG_ICASE; 297 rev.grep_filter.regflags |= REG_ICASE;
239 compile_grep_patterns(&rev.grep_filter); 298 compile_grep_patterns(&rev.grep_filter);
240 prepare_revision_walk(&rev); 299 prepare_revision_walk(&rev);
241 300
242 if (pager) 301 if (pager)
243 html("<table class='list nowrap'>"); 302 html("<table class='list nowrap'>");
244 303
245 html("<tr class='nohover'><th class='left'>Age</th>" 304 html("<tr class='nohover'><th class='left'>Age</th>");
246 "<th class='left'>Commit message"); 305 if (ctx.repo->enable_commit_graph)
306 html("<th></th>");
307 html("<th class='left'>Commit message");
247 if (pager) { 308 if (pager) {
248 html(" ("); 309 html(" (");
249 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL, 310 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL,
250 NULL, ctx.qry.head, ctx.qry.sha1, 311 NULL, ctx.qry.head, ctx.qry.sha1,
251 ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep, 312 ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep,
252 ctx.qry.search, ctx.qry.showmsg ? 0 : 1); 313 ctx.qry.search, ctx.qry.showmsg ? 0 : 1);
253 html(")"); 314 html(")");
254 } 315 }
255 html("</th><th class='left'>Author</th>"); 316 html("</th><th class='left'>Author</th>");
256 if (ctx.repo->enable_log_filecount) { 317 if (ctx.repo->enable_log_filecount) {
257 html("<th class='left'>Files</th>"); 318 html("<th class='left'>Files</th>");
258 columns++; 319 columns++;
259 if (ctx.repo->enable_log_linecount) { 320 if (ctx.repo->enable_log_linecount) {
260 html("<th class='left'>Lines</th>"); 321 html("<th class='left'>Lines</th>");
261 columns++; 322 columns++;
262 } 323 }
263 } 324 }
264 html("</tr>\n"); 325 html("</tr>\n");
265 326
266 if (ofs<0) 327 if (ofs<0)
267 ofs = 0; 328 ofs = 0;
268 329
269 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) { 330 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) {
270 free(commit->buffer); 331 free(commit->buffer);
271 commit->buffer = NULL; 332 commit->buffer = NULL;
272 free_commit_list(commit->parents); 333 free_commit_list(commit->parents);
273 commit->parents = NULL; 334 commit->parents = NULL;
274 } 335 }
275 336
276 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) { 337 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) {
277 print_commit(commit); 338 print_commit(commit, &rev);
278 free(commit->buffer); 339 free(commit->buffer);
279 commit->buffer = NULL; 340 commit->buffer = NULL;
280 free_commit_list(commit->parents); 341 free_commit_list(commit->parents);
281 commit->parents = NULL; 342 commit->parents = NULL;
282 } 343 }
283 if (pager) { 344 if (pager) {
284 html("</table><div class='pager'>"); 345 html("</table><div class='pager'>");
285 if (ofs > 0) { 346 if (ofs > 0) {
286 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head, 347 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head,
287 ctx.qry.sha1, ctx.qry.vpath, 348 ctx.qry.sha1, ctx.qry.vpath,
288 ofs - cnt, ctx.qry.grep, 349 ofs - cnt, ctx.qry.grep,
289 ctx.qry.search, ctx.qry.showmsg); 350 ctx.qry.search, ctx.qry.showmsg);
290 html("&nbsp;"); 351 html("&nbsp;");
291 } 352 }
292 if ((commit = get_revision(&rev)) != NULL) { 353 if ((commit = get_revision(&rev)) != NULL) {
293 cgit_log_link("[next]", NULL, NULL, ctx.qry.head, 354 cgit_log_link("[next]", NULL, NULL, ctx.qry.head,
294 ctx.qry.sha1, ctx.qry.vpath, 355 ctx.qry.sha1, ctx.qry.vpath,
295 ofs + cnt, ctx.qry.grep, 356 ofs + cnt, ctx.qry.grep,
296 ctx.qry.search, ctx.qry.showmsg); 357 ctx.qry.search, ctx.qry.showmsg);
297 } 358 }
298 html("</div>"); 359 html("</div>");
299 } else if ((commit = get_revision(&rev)) != NULL) { 360 } else if ((commit = get_revision(&rev)) != NULL) {
300 html("<tr class='nohover'><td colspan='3'>"); 361 html("<tr class='nohover'><td colspan='3'>");
301 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, 362 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL,