all repos — cgit @ e1ad15d368bdeb1bffea588b93a29055c5dfb7f4

a hyperfast web frontend for git written in c

ui-blame.c (view raw)

  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 "argv-array.h"
 14#include "blame.h"
 15
 16
 17static 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
 44static 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='sha1'>");
 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	while (line++ < ent->num_lines)
 58		html("\n");
 59}
 60
 61static void emit_blame_entry_linenumber(struct blame_entry *ent)
 62{
 63	const char *numberfmt = "<a id='n%1$d' href='#n%1$d'>%1$d</a>\n";
 64
 65	unsigned long lineno = ent->lno;
 66	while (lineno < ent->lno + ent->num_lines)
 67		htmlf(numberfmt, ++lineno);
 68}
 69
 70static void emit_blame_entry_line_background(struct blame_scoreboard *sb,
 71					     struct blame_entry *ent)
 72{
 73	unsigned long line;
 74	size_t len, maxlen = 2;
 75	const char* pos, *endpos;
 76
 77	for (line = ent->lno; line < ent->lno + ent->num_lines; line++) {
 78		html("\n");
 79		pos = blame_nth_line(sb, line);
 80		endpos = blame_nth_line(sb, line + 1);
 81		len = 0;
 82		while (pos < endpos) {
 83			len++;
 84			if (*pos++ == '\t')
 85				len = (len + 7) & ~7;
 86		}
 87		if (len > maxlen)
 88			maxlen = len;
 89	}
 90
 91	for (len = 0; len < maxlen - 1; len++)
 92		html(" ");
 93}
 94
 95struct walk_tree_context {
 96	char *curr_rev;
 97	int match_baselen;
 98	int state;
 99};
100
101static void print_object(const struct object_id *oid, const char *path,
102			 const char *basename, const char *rev)
103{
104	enum object_type type;
105	char *buf;
106	unsigned long size;
107	struct argv_array rev_argv = ARGV_ARRAY_INIT;
108	struct rev_info revs;
109	struct blame_scoreboard sb;
110	struct blame_origin *o;
111	struct blame_entry *ent = NULL;
112
113	type = oid_object_info(the_repository, oid, &size);
114	if (type == OBJ_BAD) {
115		cgit_print_error_page(404, "Not found", "Bad object name: %s",
116				      oid_to_hex(oid));
117		return;
118	}
119
120	buf = read_object_file(oid, &type, &size);
121	if (!buf) {
122		cgit_print_error_page(500, "Internal server error",
123			"Error reading object %s", oid_to_hex(oid));
124		return;
125	}
126
127	argv_array_push(&rev_argv, "blame");
128	argv_array_push(&rev_argv, rev);
129	init_revisions(&revs, NULL);
130	revs.diffopt.flags.allow_textconv = 1;
131	setup_revisions(rev_argv.argc, rev_argv.argv, &revs, NULL);
132	init_scoreboard(&sb);
133	sb.revs = &revs;
134	sb.repo = the_repository;
135	setup_scoreboard(&sb, path, &o);
136	o->suspects = blame_entry_prepend(NULL, 0, sb.num_lines, o);
137	prio_queue_put(&sb.commits, o->commit);
138	blame_origin_decref(o);
139	sb.ent = NULL;
140	sb.path = path;
141	assign_blame(&sb, 0);
142	blame_sort_final(&sb);
143	blame_coalesce(&sb);
144
145	cgit_set_title_from_path(path);
146
147	cgit_print_layout_start();
148	htmlf("blob: %s (", oid_to_hex(oid));
149	cgit_plain_link("plain", NULL, NULL, ctx.qry.head, rev, path);
150	html(") (");
151	cgit_tree_link("tree", NULL, NULL, ctx.qry.head, rev, path);
152	html(")\n");
153
154	if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) {
155		htmlf("<div class='error'>blob size (%ldKB)"
156		      " exceeds display size limit (%dKB).</div>",
157		      size / 1024, ctx.cfg.max_blob_size);
158		goto cleanup;
159	}
160
161	html("<table class='blame blob'>\n<tr>\n");
162
163	/* Commit hashes */
164	html("<td class='hashes'>");
165	for (ent = sb.ent; ent; ent = ent->next) {
166		html("<div class='alt'><pre>");
167		emit_blame_entry_hash(ent);
168		html("</pre></div>");
169	}
170	html("</td>\n");
171
172	/* Line numbers */
173	if (ctx.cfg.enable_tree_linenumbers) {
174		html("<td class='linenumbers'>");
175		for (ent = sb.ent; ent; ent = ent->next) {
176			html("<div class='alt'><pre>");
177			emit_blame_entry_linenumber(ent);
178			html("</pre></div>");
179		}
180		html("</td>\n");
181	}
182
183	html("<td class='lines'><div>");
184
185	/* Colored bars behind lines */
186	html("<div>");
187	for (ent = sb.ent; ent; ) {
188		struct blame_entry *e = ent->next;
189		html("<div class='alt'><pre>");
190		emit_blame_entry_line_background(&sb, ent);
191		html("</pre></div>");
192		free(ent);
193		ent = e;
194	}
195	html("</div>");
196
197	free((void *)sb.final_buf);
198
199	/* Lines */
200	html("<pre><code>");
201	if (ctx.repo->source_filter) {
202		char *filter_arg = xstrdup(basename);
203		cgit_open_filter(ctx.repo->source_filter, filter_arg);
204		html_raw(buf, size);
205		cgit_close_filter(ctx.repo->source_filter);
206		free(filter_arg);
207	} else {
208		html_txt(buf);
209	}
210	html("</code></pre>");
211
212	html("</div></td>\n");
213
214	html("</tr>\n</table>\n");
215
216	cgit_print_layout_end();
217
218cleanup:
219	free(buf);
220}
221
222static int walk_tree(const struct object_id *oid, struct strbuf *base,
223		     const char *pathname, unsigned mode, int stage,
224		     void *cbdata)
225{
226	struct walk_tree_context *walk_tree_ctx = cbdata;
227
228	if (base->len == walk_tree_ctx->match_baselen) {
229		if (S_ISREG(mode)) {
230			struct strbuf buffer = STRBUF_INIT;
231			strbuf_addbuf(&buffer, base);
232			strbuf_addstr(&buffer, pathname);
233			print_object(oid, buffer.buf, pathname,
234				     walk_tree_ctx->curr_rev);
235			strbuf_release(&buffer);
236			walk_tree_ctx->state = 1;
237		} else if (S_ISDIR(mode)) {
238			walk_tree_ctx->state = 2;
239		}
240	} else if (base->len < INT_MAX
241			&& (int)base->len > walk_tree_ctx->match_baselen) {
242		walk_tree_ctx->state = 2;
243	} else if (S_ISDIR(mode)) {
244		return READ_TREE_RECURSIVE;
245	}
246	return 0;
247}
248
249static int basedir_len(const char *path)
250{
251	char *p = strrchr(path, '/');
252	if (p)
253		return p - path + 1;
254	return 0;
255}
256
257void cgit_print_blame(void)
258{
259	const char *rev = ctx.qry.sha1;
260	struct object_id oid;
261	struct commit *commit;
262	struct pathspec_item path_items = {
263		.match = ctx.qry.path,
264		.len = ctx.qry.path ? strlen(ctx.qry.path) : 0
265	};
266	struct pathspec paths = {
267		.nr = 1,
268		.items = &path_items
269	};
270	struct walk_tree_context walk_tree_ctx = {
271		.state = 0
272	};
273
274	if (!rev)
275		rev = ctx.qry.head;
276
277	if (get_oid(rev, &oid)) {
278		cgit_print_error_page(404, "Not found",
279			"Invalid revision name: %s", rev);
280		return;
281	}
282	commit = lookup_commit_reference(the_repository, &oid);
283	if (!commit || parse_commit(commit)) {
284		cgit_print_error_page(404, "Not found",
285			"Invalid commit reference: %s", rev);
286		return;
287	}
288
289	walk_tree_ctx.curr_rev = xstrdup(rev);
290	walk_tree_ctx.match_baselen = (path_items.match) ?
291				       basedir_len(path_items.match) : -1;
292
293	read_tree_recursive(the_repository, commit->maybe_tree, "", 0, 0,
294		&paths, walk_tree, &walk_tree_ctx);
295	if (!walk_tree_ctx.state)
296		cgit_print_error_page(404, "Not found", "Not found");
297	else if (walk_tree_ctx.state == 2)
298		cgit_print_error_page(404, "No blame for folders",
299			"Blame is not available for folders.");
300
301	free(walk_tree_ctx.curr_rev);
302}