blame: use the fingerprint heuristic to match ignored lines
[gitweb.git] / builtin / blame.c
index c2da673ac802b02609d54de3932fa8a2db221dbd..bb321eb2000179ad924588080202a527a751d7f6 100644 (file)
@@ -52,6 +52,9 @@ static int no_whole_file_rename;
 static int show_progress;
 static char repeated_meta_color[COLOR_MAXLEN];
 static int coloring_mode;
+static struct string_list ignore_revs_file_list = STRING_LIST_INIT_NODUP;
+static int mark_unblamable_lines;
+static int mark_ignored_lines;
 
 static struct date_mode blame_date_mode = { DATE_ISO8601 };
 static size_t blame_date_width;
@@ -479,6 +482,14 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int
                        }
                }
 
+               if (mark_unblamable_lines && ent->unblamable) {
+                       length--;
+                       putchar('*');
+               }
+               if (mark_ignored_lines && ent->ignored) {
+                       length--;
+                       putchar('?');
+               }
                printf("%.*s", length, hex);
                if (opt & OUTPUT_ANNOTATE_COMPAT) {
                        const char *name;
@@ -695,6 +706,24 @@ static int git_blame_config(const char *var, const char *value, void *cb)
                parse_date_format(value, &blame_date_mode);
                return 0;
        }
+       if (!strcmp(var, "blame.ignorerevsfile")) {
+               const char *str;
+               int ret;
+
+               ret = git_config_pathname(&str, var, value);
+               if (ret)
+                       return ret;
+               string_list_insert(&ignore_revs_file_list, str);
+               return 0;
+       }
+       if (!strcmp(var, "blame.markunblamablelines")) {
+               mark_unblamable_lines = git_config_bool(var, value);
+               return 0;
+       }
+       if (!strcmp(var, "blame.markignoredlines")) {
+               mark_ignored_lines = git_config_bool(var, value);
+               return 0;
+       }
        if (!strcmp(var, "color.blame.repeatedlines")) {
                if (color_parse_mem(value, strlen(value), repeated_meta_color))
                        warning(_("invalid color '%s' in color.blame.repeatedLines"),
@@ -732,6 +761,8 @@ static int blame_copy_callback(const struct option *option, const char *arg, int
 {
        int *opt = option->value;
 
+       BUG_ON_OPT_NEG(unset);
+
        /*
         * -C enables copy from removed files;
         * -C -C enables copy from existing files, but only
@@ -754,6 +785,8 @@ static int blame_move_callback(const struct option *option, const char *arg, int
 {
        int *opt = option->value;
 
+       BUG_ON_OPT_NEG(unset);
+
        *opt |= PICKAXE_BLAME_MOVE;
 
        if (arg)
@@ -770,6 +803,27 @@ static int is_a_rev(const char *name)
        return OBJ_NONE < oid_object_info(the_repository, &oid, NULL);
 }
 
+static void build_ignorelist(struct blame_scoreboard *sb,
+                            struct string_list *ignore_revs_file_list,
+                            struct string_list *ignore_rev_list)
+{
+       struct string_list_item *i;
+       struct object_id oid;
+
+       oidset_init(&sb->ignore_list, 0);
+       for_each_string_list_item(i, ignore_revs_file_list) {
+               if (!strcmp(i->string, ""))
+                       oidset_clear(&sb->ignore_list);
+               else
+                       oidset_parse_file(&sb->ignore_list, i->string);
+       }
+       for_each_string_list_item(i, ignore_rev_list) {
+               if (get_oid_committish(i->string, &oid))
+                       die(_("cannot find revision %s to ignore"), i->string);
+               oidset_insert(&sb->ignore_list, &oid);
+       }
+}
+
 int cmd_blame(int argc, const char **argv, const char *prefix)
 {
        struct rev_info revs;
@@ -781,6 +835,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        struct progress_info pi = { NULL, 0 };
 
        struct string_list range_list = STRING_LIST_INIT_NODUP;
+       struct string_list ignore_rev_list = STRING_LIST_INIT_NODUP;
        int output_option = 0, opt = 0;
        int show_stats = 0;
        const char *revs_file = NULL;
@@ -802,6 +857,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                OPT_BIT('s', NULL, &output_option, N_("Suppress author name and timestamp (Default: off)"), OUTPUT_NO_AUTHOR),
                OPT_BIT('e', "show-email", &output_option, N_("Show author email instead of name (Default: off)"), OUTPUT_SHOW_EMAIL),
                OPT_BIT('w', NULL, &xdl_opts, N_("Ignore whitespace differences"), XDF_IGNORE_WHITESPACE),
+               OPT_STRING_LIST(0, "ignore-rev", &ignore_rev_list, N_("rev"), N_("Ignore <rev> when blaming")),
+               OPT_STRING_LIST(0, "ignore-revs-file", &ignore_revs_file_list, N_("file"), N_("Ignore revisions from <file>")),
                OPT_BIT(0, "color-lines", &output_option, N_("color redundant metadata from previous line differently"), OUTPUT_COLOR_LINE),
                OPT_BIT(0, "color-by-age", &output_option, N_("color lines by age"), OUTPUT_SHOW_AGE_WITH_COLOR),
 
@@ -830,7 +887,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
 
        setup_default_color_by_age();
        git_config(git_blame_config, &output_option);
-       init_revisions(&revs, NULL);
+       repo_init_revisions(the_repository, &revs, NULL);
        revs.date_mode = blame_date_mode;
        revs.diffopt.flags.allow_textconv = 1;
        revs.diffopt.flags.follow_renames = 1;
@@ -846,6 +903,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                case PARSE_OPT_HELP:
                case PARSE_OPT_ERROR:
                        exit(129);
+               case PARSE_OPT_COMPLETE:
+                       exit(0);
                case PARSE_OPT_DONE:
                        if (ctx.argv[0])
                                dashdash_pos = ctx.cpidx;
@@ -989,6 +1048,9 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        sb.contents_from = contents_from;
        sb.reverse = reverse;
        sb.repo = the_repository;
+       build_ignorelist(&sb, &ignore_revs_file_list, &ignore_rev_list);
+       string_list_clear(&ignore_revs_file_list, 0);
+       string_list_clear(&ignore_rev_list, 0);
        setup_scoreboard(&sb, path, &o);
        lno = sb.num_lines;
 
@@ -1001,7 +1063,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                long bottom, top;
                if (parse_range_arg(range_list.items[range_i].string,
                                    nth_line_cb, &sb, lno, anchor,
-                                   &bottom, &top, sb.path))
+                                   &bottom, &top, sb.path, &the_index))
                        usage(blame_usage);
                if ((!lno && (top || bottom)) || lno < bottom)
                        die(Q_("file %s has only %lu line",