view: replace line user_flags with no_commit_refs and commit_title flags
[tig.git] / src / diff.c
blobaf97988b97e76d970b4bed02fc0390f67cbd7170
1 /* Copyright (c) 2006-2014 Jonas Fonseca <jonas.fonseca@gmail.com>
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU General Public License as
5 * published by the Free Software Foundation; either version 2 of
6 * the License, or (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
14 #include "tig/argv.h"
15 #include "tig/refdb.h"
16 #include "tig/repo.h"
17 #include "tig/options.h"
18 #include "tig/display.h"
19 #include "tig/parse.h"
20 #include "tig/pager.h"
21 #include "tig/diff.h"
22 #include "tig/draw.h"
24 static bool
25 diff_open(struct view *view, enum open_flags flags)
27 const char *diff_argv[] = {
28 "git", "show", encoding_arg, "--pretty=fuller", "--root",
29 "--patch-with-stat",
30 show_notes_arg(), diff_context_arg(), ignore_space_arg(),
31 "%(diffargs)", "%(cmdlineargs)", "--no-color", "%(commit)",
32 "--", "%(fileargs)", NULL
35 if (!pager_column_init(view))
36 return FALSE;
37 return begin_update(view, NULL, diff_argv, flags);
40 bool
41 diff_common_read(struct view *view, const char *data, struct diff_state *state)
43 enum line_type type = get_line_type(data);
45 if (!view->lines && type != LINE_COMMIT)
46 state->reading_diff_stat = TRUE;
48 if (state->combined_diff && !state->after_diff && data[0] == ' ' && data[1] != ' ')
49 state->reading_diff_stat = TRUE;
51 if (state->reading_diff_stat) {
52 size_t len = strlen(data);
53 char *pipe = strchr(data, '|');
54 bool has_histogram = data[len - 1] == '-' || data[len - 1] == '+';
55 bool has_bin_diff = pipe && strstr(pipe, "Bin") && strstr(pipe, "->");
56 bool has_rename = data[len - 1] == '0' && (strstr(data, "=>") || !strncmp(data, " ...", 4));
57 bool has_no_change = pipe && strstr(pipe, " 0");
59 if (pipe && (has_histogram || has_bin_diff || has_rename || has_no_change)) {
60 return add_line_text(view, data, LINE_DIFF_STAT) != NULL;
61 } else {
62 state->reading_diff_stat = FALSE;
65 } else if (!strcmp(data, "---")) {
66 state->reading_diff_stat = TRUE;
69 if (!state->after_commit_title && !prefixcmp(data, " ")) {
70 struct line *line = add_line_text(view, data, LINE_DEFAULT);
72 if (line)
73 line->commit_title = 1;
74 state->after_commit_title = TRUE;
75 return line != NULL;
78 if (type == LINE_DIFF_HEADER) {
79 const int len = STRING_SIZE("diff --");
81 state->after_diff = TRUE;
82 if (!strncmp(data + len, "combined ", strlen("combined ")) ||
83 !strncmp(data + len, "cc ", strlen("cc ")))
84 state->combined_diff = TRUE;
86 } else if (type == LINE_PP_MERGE) {
87 state->combined_diff = TRUE;
90 /* ADD2 and DEL2 are only valid in combined diff hunks */
91 if (!state->combined_diff && (type == LINE_DIFF_ADD2 || type == LINE_DIFF_DEL2))
92 type = LINE_DEFAULT;
94 return pager_common_read(view, data, type);
97 static bool
98 diff_find_stat_entry(struct view *view, struct line *line, enum line_type type)
100 struct line *marker = find_next_line_by_type(view, line, type);
102 return marker &&
103 line == find_prev_line_by_type(view, marker, LINE_DIFF_HEADER);
106 enum request
107 diff_common_enter(struct view *view, enum request request, struct line *line)
109 if (line->type == LINE_DIFF_STAT) {
110 int file_number = 0;
112 while (view_has_line(view, line) && line->type == LINE_DIFF_STAT) {
113 file_number++;
114 line--;
117 for (line = view->line; view_has_line(view, line); line++) {
118 line = find_next_line_by_type(view, line, LINE_DIFF_HEADER);
119 if (!line)
120 break;
122 if (diff_find_stat_entry(view, line, LINE_DIFF_INDEX)
123 || diff_find_stat_entry(view, line, LINE_DIFF_SIMILARITY)) {
124 if (file_number == 1) {
125 break;
127 file_number--;
131 if (!line) {
132 report("Failed to find file diff");
133 return REQ_NONE;
136 select_view_line(view, line - view->line);
137 report_clear();
138 return REQ_NONE;
140 } else {
141 return pager_request(view, request, line);
145 static bool
146 diff_common_draw_part(struct view *view, enum line_type *type, char **text, char c, enum line_type next_type)
148 char *sep = strchr(*text, c);
150 if (sep != NULL) {
151 *sep = 0;
152 draw_text(view, *type, *text);
153 *sep = c;
154 *text = sep;
155 *type = next_type;
158 return sep != NULL;
161 bool
162 diff_common_draw(struct view *view, struct line *line, unsigned int lineno)
164 char *text = line->data;
165 enum line_type type = line->type;
166 struct view_column *column = get_view_column(view, VIEW_COLUMN_LINE_NUMBER);
168 if (column && draw_lineno(view, column, lineno))
169 return TRUE;
171 if (line->wrapped && draw_text(view, LINE_DELIMITER, "+"))
172 return TRUE;
174 if (type == LINE_DIFF_STAT) {
175 diff_common_draw_part(view, &type, &text, '|', LINE_DEFAULT);
176 if (diff_common_draw_part(view, &type, &text, 'B', LINE_DEFAULT)) {
177 /* Handle binary diffstat: Bin <deleted> -> <added> bytes */
178 diff_common_draw_part(view, &type, &text, ' ', LINE_DIFF_DEL);
179 diff_common_draw_part(view, &type, &text, '-', LINE_DEFAULT);
180 diff_common_draw_part(view, &type, &text, ' ', LINE_DIFF_ADD);
181 diff_common_draw_part(view, &type, &text, 'b', LINE_DEFAULT);
183 } else {
184 diff_common_draw_part(view, &type, &text, '+', LINE_DIFF_ADD);
185 diff_common_draw_part(view, &type, &text, '-', LINE_DIFF_DEL);
189 if (line->commit_title)
190 draw_commit_title(view, text, 4);
191 else
192 draw_text(view, type, text);
193 return TRUE;
196 static bool
197 diff_read(struct view *view, char *data)
199 struct diff_state *state = view->private;
201 if (!data) {
202 /* Fall back to retry if no diff will be shown. */
203 if (view->lines == 0 && opt_file_argv) {
204 int pos = argv_size(view->argv)
205 - argv_size(opt_file_argv) - 1;
207 if (pos > 0 && !strcmp(view->argv[pos], "--")) {
208 for (; view->argv[pos]; pos++) {
209 free((void *) view->argv[pos]);
210 view->argv[pos] = NULL;
213 if (view->pipe)
214 io_done(view->pipe);
215 if (io_run(&view->io, IO_RD, view->dir, opt_env, view->argv))
216 return FALSE;
219 return TRUE;
222 return diff_common_read(view, data, state);
225 static bool
226 diff_blame_line(const char *ref, const char *file, unsigned long lineno,
227 struct blame_header *header, struct blame_commit *commit)
229 char author[SIZEOF_STR] = "";
230 char line_arg[SIZEOF_STR];
231 const char *blame_argv[] = {
232 "git", "blame", encoding_arg, "-p", line_arg, ref, "--", file, NULL
234 struct io io;
235 bool ok = FALSE;
236 char *buf;
238 if (!string_format(line_arg, "-L%ld,+1", lineno))
239 return FALSE;
241 if (!io_run(&io, IO_RD, repo.cdup, opt_env, blame_argv))
242 return FALSE;
244 while ((buf = io_get(&io, '\n', TRUE))) {
245 if (header) {
246 if (!parse_blame_header(header, buf, 9999999))
247 break;
248 header = NULL;
250 } else if (parse_blame_info(commit, author, buf)) {
251 ok = commit->filename != NULL;
252 break;
256 if (io_error(&io))
257 ok = FALSE;
259 io_done(&io);
260 return ok;
263 unsigned int
264 diff_get_lineno(struct view *view, struct line *line)
266 const struct line *header, *chunk;
267 unsigned int lineno;
268 struct chunk_header chunk_header;
270 /* Verify that we are after a diff header and one of its chunks */
271 header = find_prev_line_by_type(view, line, LINE_DIFF_HEADER);
272 chunk = find_prev_line_by_type(view, line, LINE_DIFF_CHUNK);
273 if (!header || !chunk || chunk < header)
274 return 0;
277 * In a chunk header, the number after the '+' sign is the number of its
278 * following line, in the new version of the file. We increment this
279 * number for each non-deletion line, until the given line position.
281 if (!parse_chunk_header(&chunk_header, chunk->data))
282 return 0;
284 lineno = chunk_header.new.position;
286 for (chunk++; chunk < line; chunk++)
287 if (chunk->type != LINE_DIFF_DEL &&
288 chunk->type != LINE_DIFF_DEL2)
289 lineno++;
291 return lineno;
294 static enum request
295 diff_trace_origin(struct view *view, struct line *line)
297 struct line *diff = find_prev_line_by_type(view, line, LINE_DIFF_HEADER);
298 struct line *chunk = find_prev_line_by_type(view, line, LINE_DIFF_CHUNK);
299 const char *chunk_data;
300 int chunk_marker = line->type == LINE_DIFF_DEL ? '-' : '+';
301 unsigned long lineno = 0;
302 const char *file = NULL;
303 char ref[SIZEOF_REF];
304 struct blame_header header;
305 struct blame_commit commit;
307 if (!diff || !chunk || chunk == line) {
308 report("The line to trace must be inside a diff chunk");
309 return REQ_NONE;
312 for (; diff < line && !file; diff++) {
313 const char *data = diff->data;
315 if (!prefixcmp(data, "--- a/")) {
316 file = data + STRING_SIZE("--- a/");
317 break;
321 if (diff == line || !file) {
322 report("Failed to read the file name");
323 return REQ_NONE;
326 chunk_data = chunk->data;
328 if (!parse_chunk_lineno(&lineno, chunk_data, chunk_marker)) {
329 report("Failed to read the line number");
330 return REQ_NONE;
333 if (lineno == 0) {
334 report("This is the origin of the line");
335 return REQ_NONE;
338 for (chunk += 1; chunk < line; chunk++) {
339 if (chunk->type == LINE_DIFF_ADD) {
340 lineno += chunk_marker == '+';
341 } else if (chunk->type == LINE_DIFF_DEL) {
342 lineno += chunk_marker == '-';
343 } else {
344 lineno++;
348 if (chunk_marker == '+')
349 string_copy(ref, view->vid);
350 else
351 string_format(ref, "%s^", view->vid);
353 if (string_rev_is_null(ref)) {
354 string_ncopy(view->env->file, file, strlen(file));
355 string_copy(view->env->ref, "");
356 view->env->lineno = lineno - 1;
358 } else {
359 if (!diff_blame_line(ref, file, lineno, &header, &commit)) {
360 report("Failed to read blame data");
361 return REQ_NONE;
364 string_ncopy(view->env->file, commit.filename, strlen(commit.filename));
365 string_copy(view->env->ref, header.id);
366 view->env->lineno = header.orig_lineno - 1;
369 return REQ_VIEW_BLAME;
372 const char *
373 diff_get_pathname(struct view *view, struct line *line)
375 const struct line *header;
376 const char *dst = NULL;
377 const char *prefixes[] = { " b/", "cc ", "combined " };
378 int i;
380 header = find_prev_line_by_type(view, line, LINE_DIFF_HEADER);
381 if (!header)
382 return NULL;
384 for (i = 0; i < ARRAY_SIZE(prefixes) && !dst; i++)
385 dst = strstr(header->data, prefixes[i]);
387 return dst ? dst + strlen(prefixes[--i]) : NULL;
390 enum request
391 diff_common_edit(struct view *view, enum request request, struct line *line)
393 const char *file = diff_get_pathname(view, line);
394 char path[SIZEOF_STR];
395 bool has_path = file && string_format(path, "%s%s", repo.cdup, file);
397 if (has_path && access(path, R_OK)) {
398 report("Failed to open file: %s", file);
399 return REQ_NONE;
402 open_editor(file, diff_get_lineno(view, line));
403 return REQ_NONE;
406 static enum request
407 diff_request(struct view *view, enum request request, struct line *line)
409 switch (request) {
410 case REQ_VIEW_BLAME:
411 return diff_trace_origin(view, line);
413 case REQ_EDIT:
414 return diff_common_edit(view, request, line);
416 case REQ_ENTER:
417 return diff_common_enter(view, request, line);
419 case REQ_REFRESH:
420 if (string_rev_is_null(view->vid))
421 refresh_view(view);
422 else
423 reload_view(view);
424 return REQ_NONE;
426 default:
427 return pager_request(view, request, line);
431 static void
432 diff_select(struct view *view, struct line *line)
434 if (line->type == LINE_DIFF_STAT) {
435 string_format(view->ref, "Press '%s' to jump to file diff",
436 get_view_key(view, REQ_ENTER));
437 } else {
438 const char *file = diff_get_pathname(view, line);
440 if (file) {
441 string_format(view->ref, "Changes to '%s'", file);
442 string_format(view->env->file, "%s", file);
443 view->env->blob[0] = 0;
444 } else {
445 string_ncopy(view->ref, view->ops->id, strlen(view->ops->id));
446 pager_select(view, line);
451 static struct view_ops diff_ops = {
452 "line",
453 argv_env.commit,
454 VIEW_DIFF_LIKE | VIEW_ADD_DESCRIBE_REF | VIEW_ADD_PAGER_REFS | VIEW_FILE_FILTER | VIEW_REFRESH,
455 sizeof(struct diff_state),
456 diff_open,
457 diff_read,
458 diff_common_draw,
459 diff_request,
460 view_column_grep,
461 diff_select,
462 NULL,
463 pager_get_column_data,
466 DEFINE_VIEW(diff);
468 /* vim: set ts=8 sw=8 noexpandtab: */