]> gitweb.ps.run Git - ps-cgit/blob - ui-log.c
Do not load user or system gitconfig and gitattributes
[ps-cgit] / ui-log.c
1 /* ui-log.c: functions for log output
2  *
3  * Copyright (C) 2006 Lars Hjemli
4  *
5  * Licensed under GNU General Public License v2
6  *   (see COPYING for full license text)
7  */
8
9 #include "cgit.h"
10 #include "ui-log.h"
11 #include "html.h"
12 #include "ui-shared.h"
13 #include "vector.h"
14
15 int files, add_lines, rem_lines;
16
17 /*
18  * The list of available column colors in the commit graph.
19  */
20 static const char *column_colors_html[] = {
21         "<span class='column1'>",
22         "<span class='column2'>",
23         "<span class='column3'>",
24         "<span class='column4'>",
25         "<span class='column5'>",
26         "<span class='column6'>",
27         "</span>",
28 };
29
30 #define COLUMN_COLORS_HTML_MAX (ARRAY_SIZE(column_colors_html) - 1)
31
32 static void count_lines(char *line, int size)
33 {
34         if (size <= 0)
35                 return;
36
37         if (line[0] == '+')
38                 add_lines++;
39
40         else if (line[0] == '-')
41                 rem_lines++;
42 }
43
44 static void inspect_files(struct diff_filepair *pair)
45 {
46         unsigned long old_size = 0;
47         unsigned long new_size = 0;
48         int binary = 0;
49
50         files++;
51         if (ctx.repo->enable_log_linecount)
52                 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
53                                 &new_size, &binary, 0, ctx.qry.ignorews,
54                                 count_lines);
55 }
56
57 void show_commit_decorations(struct commit *commit)
58 {
59         struct name_decoration *deco;
60         static char buf[1024];
61
62         buf[sizeof(buf) - 1] = 0;
63         deco = lookup_decoration(&name_decoration, &commit->object);
64         while (deco) {
65                 if (!prefixcmp(deco->name, "refs/heads/")) {
66                         strncpy(buf, deco->name + 11, sizeof(buf) - 1);
67                         cgit_log_link(buf, NULL, "branch-deco", buf, NULL,
68                                       ctx.qry.vpath, 0, NULL, NULL,
69                                       ctx.qry.showmsg);
70                 }
71                 else if (!prefixcmp(deco->name, "tag: refs/tags/")) {
72                         strncpy(buf, deco->name + 15, sizeof(buf) - 1);
73                         cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
74                 }
75                 else if (!prefixcmp(deco->name, "refs/tags/")) {
76                         strncpy(buf, deco->name + 10, sizeof(buf) - 1);
77                         cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
78                 }
79                 else if (!prefixcmp(deco->name, "refs/remotes/")) {
80                         if (!ctx.repo->enable_remote_branches)
81                                 goto next;
82                         strncpy(buf, deco->name + 13, sizeof(buf) - 1);
83                         cgit_log_link(buf, NULL, "remote-deco", NULL,
84                                       sha1_to_hex(commit->object.sha1),
85                                       ctx.qry.vpath, 0, NULL, NULL,
86                                       ctx.qry.showmsg);
87                 }
88                 else {
89                         strncpy(buf, deco->name, sizeof(buf) - 1);
90                         cgit_commit_link(buf, NULL, "deco", ctx.qry.head,
91                                          sha1_to_hex(commit->object.sha1),
92                                          ctx.qry.vpath, 0);
93                 }
94 next:
95                 deco = deco->next;
96         }
97 }
98
99 static void print_commit(struct commit *commit, struct rev_info *revs)
100 {
101         struct commitinfo *info;
102         int columns = revs->graph ? 4 : 3;
103         struct strbuf graphbuf = STRBUF_INIT;
104         struct strbuf msgbuf = STRBUF_INIT;
105
106         if (ctx.repo->enable_log_filecount)
107                 columns++;
108         if (ctx.repo->enable_log_linecount)
109                 columns++;
110
111         if (revs->graph) {
112                 /* Advance graph until current commit */
113                 while (!graph_next_line(revs->graph, &graphbuf)) {
114                         /* Print graph segment in otherwise empty table row */
115                         html("<tr class='nohover'><td class='commitgraph'>");
116                         html(graphbuf.buf);
117                         htmlf("</td><td colspan='%d' /></tr>\n", columns);
118                         strbuf_setlen(&graphbuf, 0);
119                 }
120                 /* Current commit's graph segment is now ready in graphbuf */
121         }
122
123         info = cgit_parse_commit(commit);
124         htmlf("<tr%s>", ctx.qry.showmsg ? " class='logheader'" : "");
125
126         if (revs->graph) {
127                 /* Print graph segment for current commit */
128                 html("<td class='commitgraph'>");
129                 html(graphbuf.buf);
130                 html("</td>");
131                 strbuf_setlen(&graphbuf, 0);
132         }
133         else {
134                 html("<td>");
135                 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
136                 html("</td>");
137         }
138
139         htmlf("<td%s>", ctx.qry.showmsg ? " class='logsubject'" : "");
140         if (ctx.qry.showmsg) {
141                 /* line-wrap long commit subjects instead of truncating them */
142                 size_t subject_len = strlen(info->subject);
143
144                 if (subject_len > ctx.cfg.max_msg_len &&
145                     ctx.cfg.max_msg_len >= 15) {
146                         /* symbol for signaling line-wrap (in PAGE_ENCODING) */
147                         const char wrap_symbol[] = { ' ', 0xE2, 0x86, 0xB5, 0 };
148                         int i = ctx.cfg.max_msg_len - strlen(wrap_symbol);
149
150                         /* Rewind i to preceding space character */
151                         while (i > 0 && !isspace(info->subject[i]))
152                                 --i;
153                         if (!i) /* Oops, zero spaces. Reset i */
154                                 i = ctx.cfg.max_msg_len - strlen(wrap_symbol);
155
156                         /* add remainder starting at i to msgbuf */
157                         strbuf_add(&msgbuf, info->subject + i, subject_len - i);
158                         strbuf_trim(&msgbuf);
159                         strbuf_add(&msgbuf, "\n\n", 2);
160
161                         /* Place wrap_symbol at position i in info->subject */
162                         strcpy(info->subject + i, wrap_symbol);
163                 }
164         }
165         cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
166                          sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0);
167         show_commit_decorations(commit);
168         html("</td><td>");
169         html_txt(info->author);
170
171         if (revs->graph) {
172                 html("</td><td>");
173                 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
174         }
175
176         if (ctx.repo->enable_log_filecount || ctx.repo->enable_log_linecount) {
177                 files = 0;
178                 add_lines = 0;
179                 rem_lines = 0;
180                 cgit_diff_commit(commit, inspect_files, ctx.qry.vpath);
181         }
182
183         if (ctx.repo->enable_log_filecount)
184                 htmlf("</td><td>%d", files);
185         if (ctx.repo->enable_log_linecount)
186                 htmlf("</td><td>-%d/+%d", rem_lines, add_lines);
187
188         html("</td></tr>\n");
189
190         if (revs->graph || ctx.qry.showmsg) { /* Print a second table row */
191                 html("<tr class='nohover'>");
192
193                 if (ctx.qry.showmsg) {
194                         /* Concatenate commit message + notes in msgbuf */
195                         if (info->msg && *(info->msg)) {
196                                 strbuf_addstr(&msgbuf, info->msg);
197                                 strbuf_addch(&msgbuf, '\n');
198                         }
199                         format_display_notes(commit->object.sha1,
200                                              &msgbuf, PAGE_ENCODING, 0);
201                         strbuf_addch(&msgbuf, '\n');
202                         strbuf_ltrim(&msgbuf);
203                 }
204
205                 if (revs->graph) {
206                         int lines = 0;
207
208                         /* Calculate graph padding */
209                         if (ctx.qry.showmsg) {
210                                 /* Count #lines in commit message + notes */
211                                 const char *p = msgbuf.buf;
212                                 lines = 1;
213                                 while ((p = strchr(p, '\n'))) {
214                                         p++;
215                                         lines++;
216                                 }
217                         }
218
219                         /* Print graph padding */
220                         html("<td class='commitgraph'>");
221                         while (lines > 0 || !graph_is_commit_finished(revs->graph)) {
222                                 if (graphbuf.len)
223                                         html("\n");
224                                 strbuf_setlen(&graphbuf, 0);
225                                 graph_next_line(revs->graph, &graphbuf);
226                                 html(graphbuf.buf);
227                                 lines--;
228                         }
229                         html("</td>\n");
230                 }
231                 else
232                         html("<td/>"); /* Empty 'Age' column */
233
234                 /* Print msgbuf into remainder of table row */
235                 htmlf("<td colspan='%d'%s>\n", columns - (revs->graph ? 1 : 0),
236                         ctx.qry.showmsg ? " class='logmsg'" : "");
237                 html_txt(msgbuf.buf);
238                 html("</td></tr>\n");
239         }
240
241         strbuf_release(&msgbuf);
242         strbuf_release(&graphbuf);
243         cgit_free_commitinfo(info);
244 }
245
246 static const char *disambiguate_ref(const char *ref, int *must_free_result)
247 {
248         unsigned char sha1[20];
249         struct strbuf longref = STRBUF_INIT;
250
251         strbuf_addf(&longref, "refs/heads/%s", ref);
252         if (get_sha1(longref.buf, sha1) == 0) {
253                 *must_free_result = 1;
254                 return strbuf_detach(&longref, NULL);
255         }
256
257         *must_free_result = 0;
258         strbuf_release(&longref);
259         return ref;
260 }
261
262 static char *next_token(char **src)
263 {
264         char *result;
265
266         if (!src || !*src)
267                 return NULL;
268         while (isspace(**src))
269                 (*src)++;
270         if (!**src)
271                 return NULL;
272         result = *src;
273         while (**src) {
274                 if (isspace(**src)) {
275                         **src = '\0';
276                         (*src)++;
277                         break;
278                 }
279                 (*src)++;
280         }
281         return result;
282 }
283
284 void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern,
285                     char *path, int pager, int commit_graph, int commit_sort)
286 {
287         struct rev_info rev;
288         struct commit *commit;
289         struct vector vec = VECTOR_INIT(char *);
290         int i, columns = commit_graph ? 4 : 3;
291         int must_free_tip = 0;
292         struct strbuf argbuf = STRBUF_INIT;
293
294         /* First argv is NULL */
295         vector_push(&vec, NULL, 0);
296
297         if (!tip)
298                 tip = ctx.qry.head;
299         tip = disambiguate_ref(tip, &must_free_tip);
300         vector_push(&vec, &tip, 0);
301
302         if (grep && pattern && *pattern) {
303                 pattern = xstrdup(pattern);
304                 if (!strcmp(grep, "grep") || !strcmp(grep, "author") ||
305                     !strcmp(grep, "committer")) {
306                         strbuf_addf(&argbuf, "--%s=%s", grep, pattern);
307                         vector_push(&vec, &argbuf.buf, 0);
308                 }
309                 if (!strcmp(grep, "range")) {
310                         char *arg;
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);
334         }
335
336         if (commit_sort == 1) {
337                 static const char *date_order_arg = "--date-order";
338                 vector_push(&vec, &date_order_arg, 0);
339         } else if (commit_sort == 2) {
340                 static const char *topo_order_arg = "--topo-order";
341                 vector_push(&vec, &topo_order_arg, 0);
342         }
343
344         if (path) {
345                 static const char *double_dash_arg = "--";
346                 vector_push(&vec, &double_dash_arg, 0);
347                 vector_push(&vec, &path, 0);
348         }
349
350         /* Make sure the vector is NULL-terminated */
351         vector_push(&vec, NULL, 0);
352         vec.count--;
353
354         init_revisions(&rev, NULL);
355         rev.abbrev = DEFAULT_ABBREV;
356         rev.commit_format = CMIT_FMT_DEFAULT;
357         rev.verbose_header = 1;
358         rev.show_root_diff = 0;
359         setup_revisions(vec.count, vec.data, &rev, NULL);
360         load_ref_decorations(DECORATE_FULL_REFS);
361         rev.show_decorations = 1;
362         rev.grep_filter.regflags |= REG_ICASE;
363         compile_grep_patterns(&rev.grep_filter);
364         prepare_revision_walk(&rev);
365
366         if (pager)
367                 html("<table class='list nowrap'>");
368
369         html("<tr class='nohover'>");
370         if (commit_graph)
371                 html("<th></th>");
372         else
373                 html("<th class='left'>Age</th>");
374         html("<th class='left'>Commit message");
375         if (pager) {
376                 html(" (");
377                 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL,
378                               NULL, ctx.qry.head, ctx.qry.sha1,
379                               ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep,
380                               ctx.qry.search, ctx.qry.showmsg ? 0 : 1);
381                 html(")");
382         }
383         html("</th><th class='left'>Author</th>");
384         if (commit_graph)
385                 html("<th class='left'>Age</th>");
386         if (ctx.repo->enable_log_filecount) {
387                 html("<th class='left'>Files</th>");
388                 columns++;
389         }
390         if (ctx.repo->enable_log_linecount) {
391                 html("<th class='left'>Lines</th>");
392                 columns++;
393         }
394         html("</tr>\n");
395
396         if (ofs<0)
397                 ofs = 0;
398
399         for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) {
400                 free(commit->buffer);
401                 commit->buffer = NULL;
402                 free_commit_list(commit->parents);
403                 commit->parents = NULL;
404         }
405
406         for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) {
407                 print_commit(commit, &rev);
408                 free(commit->buffer);
409                 commit->buffer = NULL;
410                 free_commit_list(commit->parents);
411                 commit->parents = NULL;
412         }
413         if (pager) {
414                 html("</table><ul class='pager'>");
415                 if (ofs > 0) {
416                         html("<li>");
417                         cgit_log_link("[prev]", NULL, NULL, ctx.qry.head,
418                                       ctx.qry.sha1, ctx.qry.vpath,
419                                       ofs - cnt, ctx.qry.grep,
420                                       ctx.qry.search, ctx.qry.showmsg);
421                         html("</li>");
422                 }
423                 if ((commit = get_revision(&rev)) != NULL) {
424                         html("<li>");
425                         cgit_log_link("[next]", NULL, NULL, ctx.qry.head,
426                                       ctx.qry.sha1, ctx.qry.vpath,
427                                       ofs + cnt, ctx.qry.grep,
428                                       ctx.qry.search, ctx.qry.showmsg);
429                         html("</li>");
430                 }
431                 html("</ul>");
432         } else if ((commit = get_revision(&rev)) != NULL) {
433                 htmlf("<tr class='nohover'><td colspan='%d'>", columns);
434                 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL,
435                               ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg);
436                 html("</td></tr>\n");
437         }
438
439         /* If we allocated tip then it is safe to cast away const. */
440         if (must_free_tip)
441                 free((char*) tip);
442         strbuf_release(&argbuf);
443 }