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.
15 #include "tig/refdb.h"
17 #include "tig/options.h"
18 #include "tig/display.h"
19 #include "tig/parse.h"
20 #include "tig/pager.h"
25 diff_open(struct view
*view
, enum open_flags flags
)
27 const char *diff_argv
[] = {
28 "git", "show", encoding_arg
, "--pretty=fuller", "--root",
30 show_notes_arg(), diff_context_arg(), ignore_space_arg(),
31 "%(diffargs)", "%(cmdlineargs)", "--no-color", "%(commit)",
32 "--", "%(fileargs)", NULL
35 diff_save_line(view
, view
->private, flags
);
37 return begin_update(view
, NULL
, diff_argv
, flags
);
41 diff_common_add_diff_stat(struct view
*view
, const char *text
, size_t offset
)
43 const char *start
= text
+ offset
;
44 const char *data
= start
+ strspn(start
, " ");
45 size_t len
= strlen(data
);
46 char *pipe
= strchr(data
, '|');
48 /* Ensure that '|' is present and the file name part contains
49 * non-space characters. */
50 if (!pipe
|| pipe
== data
|| strcspn(data
, " ") == 0)
53 /* Detect remaining part of a diff stat line:
55 * added | 40 +++++++++++
56 * remove | 124 --------------------------
58 * rename.from => rename.to | 0
59 * .../truncated file name | 11 ++---
60 * binary add | Bin 0 -> 1234 bytes
61 * binary update | Bin 1234 -> 2345 bytes
64 if ((data
[len
- 1] == '-' || data
[len
- 1] == '+') ||
66 (strstr(pipe
, "Bin") && strstr(pipe
, "->")) ||
67 strstr(pipe
, "Unmerged") ||
68 (data
[len
- 1] == '0' && (strstr(data
, "=>") || !prefixcmp(data
, "..."))))
69 return add_line_text(view
, text
, LINE_DIFF_STAT
);
74 diff_common_read(struct view
*view
, const char *data
, struct diff_state
*state
)
76 enum line_type type
= get_line_type(data
);
78 if (!view
->lines
&& type
!= LINE_COMMIT
)
79 state
->reading_diff_stat
= TRUE
;
81 if (state
->combined_diff
&& !state
->after_diff
&& data
[0] == ' ' && data
[1] != ' ')
82 state
->reading_diff_stat
= TRUE
;
84 if (state
->reading_diff_stat
) {
85 if (diff_common_add_diff_stat(view
, data
, 0))
87 state
->reading_diff_stat
= FALSE
;
89 } else if (!strcmp(data
, "---")) {
90 state
->reading_diff_stat
= TRUE
;
93 if (!state
->after_commit_title
&& !prefixcmp(data
, " ")) {
94 struct line
*line
= add_line_text(view
, data
, LINE_DEFAULT
);
97 line
->commit_title
= 1;
98 state
->after_commit_title
= TRUE
;
102 if (type
== LINE_DIFF_HEADER
) {
103 const int len
= STRING_SIZE("diff --");
105 state
->after_diff
= TRUE
;
106 if (!strncmp(data
+ len
, "combined ", strlen("combined ")) ||
107 !strncmp(data
+ len
, "cc ", strlen("cc ")))
108 state
->combined_diff
= TRUE
;
110 } else if (type
== LINE_PP_MERGE
) {
111 state
->combined_diff
= TRUE
;
114 /* ADD2 and DEL2 are only valid in combined diff hunks */
115 if (!state
->combined_diff
&& (type
== LINE_DIFF_ADD2
|| type
== LINE_DIFF_DEL2
))
118 return pager_common_read(view
, data
, type
, NULL
);
122 diff_find_stat_entry(struct view
*view
, struct line
*line
, enum line_type type
)
124 struct line
*marker
= find_next_line_by_type(view
, line
, type
);
127 line
== find_prev_line_by_type(view
, marker
, LINE_DIFF_HEADER
);
131 diff_common_enter(struct view
*view
, enum request request
, struct line
*line
)
133 if (line
->type
== LINE_DIFF_STAT
) {
136 while (view_has_line(view
, line
) && line
->type
== LINE_DIFF_STAT
) {
141 for (line
= view
->line
; view_has_line(view
, line
); line
++) {
142 line
= find_next_line_by_type(view
, line
, LINE_DIFF_HEADER
);
146 if (diff_find_stat_entry(view
, line
, LINE_DIFF_INDEX
)
147 || diff_find_stat_entry(view
, line
, LINE_DIFF_SIMILARITY
)) {
148 if (file_number
== 1) {
156 report("Failed to find file diff");
160 select_view_line(view
, line
- view
->line
);
165 return pager_request(view
, request
, line
);
170 diff_save_line(struct view
*view
, struct diff_state
*state
, enum open_flags flags
)
172 if (flags
& OPEN_RELOAD
) {
173 struct line
*line
= &view
->line
[view
->pos
.lineno
];
174 const char *file
= view_has_line(view
, line
) ? diff_get_pathname(view
, line
) : NULL
;
177 state
->file
= get_path(file
);
178 state
->lineno
= diff_get_lineno(view
, line
);
179 state
->pos
= view
->pos
;
185 diff_restore_line(struct view
*view
, struct diff_state
*state
)
187 struct line
*line
= &view
->line
[view
->lines
- 1];
192 while ((line
= find_prev_line_by_type(view
, line
, LINE_DIFF_HEADER
))) {
193 const char *file
= diff_get_pathname(view
, line
);
195 if (file
&& !strcmp(file
, state
->file
))
205 while ((line
= find_next_line_by_type(view
, line
, LINE_DIFF_CHUNK
))) {
206 unsigned int lineno
= diff_get_lineno(view
, line
);
208 for (line
++; view_has_line(view
, line
) && line
->type
!= LINE_DIFF_CHUNK
; line
++) {
209 if (lineno
== state
->lineno
) {
210 unsigned long lineno
= line
- view
->line
;
211 unsigned long offset
= lineno
- (state
->pos
.lineno
- state
->pos
.offset
);
213 goto_view_line(view
, offset
, lineno
);
217 if (line
->type
!= LINE_DIFF_DEL
&&
218 line
->type
!= LINE_DIFF_DEL2
)
225 diff_read(struct view
*view
, struct buffer
*buf
)
227 struct diff_state
*state
= view
->private;
230 /* Fall back to retry if no diff will be shown. */
231 if (view
->lines
== 0 && opt_file_args
) {
232 int pos
= argv_size(view
->argv
)
233 - argv_size(opt_file_args
) - 1;
235 if (pos
> 0 && !strcmp(view
->argv
[pos
], "--")) {
236 for (; view
->argv
[pos
]; pos
++) {
237 free((void *) view
->argv
[pos
]);
238 view
->argv
[pos
] = NULL
;
243 if (io_run(&view
->io
, IO_RD
, view
->dir
, opt_env
, view
->argv
))
248 diff_restore_line(view
, state
);
253 return diff_common_read(view
, buf
->data
, state
);
257 diff_blame_line(const char *ref
, const char *file
, unsigned long lineno
,
258 struct blame_header
*header
, struct blame_commit
*commit
)
260 char author
[SIZEOF_STR
] = "";
261 char line_arg
[SIZEOF_STR
];
262 const char *blame_argv
[] = {
263 "git", "blame", encoding_arg
, "-p", line_arg
, ref
, "--", file
, NULL
269 if (!string_format(line_arg
, "-L%ld,+1", lineno
))
272 if (!io_run(&io
, IO_RD
, repo
.cdup
, opt_env
, blame_argv
))
275 while (io_get(&io
, &buf
, '\n', TRUE
)) {
277 if (!parse_blame_header(header
, buf
.data
, 9999999))
281 } else if (parse_blame_info(commit
, author
, buf
.data
)) {
282 ok
= commit
->filename
!= NULL
;
295 diff_get_lineno(struct view
*view
, struct line
*line
)
297 const struct line
*header
, *chunk
;
299 struct chunk_header chunk_header
;
301 /* Verify that we are after a diff header and one of its chunks */
302 header
= find_prev_line_by_type(view
, line
, LINE_DIFF_HEADER
);
303 chunk
= find_prev_line_by_type(view
, line
, LINE_DIFF_CHUNK
);
304 if (!header
|| !chunk
|| chunk
< header
)
308 * In a chunk header, the number after the '+' sign is the number of its
309 * following line, in the new version of the file. We increment this
310 * number for each non-deletion line, until the given line position.
312 if (!parse_chunk_header(&chunk_header
, chunk
->data
))
315 lineno
= chunk_header
.new.position
;
317 for (chunk
++; chunk
< line
; chunk
++)
318 if (chunk
->type
!= LINE_DIFF_DEL
&&
319 chunk
->type
!= LINE_DIFF_DEL2
)
326 diff_trace_origin(struct view
*view
, struct line
*line
)
328 struct line
*diff
= find_prev_line_by_type(view
, line
, LINE_DIFF_HEADER
);
329 struct line
*chunk
= find_prev_line_by_type(view
, line
, LINE_DIFF_CHUNK
);
330 const char *chunk_data
;
331 int chunk_marker
= line
->type
== LINE_DIFF_DEL
? '-' : '+';
332 unsigned long lineno
= 0;
333 const char *file
= NULL
;
334 char ref
[SIZEOF_REF
];
335 struct blame_header header
;
336 struct blame_commit commit
;
338 if (!diff
|| !chunk
|| chunk
== line
) {
339 report("The line to trace must be inside a diff chunk");
343 for (; diff
< line
&& !file
; diff
++) {
344 const char *data
= diff
->data
;
346 if (!prefixcmp(data
, "--- a/")) {
347 file
= data
+ STRING_SIZE("--- a/");
352 if (diff
== line
|| !file
) {
353 report("Failed to read the file name");
357 chunk_data
= chunk
->data
;
359 if (!parse_chunk_lineno(&lineno
, chunk_data
, chunk_marker
)) {
360 report("Failed to read the line number");
365 report("This is the origin of the line");
369 for (chunk
+= 1; chunk
< line
; chunk
++) {
370 if (chunk
->type
== LINE_DIFF_ADD
) {
371 lineno
+= chunk_marker
== '+';
372 } else if (chunk
->type
== LINE_DIFF_DEL
) {
373 lineno
+= chunk_marker
== '-';
379 if (chunk_marker
== '+')
380 string_copy(ref
, view
->vid
);
382 string_format(ref
, "%s^", view
->vid
);
384 if (string_rev_is_null(ref
)) {
385 string_ncopy(view
->env
->file
, file
, strlen(file
));
386 string_copy(view
->env
->ref
, "");
387 view
->env
->lineno
= lineno
- 1;
390 if (!diff_blame_line(ref
, file
, lineno
, &header
, &commit
)) {
391 report("Failed to read blame data");
395 string_ncopy(view
->env
->file
, commit
.filename
, strlen(commit
.filename
));
396 string_copy(view
->env
->ref
, header
.id
);
397 view
->env
->lineno
= header
.orig_lineno
- 1;
400 return REQ_VIEW_BLAME
;
404 diff_get_pathname(struct view
*view
, struct line
*line
)
406 const struct line
*header
;
407 const char *dst
= NULL
;
408 const char *prefixes
[] = { " b/", "cc ", "combined " };
411 header
= find_prev_line_by_type(view
, line
, LINE_DIFF_HEADER
);
415 for (i
= 0; i
< ARRAY_SIZE(prefixes
) && !dst
; i
++)
416 dst
= strstr(header
->data
, prefixes
[i
]);
418 return dst
? dst
+ strlen(prefixes
[--i
]) : NULL
;
422 diff_common_edit(struct view
*view
, enum request request
, struct line
*line
)
424 const char *file
= diff_get_pathname(view
, line
);
425 char path
[SIZEOF_STR
];
426 bool has_path
= file
&& string_format(path
, "%s%s", repo
.cdup
, file
);
428 if (has_path
&& access(path
, R_OK
)) {
429 report("Failed to open file: %s", file
);
433 open_editor(file
, diff_get_lineno(view
, line
));
438 diff_request(struct view
*view
, enum request request
, struct line
*line
)
442 return diff_trace_origin(view
, line
);
445 return diff_common_edit(view
, request
, line
);
448 return diff_common_enter(view
, request
, line
);
451 if (string_rev_is_null(view
->vid
))
458 return pager_request(view
, request
, line
);
463 diff_select(struct view
*view
, struct line
*line
)
465 if (line
->type
== LINE_DIFF_STAT
) {
466 string_format(view
->ref
, "Press '%s' to jump to file diff",
467 get_view_key(view
, REQ_ENTER
));
469 const char *file
= diff_get_pathname(view
, line
);
472 string_format(view
->ref
, "Changes to '%s'", file
);
473 string_format(view
->env
->file
, "%s", file
);
474 view
->env
->lineno
= diff_get_lineno(view
, line
);
475 view
->env
->blob
[0] = 0;
477 string_ncopy(view
->ref
, view
->ops
->id
, strlen(view
->ops
->id
));
478 pager_select(view
, line
);
483 static struct view_ops diff_ops
= {
486 VIEW_DIFF_LIKE
| VIEW_ADD_DESCRIBE_REF
| VIEW_ADD_PAGER_REFS
| VIEW_FILE_FILTER
| VIEW_REFRESH
,
487 sizeof(struct diff_state
),
495 view_column_bit(LINE_NUMBER
) | view_column_bit(TEXT
),
496 pager_get_column_data
,
501 /* vim: set ts=8 sw=8 noexpandtab: */