aboutsummaryrefslogtreecommitdiffstats
path: root/ui-log.c
diff options
context:
space:
mode:
Diffstat (limited to 'ui-log.c')
-rw-r--r--ui-log.c287
1 files changed, 231 insertions, 56 deletions
diff --git a/ui-log.c b/ui-log.c
index b9771fa..2e6e9d6 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -9,9 +9,25 @@
9#include "cgit.h" 9#include "cgit.h"
10#include "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12#include "vector.h"
12 13
13int files, add_lines, rem_lines; 14int files, add_lines, rem_lines;
14 15
16/*
17 * The list of available column colors in the commit graph.
18 */
19static const char *column_colors_html[] = {
20 "<span class='column1'>",
21 "<span class='column2'>",
22 "<span class='column3'>",
23 "<span class='column4'>",
24 "<span class='column5'>",
25 "<span class='column6'>",
26 "</span>",
27};
28
29#define COLUMN_COLORS_HTML_MAX (ARRAY_SIZE(column_colors_html) - 1)
30
15void count_lines(char *line, int size) 31void count_lines(char *line, int size)
16{ 32{
17 if (size <= 0) 33 if (size <= 0)
@@ -76,63 +92,160 @@ void show_commit_decorations(struct commit *commit)
76 } 92 }
77} 93}
78 94
79void print_commit(struct commit *commit) 95void print_commit(struct commit *commit, struct rev_info *revs)
80{ 96{
81 struct commitinfo *info; 97 struct commitinfo *info;
82 char *tmp; 98 char *tmp;
83 int cols = 2; 99 int cols = revs->graph ? 3 : 2;
100 struct strbuf graphbuf = STRBUF_INIT;
101 struct strbuf msgbuf = STRBUF_INIT;
102
103 if (ctx.repo->enable_log_filecount)
104 cols++;
105 if (ctx.repo->enable_log_linecount)
106 cols++;
107
108 if (revs->graph) {
109 /* Advance graph until current commit */
110 while (!graph_next_line(revs->graph, &graphbuf)) {
111 /* Print graph segment in otherwise empty table row */
112 html("<tr class='nohover'><td class='commitgraph'>");
113 html(graphbuf.buf);
114 htmlf("</td><td colspan='%d' /></tr>\n", cols);
115 strbuf_setlen(&graphbuf, 0);
116 }
117 /* Current commit's graph segment is now ready in graphbuf */
118 }
84 119
85 info = cgit_parse_commit(commit); 120 info = cgit_parse_commit(commit);
86 htmlf("<tr%s><td>", 121 htmlf("<tr%s>", ctx.qry.showmsg ? " class='logheader'" : "");
87 ctx.qry.showmsg ? " class='logheader'" : ""); 122
88 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); 123 if (revs->graph) {
89 tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp); 124 /* Print graph segment for current commit */
90 html_link_open(tmp, NULL, NULL); 125 html("<td class='commitgraph'>");
91 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); 126 html(graphbuf.buf);
92 html_link_close(); 127 html("</td>");
93 htmlf("</td><td%s>", 128 strbuf_setlen(&graphbuf, 0);
94 ctx.qry.showmsg ? " class='logsubject'" : ""); 129 }
130 else {
131 html("<td>");
132 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
133 tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp);
134 html_link_open(tmp, NULL, NULL);
135 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
136 html_link_close();
137 html("</td>");
138 }
139
140 htmlf("<td%s>", ctx.qry.showmsg ? " class='logsubject'" : "");
141 if (ctx.qry.showmsg) {
142 /* line-wrap long commit subjects instead of truncating them */
143 size_t subject_len = strlen(info->subject);
144
145 if (subject_len > ctx.cfg.max_msg_len &&
146 ctx.cfg.max_msg_len >= 15) {
147 /* symbol for signaling line-wrap (in PAGE_ENCODING) */
148 const char wrap_symbol[] = { ' ', 0xE2, 0x86, 0xB5, 0 };
149 int i = ctx.cfg.max_msg_len - strlen(wrap_symbol);
150
151 /* Rewind i to preceding space character */
152 while (i > 0 && !isspace(info->subject[i]))
153 --i;
154 if (!i) /* Oops, zero spaces. Reset i */
155 i = ctx.cfg.max_msg_len - strlen(wrap_symbol);
156
157 /* add remainder starting at i to msgbuf */
158 strbuf_add(&msgbuf, info->subject + i, subject_len - i);
159 strbuf_trim(&msgbuf);
160 strbuf_add(&msgbuf, "\n\n", 2);
161
162 /* Place wrap_symbol at position i in info->subject */
163 strcpy(info->subject + i, wrap_symbol);
164 }
165 }
95 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, 166 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
96 sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0); 167 sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0);
97 show_commit_decorations(commit); 168 show_commit_decorations(commit);
98 html("</td><td>"); 169 html("</td><td>");
99 html_txt(info->author); 170 html_txt(info->author);
100 if (ctx.repo->enable_log_filecount) { 171
172 if (revs->graph) {
173 html("</td><td>");
174 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
175 tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp);
176 html_link_open(tmp, NULL, NULL);
177 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
178 html_link_close();
179 }
180
181 if (ctx.repo->enable_log_filecount || ctx.repo->enable_log_linecount) {
101 files = 0; 182 files = 0;
102 add_lines = 0; 183 add_lines = 0;
103 rem_lines = 0; 184 rem_lines = 0;
104 cgit_diff_commit(commit, inspect_files, ctx.qry.vpath); 185 cgit_diff_commit(commit, inspect_files, ctx.qry.vpath);
105 html("</td><td>");
106 htmlf("%d", files);
107 if (ctx.repo->enable_log_linecount) {
108 html("</td><td>");
109 htmlf("-%d/+%d", rem_lines, add_lines);
110 }
111 } 186 }
187
188 if (ctx.repo->enable_log_filecount)
189 htmlf("</td><td>%d", files);
190 if (ctx.repo->enable_log_linecount)
191 htmlf("</td><td>-%d/+%d", rem_lines, add_lines);
192
112 html("</td></tr>\n"); 193 html("</td></tr>\n");
113 if (ctx.qry.showmsg) {
114 struct strbuf notes = STRBUF_INIT;
115 format_note(NULL, commit->object.sha1, &notes, PAGE_ENCODING, 0);
116 194
117 if (ctx.repo->enable_log_filecount) { 195 if (revs->graph || ctx.qry.showmsg) { /* Print a second table row */
118 cols++; 196 html("<tr class='nohover'>");
119 if (ctx.repo->enable_log_linecount) 197
120 cols++; 198 if (ctx.qry.showmsg) {
199 /* Concatenate commit message + notes in msgbuf */
200 if (info->msg && *(info->msg)) {
201 strbuf_addstr(&msgbuf, info->msg);
202 strbuf_addch(&msgbuf, '\n');
203 }
204 format_note(NULL, commit->object.sha1, &msgbuf,
205 PAGE_ENCODING,
206 NOTES_SHOW_HEADER | NOTES_INDENT);
207 strbuf_addch(&msgbuf, '\n');
208 strbuf_ltrim(&msgbuf);
121 } 209 }
122 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>", 210
123 cols); 211 if (revs->graph) {
124 html_txt(info->msg); 212 int lines = 0;
125 html("</td></tr>\n"); 213
126 if (notes.len != 0) { 214 /* Calculate graph padding */
127 html("<tr class='nohover'>"); 215 if (ctx.qry.showmsg) {
128 html("<td class='lognotes-label'>Notes:</td>"); 216 /* Count #lines in commit message + notes */
129 htmlf("<td colspan='%d' class='lognotes'>", 217 const char *p = msgbuf.buf;
130 cols); 218 lines = 1;
131 html_txt(notes.buf); 219 while ((p = strchr(p, '\n'))) {
132 html("</td></tr>\n"); 220 p++;
221 lines++;
222 }
223 }
224
225 /* Print graph padding */
226 html("<td class='commitgraph'>");
227 while (lines > 0 || !graph_is_commit_finished(revs->graph)) {
228 if (graphbuf.len)
229 html("\n");
230 strbuf_setlen(&graphbuf, 0);
231 graph_next_line(revs->graph, &graphbuf);
232 html(graphbuf.buf);
233 lines--;
234 }
235 html("</td>\n");
133 } 236 }
134 strbuf_release(&notes); 237 else
238 html("<td/>"); /* Empty 'Age' column */
239
240 /* Print msgbuf into remainder of table row */
241 htmlf("<td colspan='%d'%s>\n", cols,
242 ctx.qry.showmsg ? " class='logmsg'" : "");
243 html_txt(msgbuf.buf);
244 html("</td></tr>\n");
135 } 245 }
246
247 strbuf_release(&msgbuf);
248 strbuf_release(&graphbuf);
136 cgit_free_commitinfo(info); 249 cgit_free_commitinfo(info);
137} 250}
138 251
@@ -148,38 +261,94 @@ static const char *disambiguate_ref(const char *ref)
148 return ref; 261 return ref;
149} 262}
150 263
264static char *next_token(char **src)
265{
266 char *result;
267
268 if (!src || !*src)
269 return NULL;
270 while (isspace(**src))
271 (*src)++;
272 if (!**src)
273 return NULL;
274 result = *src;
275 while (**src) {
276 if (isspace(**src)) {
277 **src = '\0';
278 (*src)++;
279 break;
280 }
281 (*src)++;
282 }
283 return result;
284}
285
151void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern, 286void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern,
152 char *path, int pager) 287 char *path, int pager, int commit_graph)
153{ 288{
154 struct rev_info rev; 289 struct rev_info rev;
155 struct commit *commit; 290 struct commit *commit;
156 const char *argv[] = {NULL, NULL, NULL, NULL, NULL}; 291 struct vector vec = VECTOR_INIT(char *);
157 int argc = 2;
158 int i, columns = 3; 292 int i, columns = 3;
293 char *arg;
294
295 /* First argv is NULL */
296 vector_push(&vec, NULL, 0);
159 297
160 if (!tip) 298 if (!tip)
161 tip = ctx.qry.head; 299 tip = ctx.qry.head;
162 300 tip = disambiguate_ref(tip);
163 argv[1] = disambiguate_ref(tip); 301 vector_push(&vec, &tip, 0);
164 302
165 if (grep && pattern && *pattern) { 303 if (grep && pattern && *pattern) {
304 pattern = xstrdup(pattern);
166 if (!strcmp(grep, "grep") || !strcmp(grep, "author") || 305 if (!strcmp(grep, "grep") || !strcmp(grep, "author") ||
167 !strcmp(grep, "committer")) 306 !strcmp(grep, "committer")) {
168 argv[argc++] = fmt("--%s=%s", grep, pattern); 307 arg = fmt("--%s=%s", grep, pattern);
169 if (!strcmp(grep, "range")) 308 vector_push(&vec, &arg, 0);
170 argv[1] = pattern; 309 }
310 if (!strcmp(grep, "range")) {
311 /* Split the pattern at whitespace and add each token
312 * as a revision expression. Do not accept other
313 * rev-list options. Also, replace the previously
314 * pushed tip (it's no longer relevant).
315 */
316 vec.count--;
317 while ((arg = next_token(&pattern))) {
318 if (*arg == '-') {
319 fprintf(stderr, "Bad range expr: %s\n",
320 arg);
321 break;
322 }
323 vector_push(&vec, &arg, 0);
324 }
325 }
326 }
327 if (commit_graph) {
328 static const char *graph_arg = "--graph";
329 static const char *color_arg = "--color";
330 vector_push(&vec, &graph_arg, 0);
331 vector_push(&vec, &color_arg, 0);
332 graph_set_column_colors(column_colors_html,
333 COLUMN_COLORS_HTML_MAX);
171 } 334 }
172 335
173 if (path) { 336 if (path) {
174 argv[argc++] = "--"; 337 arg = "--";
175 argv[argc++] = path; 338 vector_push(&vec, &arg, 0);
339 vector_push(&vec, &path, 0);
176 } 340 }
341
342 /* Make sure the vector is NULL-terminated */
343 vector_push(&vec, NULL, 0);
344 vec.count--;
345
177 init_revisions(&rev, NULL); 346 init_revisions(&rev, NULL);
178 rev.abbrev = DEFAULT_ABBREV; 347 rev.abbrev = DEFAULT_ABBREV;
179 rev.commit_format = CMIT_FMT_DEFAULT; 348 rev.commit_format = CMIT_FMT_DEFAULT;
180 rev.verbose_header = 1; 349 rev.verbose_header = 1;
181 rev.show_root_diff = 0; 350 rev.show_root_diff = 0;
182 setup_revisions(argc, argv, &rev, NULL); 351 setup_revisions(vec.count, vec.data, &rev, NULL);
183 load_ref_decorations(DECORATE_FULL_REFS); 352 load_ref_decorations(DECORATE_FULL_REFS);
184 rev.show_decorations = 1; 353 rev.show_decorations = 1;
185 rev.grep_filter.regflags |= REG_ICASE; 354 rev.grep_filter.regflags |= REG_ICASE;
@@ -189,8 +358,12 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
189 if (pager) 358 if (pager)
190 html("<table class='list nowrap'>"); 359 html("<table class='list nowrap'>");
191 360
192 html("<tr class='nohover'><th class='left'>Age</th>" 361 html("<tr class='nohover'>");
193 "<th class='left'>Commit message"); 362 if (commit_graph)
363 html("<th></th>");
364 else
365 html("<th class='left'>Age</th>");
366 html("<th class='left'>Commit message");
194 if (pager) { 367 if (pager) {
195 html(" ("); 368 html(" (");
196 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL, 369 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL,
@@ -200,13 +373,15 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
200 html(")"); 373 html(")");
201 } 374 }
202 html("</th><th class='left'>Author</th>"); 375 html("</th><th class='left'>Author</th>");
376 if (commit_graph)
377 html("<th class='left'>Age</th>");
203 if (ctx.repo->enable_log_filecount) { 378 if (ctx.repo->enable_log_filecount) {
204 html("<th class='left'>Files</th>"); 379 html("<th class='left'>Files</th>");
205 columns++; 380 columns++;
206 if (ctx.repo->enable_log_linecount) { 381 }
207 html("<th class='left'>Lines</th>"); 382 if (ctx.repo->enable_log_linecount) {
208 columns++; 383 html("<th class='left'>Lines</th>");
209 } 384 columns++;
210 } 385 }
211 html("</tr>\n"); 386 html("</tr>\n");
212 387
@@ -221,7 +396,7 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
221 } 396 }
222 397
223 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) { 398 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) {
224 print_commit(commit); 399 print_commit(commit, &rev);
225 free(commit->buffer); 400 free(commit->buffer);
226 commit->buffer = NULL; 401 commit->buffer = NULL;
227 free_commit_list(commit->parents); 402 free_commit_list(commit->parents);