Code

Improve parent blame to handle line-jumping for renames better
[tig.git] / tig.c
diff --git a/tig.c b/tig.c
index c963977bbdcdb71338534f27440563107ac9d8b1..dd155e35e23a6b11fd3aedc9e5883085372ebdb7 100644 (file)
--- a/tig.c
+++ b/tig.c
@@ -3977,73 +3977,6 @@ parse_author_line(char *ident, const char **author, struct time *time)
        }
 }
 
-static bool
-open_commit_parent_menu(char buf[SIZEOF_STR], int *parents)
-{
-       char rev[SIZEOF_REV];
-       const char *revlist_argv[] = {
-               "git", "log", "--no-color", "-1", "--pretty=format:%s", rev, NULL
-       };
-       struct menu_item *items;
-       char text[SIZEOF_STR];
-       bool ok = TRUE;
-       int i;
-
-       items = calloc(*parents + 1, sizeof(*items));
-       if (!items)
-               return FALSE;
-
-       for (i = 0; i < *parents; i++) {
-               string_copy_rev(rev, &buf[SIZEOF_REV * i]);
-               if (!io_run_buf(revlist_argv, text, sizeof(text)) ||
-                   !(items[i].text = strdup(text))) {
-                       ok = FALSE;
-                       break;
-               }
-       }
-
-       if (ok) {
-               *parents = 0;
-               ok = prompt_menu("Select parent", items, parents);
-       }
-       for (i = 0; items[i].text; i++)
-               free((char *) items[i].text);
-       free(items);
-       return ok;
-}
-
-static bool
-select_commit_parent(const char *id, char rev[SIZEOF_REV], const char *path)
-{
-       char buf[SIZEOF_STR * 4];
-       const char *revlist_argv[] = {
-               "git", "log", "--no-color", "-1",
-                       "--pretty=format:%P", id, "--", path, NULL
-       };
-       int parents;
-
-       if (!io_run_buf(revlist_argv, buf, sizeof(buf)) ||
-           (parents = strlen(buf) / 40) < 0) {
-               report("Failed to get parent information");
-               return FALSE;
-
-       } else if (parents == 0) {
-               if (path)
-                       report("Path '%s' does not exist in the parent", path);
-               else
-                       report("The selected commit has no parents");
-               return FALSE;
-       }
-
-       if (parents == 1)
-               parents = 0;
-       else if (!open_commit_parent_menu(buf, &parents))
-               return FALSE;
-
-       string_copy_rev(rev, &buf[41 * parents]);
-       return TRUE;
-}
-
 /*
  * Pager backend
  */
@@ -4898,7 +4831,8 @@ struct blame_commit {
        const char *author;             /* Author of the commit. */
        struct time time;               /* Date from the author ident. */
        char filename[128];             /* Name of file. */
-       bool has_previous;              /* Was a "previous" line detected. */
+       char parent_id[SIZEOF_REV];     /* Parent/previous SHA1 ID. */
+       char parent_filename[128];      /* Parent/previous name of file. */
 };
 
 struct blame {
@@ -4911,6 +4845,7 @@ static bool
 blame_open(struct view *view)
 {
        char path[SIZEOF_STR];
+       size_t i;
 
        if (!view->prev && *opt_prefix) {
                string_copy(path, opt_file);
@@ -4928,6 +4863,24 @@ blame_open(struct view *view)
                        return FALSE;
        }
 
+       /* First pass: remove multiple references to the same commit. */
+       for (i = 0; i < view->lines; i++) {
+               struct blame *blame = view->line[i].data;
+
+               if (blame->commit && blame->commit->id[0])
+                       blame->commit->id[0] = 0;
+               else
+                       blame->commit = NULL;
+       }
+
+       /* Second pass: free existing references. */
+       for (i = 0; i < view->lines; i++) {
+               struct blame *blame = view->line[i].data;
+
+               if (blame->commit)
+                       free(blame->commit);
+       }
+
        setup_update(view, opt_file);
        string_format(view->ref, "%s ...", opt_file);
 
@@ -5097,7 +5050,11 @@ blame_read(struct view *view, char *line)
                string_ncopy(commit->title, line, strlen(line));
 
        } else if (match_blame_header("previous ", &line)) {
-               commit->has_previous = TRUE;
+               if (strlen(line) <= SIZEOF_REV)
+                       return FALSE;
+               string_copy_rev(commit->parent_id, line);
+               line += SIZEOF_REV;
+               string_ncopy(commit->parent_filename, line, strlen(line));
 
        } else if (match_blame_header("filename ", &line)) {
                string_ncopy(commit->filename, line, strlen(line));
@@ -5153,16 +5110,20 @@ check_blame_commit(struct blame *blame, bool check_null_id)
 static void
 setup_blame_parent_line(struct view *view, struct blame *blame)
 {
+       char from[SIZEOF_REF + SIZEOF_STR];
+       char to[SIZEOF_REF + SIZEOF_STR];
        const char *diff_tree_argv[] = {
-               "git", "diff-tree", "-U0", blame->commit->id,
-                       "--", blame->commit->filename, NULL
+               "git", "diff", "--no-textconv", "--no-extdiff", "--no-color",
+                       "-U0", from, to, "--", NULL
        };
        struct io io;
        int parent_lineno = -1;
        int blamed_lineno = -1;
        char *line;
 
-       if (!io_run(&io, IO_RD, NULL, diff_tree_argv))
+       if (!string_format(from, "%s:%s", opt_ref, opt_file) ||
+           !string_format(to, "%s:%s", blame->commit->id, blame->commit->filename) ||
+           !io_run(&io, IO_RD, NULL, diff_tree_argv))
                return;
 
        while ((line = io_get(&io, '\n', TRUE))) {
@@ -5204,10 +5165,13 @@ blame_request(struct view *view, enum request request, struct line *line)
                break;
 
        case REQ_PARENT:
-               if (check_blame_commit(blame, TRUE) &&
-                   select_commit_parent(blame->commit->id, opt_ref,
-                                        blame->commit->filename)) {
-                       string_copy(opt_file, blame->commit->filename);
+               if (!check_blame_commit(blame, TRUE))
+                       break;
+               if (!*blame->commit->parent_id) {
+                       report("The selected commit has no parents");
+               } else {
+                       string_copy_rev(opt_ref, blame->commit->parent_id);
+                       string_copy(opt_file, blame->commit->parent_filename);
                        setup_blame_parent_line(view, blame);
                        open_view(view, REQ_VIEW_BLAME, OPEN_REFRESH);
                }
@@ -5228,7 +5192,7 @@ blame_request(struct view *view, enum request request, struct line *line)
                                        "-C", "-M", "HEAD", "--", view->vid, NULL
                        };
 
-                       if (!blame->commit->has_previous) {
+                       if (!*blame->commit->parent_id) {
                                diff_index_argv[1] = "diff";
                                diff_index_argv[2] = "--no-color";
                                diff_index_argv[6] = "--";