From 9e46b6ba85a2847077e4432cb1afefedf5d49c59 Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Mon, 27 Apr 2020 23:29:21 +0200 Subject: [PATCH] Blame uncommitted deletions starting from HEAD When starting a blame in the stage view on a deleted line, it seems appropriate to find the commit that added the line. To this end, instead of starting the blame view from the working tree state of a file, use the version at HEAD, which still has the deleted line (unless the line was only just staged). Since we want to visit the line before application of the stage view's diff, diff_get_lineno does not work because it computes the one after diff application. Introduce diff_get_old_lineno to get the correct line number. This works for both staged and unstaged deletions. However, when a file has both staged and unstaged hunks, then blaming an unstaged deletion may jump to the wrong line number because only the unstaged diff is considered. Closes #1008 --- NEWS.adoc | 4 ++++ include/tig/diff.h | 1 + src/diff.c | 33 +++++++++++++++++++++++++++++++++ src/stage.c | 9 +++++++-- 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/NEWS.adoc b/NEWS.adoc index 7dbea85eb..1b5d44147 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -8,6 +8,10 @@ Bug fixes: - Fix wrapping of lines with multibyte characters. (#988) +Improvements: + + - Start blame on deleted lines in the stage view from HEAD so the line's origin can be traced. (#1008) + tig-2.5.1 --------- diff --git a/include/tig/diff.h b/include/tig/diff.h index f7cb97507..eca80ec35 100644 --- a/include/tig/diff.h +++ b/include/tig/diff.h @@ -41,6 +41,7 @@ enum status_code diff_init_highlight(struct view *view, struct diff_state *state bool diff_done_highlight(struct diff_state *state); unsigned int diff_get_lineno(struct view *view, struct line *line); +unsigned int diff_get_old_lineno(struct view *view, struct line *line); const char *diff_get_pathname(struct view *view, struct line *line); extern struct view diff_view; diff --git a/src/diff.c b/src/diff.c index e38162c12..6d2ea740d 100644 --- a/src/diff.c +++ b/src/diff.c @@ -635,6 +635,39 @@ diff_get_lineno(struct view *view, struct line *line) return lineno; } +unsigned int +diff_get_old_lineno(struct view *view, struct line *line) +{ + const struct line *header, *chunk; + unsigned int lineno; + struct chunk_header chunk_header; + + /* Verify that we are after a diff header and one of its chunks */ + header = find_prev_line_by_type(view, line, LINE_DIFF_HEADER); + chunk = find_prev_line_by_type(view, line, LINE_DIFF_CHUNK); + if (!header || !chunk || chunk < header) { + return 0; + } + + /* + * In a chunk header, the number after the '-' sign is the number of its + * following line, in the old version of the file. We increment this + * number for each non-addition line, until the given line position. + */ + if (!parse_chunk_header(&chunk_header, box_text(chunk))) { + return 0; + } + + lineno = chunk_header.old.position; + + for (chunk++; chunk < line; chunk++) + if (chunk->type != LINE_DIFF_ADD && + chunk->type != LINE_DIFF_ADD2) + lineno++; + + return lineno; +} + static enum request diff_trace_origin(struct view *view, struct line *line) { diff --git a/src/stage.c b/src/stage.c index 565c946a0..c1ce01ca9 100644 --- a/src/stage.c +++ b/src/stage.c @@ -444,8 +444,13 @@ stage_request(struct view *view, enum request request, struct line *line) string_ncopy(view->env->file, file, strlen(file)); } - view->env->ref[0] = 0; - view->env->goto_lineno = diff_get_lineno(view, line); + if (!is_initial_commit() && line->type == LINE_DIFF_DEL) { + string_copy(view->env->ref, "HEAD"); + view->env->goto_lineno = diff_get_old_lineno(view, line); + } else { + view->env->ref[0] = 0; + view->env->goto_lineno = diff_get_lineno(view, line); + } if (view->env->goto_lineno > 0) view->env->goto_lineno--; return request;