]> gitweb.ps.run Git - ps-cgit/blob - ui-blame.c
global: use release_commit_memory()
[ps-cgit] / ui-blame.c
1 /* ui-blame.c: functions for blame output
2  *
3  * Copyright (C) 2006-2017 cgit Development Team <cgit@lists.zx2c4.com>
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-blame.h"
11 #include "html.h"
12 #include "ui-shared.h"
13 #include "strvec.h"
14 #include "blame.h"
15
16
17 static char *emit_suspect_detail(struct blame_origin *suspect)
18 {
19         struct commitinfo *info;
20         struct strbuf detail = STRBUF_INIT;
21
22         info = cgit_parse_commit(suspect->commit);
23
24         strbuf_addf(&detail, "author  %s", info->author);
25         if (!ctx.cfg.noplainemail)
26                 strbuf_addf(&detail, " %s", info->author_email);
27         strbuf_addf(&detail, "  %s\n",
28                     show_date(info->author_date, info->author_tz,
29                                     cgit_date_mode(DATE_ISO8601)));
30
31         strbuf_addf(&detail, "committer  %s", info->committer);
32         if (!ctx.cfg.noplainemail)
33                 strbuf_addf(&detail, " %s", info->committer_email);
34         strbuf_addf(&detail, "  %s\n\n",
35                     show_date(info->committer_date, info->committer_tz,
36                                     cgit_date_mode(DATE_ISO8601)));
37
38         strbuf_addstr(&detail, info->subject);
39
40         cgit_free_commitinfo(info);
41         return strbuf_detach(&detail, NULL);
42 }
43
44 static void emit_blame_entry_hash(struct blame_entry *ent)
45 {
46         struct blame_origin *suspect = ent->suspect;
47         struct object_id *oid = &suspect->commit->object.oid;
48         unsigned long line = 0;
49
50         char *detail = emit_suspect_detail(suspect);
51         html("<span class='oid'>");
52         cgit_commit_link(find_unique_abbrev(oid, DEFAULT_ABBREV), detail,
53                          NULL, ctx.qry.head, oid_to_hex(oid), suspect->path);
54         html("</span>");
55         free(detail);
56
57         if (!parse_commit(suspect->commit) && suspect->commit->parents) {
58                 struct commit *parent = suspect->commit->parents->item;
59
60                 html(" ");
61                 cgit_blame_link("^", "Blame the previous revision", NULL,
62                                 ctx.qry.head, oid_to_hex(&parent->object.oid),
63                                 suspect->path);
64         }
65
66         while (line++ < ent->num_lines)
67                 html("\n");
68 }
69
70 static void emit_blame_entry_linenumber(struct blame_entry *ent)
71 {
72         const char *numberfmt = "<a id='n%1$d' href='#n%1$d'>%1$d</a>\n";
73
74         unsigned long lineno = ent->lno;
75         while (lineno < ent->lno + ent->num_lines)
76                 htmlf(numberfmt, ++lineno);
77 }
78
79 static void emit_blame_entry_line_background(struct blame_scoreboard *sb,
80                                              struct blame_entry *ent)
81 {
82         unsigned long line;
83         size_t len, maxlen = 2;
84         const char* pos, *endpos;
85
86         for (line = ent->lno; line < ent->lno + ent->num_lines; line++) {
87                 html("\n");
88                 pos = blame_nth_line(sb, line);
89                 endpos = blame_nth_line(sb, line + 1);
90                 len = 0;
91                 while (pos < endpos) {
92                         len++;
93                         if (*pos++ == '\t')
94                                 len = (len + 7) & ~7;
95                 }
96                 if (len > maxlen)
97                         maxlen = len;
98         }
99
100         for (len = 0; len < maxlen - 1; len++)
101                 html(" ");
102 }
103
104 struct walk_tree_context {
105         char *curr_rev;
106         int match_baselen;
107         int state;
108 };
109
110 static void print_object(const struct object_id *oid, const char *path,
111                          const char *basename, const char *rev)
112 {
113         enum object_type type;
114         char *buf;
115         unsigned long size;
116         struct strvec rev_argv = STRVEC_INIT;
117         struct rev_info revs;
118         struct blame_scoreboard sb;
119         struct blame_origin *o;
120         struct blame_entry *ent = NULL;
121
122         type = oid_object_info(the_repository, oid, &size);
123         if (type == OBJ_BAD) {
124                 cgit_print_error_page(404, "Not found", "Bad object name: %s",
125                                       oid_to_hex(oid));
126                 return;
127         }
128
129         buf = read_object_file(oid, &type, &size);
130         if (!buf) {
131                 cgit_print_error_page(500, "Internal server error",
132                         "Error reading object %s", oid_to_hex(oid));
133                 return;
134         }
135
136         strvec_push(&rev_argv, "blame");
137         strvec_push(&rev_argv, rev);
138         init_revisions(&revs, NULL);
139         revs.diffopt.flags.allow_textconv = 1;
140         setup_revisions(rev_argv.nr, rev_argv.v, &revs, NULL);
141         init_scoreboard(&sb);
142         sb.revs = &revs;
143         sb.repo = the_repository;
144         sb.path = path;
145         setup_scoreboard(&sb, &o);
146         o->suspects = blame_entry_prepend(NULL, 0, sb.num_lines, o);
147         prio_queue_put(&sb.commits, o->commit);
148         blame_origin_decref(o);
149         sb.ent = NULL;
150         sb.path = path;
151         assign_blame(&sb, 0);
152         blame_sort_final(&sb);
153         blame_coalesce(&sb);
154
155         cgit_set_title_from_path(path);
156
157         cgit_print_layout_start();
158         htmlf("blob: %s (", oid_to_hex(oid));
159         cgit_plain_link("plain", NULL, NULL, ctx.qry.head, rev, path);
160         html(") (");
161         cgit_tree_link("tree", NULL, NULL, ctx.qry.head, rev, path);
162         html(")\n");
163
164         if (buffer_is_binary(buf, size)) {
165                 html("<div class='error'>blob is binary.</div>");
166                 goto cleanup;
167         }
168         if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) {
169                 htmlf("<div class='error'>blob size (%ldKB)"
170                       " exceeds display size limit (%dKB).</div>",
171                       size / 1024, ctx.cfg.max_blob_size);
172                 goto cleanup;
173         }
174
175         html("<table class='blame blob'>\n<tr>\n");
176
177         /* Commit hashes */
178         html("<td class='hashes'>");
179         for (ent = sb.ent; ent; ent = ent->next) {
180                 html("<div class='alt'><pre>");
181                 emit_blame_entry_hash(ent);
182                 html("</pre></div>");
183         }
184         html("</td>\n");
185
186         /* Line numbers */
187         if (ctx.cfg.enable_tree_linenumbers) {
188                 html("<td class='linenumbers'>");
189                 for (ent = sb.ent; ent; ent = ent->next) {
190                         html("<div class='alt'><pre>");
191                         emit_blame_entry_linenumber(ent);
192                         html("</pre></div>");
193                 }
194                 html("</td>\n");
195         }
196
197         html("<td class='lines'><div>");
198
199         /* Colored bars behind lines */
200         html("<div>");
201         for (ent = sb.ent; ent; ) {
202                 struct blame_entry *e = ent->next;
203                 html("<div class='alt'><pre>");
204                 emit_blame_entry_line_background(&sb, ent);
205                 html("</pre></div>");
206                 free(ent);
207                 ent = e;
208         }
209         html("</div>");
210
211         free((void *)sb.final_buf);
212
213         /* Lines */
214         html("<pre><code>");
215         if (ctx.repo->source_filter) {
216                 char *filter_arg = xstrdup(basename);
217                 cgit_open_filter(ctx.repo->source_filter, filter_arg);
218                 html_raw(buf, size);
219                 cgit_close_filter(ctx.repo->source_filter);
220                 free(filter_arg);
221         } else {
222                 html_txt(buf);
223         }
224         html("</code></pre>");
225
226         html("</div></td>\n");
227
228         html("</tr>\n</table>\n");
229
230         cgit_print_layout_end();
231
232 cleanup:
233         free(buf);
234 }
235
236 static int walk_tree(const struct object_id *oid, struct strbuf *base,
237                      const char *pathname, unsigned mode, void *cbdata)
238 {
239         struct walk_tree_context *walk_tree_ctx = cbdata;
240
241         if (base->len == walk_tree_ctx->match_baselen) {
242                 if (S_ISREG(mode)) {
243                         struct strbuf buffer = STRBUF_INIT;
244                         strbuf_addbuf(&buffer, base);
245                         strbuf_addstr(&buffer, pathname);
246                         print_object(oid, buffer.buf, pathname,
247                                      walk_tree_ctx->curr_rev);
248                         strbuf_release(&buffer);
249                         walk_tree_ctx->state = 1;
250                 } else if (S_ISDIR(mode)) {
251                         walk_tree_ctx->state = 2;
252                 }
253         } else if (base->len < INT_MAX
254                         && (int)base->len > walk_tree_ctx->match_baselen) {
255                 walk_tree_ctx->state = 2;
256         } else if (S_ISDIR(mode)) {
257                 return READ_TREE_RECURSIVE;
258         }
259         return 0;
260 }
261
262 static int basedir_len(const char *path)
263 {
264         char *p = strrchr(path, '/');
265         if (p)
266                 return p - path + 1;
267         return 0;
268 }
269
270 void cgit_print_blame(void)
271 {
272         const char *rev = ctx.qry.oid;
273         struct object_id oid;
274         struct commit *commit;
275         struct pathspec_item path_items = {
276                 .match = ctx.qry.path,
277                 .len = ctx.qry.path ? strlen(ctx.qry.path) : 0
278         };
279         struct pathspec paths = {
280                 .nr = 1,
281                 .items = &path_items
282         };
283         struct walk_tree_context walk_tree_ctx = {
284                 .state = 0
285         };
286
287         if (!rev)
288                 rev = ctx.qry.head;
289
290         if (get_oid(rev, &oid)) {
291                 cgit_print_error_page(404, "Not found",
292                         "Invalid revision name: %s", rev);
293                 return;
294         }
295         commit = lookup_commit_reference(the_repository, &oid);
296         if (!commit || parse_commit(commit)) {
297                 cgit_print_error_page(404, "Not found",
298                         "Invalid commit reference: %s", rev);
299                 return;
300         }
301
302         walk_tree_ctx.curr_rev = xstrdup(rev);
303         walk_tree_ctx.match_baselen = (path_items.match) ?
304                                        basedir_len(path_items.match) : -1;
305
306         read_tree(the_repository, repo_get_commit_tree(the_repository, commit),
307                   &paths, walk_tree, &walk_tree_ctx);
308         if (!walk_tree_ctx.state)
309                 cgit_print_error_page(404, "Not found", "Not found");
310         else if (walk_tree_ctx.state == 2)
311                 cgit_print_error_page(404, "No blame for folders",
312                         "Blame is not available for folders.");
313
314         free(walk_tree_ctx.curr_rev);
315 }