{"thread":{"id":"57286","subject":"[PATCH] blame: add --ignore-revs-blob and blame.ignoreRevsBlob","startedAt":"2022-01-22T10:07:20Z","lastAt":"2022-02-17T08:27:18Z","messageCount":2,"participants":["David Barr via GitGitGadget","David Barr"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"446688","messageId":"pull.1204.git.git.1642846032807.gitgitgadget@gmail.com","threadId":"57286","inReplyTo":null,"subject":"[PATCH] blame: add --ignore-revs-blob and blame.ignoreRevsBlob","fromName":"David Barr via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2022-01-22T10:07:12Z","receivedAt":"2022-01-22T10:07:20Z","isPatch":true,"sender":{"key":"david.barr@cordelta.com","avatar":"https://avatars.githubusercontent.com/u/220594?v=4"},"body":"From: David Barr <git@davebarr.dev>\n\nIn a bare repository, there isn't a simple way to\nignore revisions via a file without extracting it\nto a temporary file.\n\nThis patch provides a command-line option and\nconfig variable, similar to --ignore-revs-file\nand blame.ignoreRevsFile, which reads the list\nof revisions to ignore from a blob in the\nrepository.\n\nSigned-off-by: David Barr <git@davebarr.dev>\n---\n    blame: Add --ignore-revs-blob and blame.ignoreRevsBlob\n    \n    In a bare repository, there isn't a simple way to ignore revisions via a\n    file without extracting it to a temporary file.\n    \n    This patch provides a command-line option and config variable, similar\n    to --ignore-revs-file and blame.ignoreRevsFile, which reads the list of\n    revisions to ignore from a blob in the repository.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1204%2Fdavebarrau%2Fadd-ignore-revs-blob-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1204/davebarrau/add-ignore-revs-blob-v1\nPull-Request: https://github.com/git/git/pull/1204\n\n Documentation/blame-options.txt | 10 ++--\n Documentation/config/blame.txt  |  7 ++-\n builtin/blame.c                 | 23 ++++++++-\n oidset.c                        | 84 ++++++++++++++++++++++++++-------\n oidset.h                        |  2 +\n t/t8013-blame-ignore-revs.sh    | 19 ++++++++\n 6 files changed, 124 insertions(+), 21 deletions(-)\n\ndiff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt\nindex 9a663535f44..d6e59c57983 100644\n--- a/Documentation/blame-options.txt\n+++ b/Documentation/blame-options.txt\n@@ -132,9 +132,13 @@ take effect.\n --ignore-revs-file <file>::\n \tIgnore revisions listed in `file`, which must be in the same format as an\n \t`fsck.skipList`.  This option may be repeated, and these files will be\n-\tprocessed after any files specified with the `blame.ignoreRevsFile` config\n-\toption.  An empty file name, `\"\"`, will clear the list of revs from\n-\tpreviously processed files.\n+\tprocessed after any files specified with the `blame.ignoreRevsFile` or\n+\t`blame.ignoreRevsBlob` config options.  An empty file name, `\"\"`, will\n+\tclear the list of revs from previously processed files.\n+\n+--ignore-revs-blob <blob>::\n+\tLike `--ignore-revs-file`, but consider the value as a reference to a blob\n+\tin the repository.\n \n --color-lines::\n \tColor line annotations in the default format differently if they come from\ndiff --git a/Documentation/config/blame.txt b/Documentation/config/blame.txt\nindex 4d047c17908..109ca796de0 100644\n--- a/Documentation/config/blame.txt\n+++ b/Documentation/config/blame.txt\n@@ -25,7 +25,12 @@ blame.ignoreRevsFile::\n \tline, in linkgit:git-blame[1].  Whitespace and comments beginning with\n \t`#` are ignored.  This option may be repeated multiple times.  Empty\n \tfile names will reset the list of ignored revisions.  This option will\n-\tbe handled before the command line option `--ignore-revs-file`.\n+\tbe handled before the command line options `--ignore-revs-file` and\n+\t`--ignore-revs-blob`.\n+\n+blame.ignoreRevsBlob::\n+\tLike `blame.ignoreRevsFile`, but consider the value as a reference to\n+\ta blob in the repository.\n \n blame.markUnblamableLines::\n \tMark lines that were changed by an ignored revision that we could not\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex 7fafeac4081..c70c99cfda5 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -54,6 +54,7 @@ static int show_progress;\n static char repeated_meta_color[COLOR_MAXLEN];\n static int coloring_mode;\n static struct string_list ignore_revs_file_list = STRING_LIST_INIT_NODUP;\n+static struct string_list ignore_revs_blob_list = STRING_LIST_INIT_NODUP;\n static int mark_unblamable_lines;\n static int mark_ignored_lines;\n \n@@ -711,6 +712,16 @@ static int git_blame_config(const char *var, const char *value, void *cb)\n \t\tstring_list_insert(&ignore_revs_file_list, str);\n \t\treturn 0;\n \t}\n+\tif (!strcmp(var, \"blame.ignorerevsblob\")) {\n+\t\tconst char *str;\n+\t\tint ret;\n+\n+\t\tret = git_config_string(&str, var, value);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t\tstring_list_insert(&ignore_revs_blob_list, str);\n+\t\treturn 0;\n+\t}\n \tif (!strcmp(var, \"blame.markunblamablelines\")) {\n \t\tmark_unblamable_lines = git_config_bool(var, value);\n \t\treturn 0;\n@@ -822,6 +833,7 @@ static int peel_to_commit_oid(struct object_id *oid_ret, void *cbdata)\n \n static void build_ignorelist(struct blame_scoreboard *sb,\n \t\t\t     struct string_list *ignore_revs_file_list,\n+\t\t\t     struct string_list *ignore_revs_blob_list,\n \t\t\t     struct string_list *ignore_rev_list)\n {\n \tstruct string_list_item *i;\n@@ -835,6 +847,13 @@ static void build_ignorelist(struct blame_scoreboard *sb,\n \t\t\toidset_parse_file_carefully(&sb->ignore_list, i->string,\n \t\t\t\t\t\t    peel_to_commit_oid, sb);\n \t}\n+\tfor_each_string_list_item(i, ignore_revs_blob_list) {\n+\t\tif (!strcmp(i->string, \"\"))\n+\t\t\toidset_clear(&sb->ignore_list);\n+\t\telse\n+\t\t\toidset_parse_blob(&sb->ignore_list, i->string,\n+\t\t\t\t\t\t    peel_to_commit_oid, sb);\n+\t}\n \tfor_each_string_list_item(i, ignore_rev_list) {\n \t\tif (get_oid_committish(i->string, &oid) ||\n \t\t    peel_to_commit_oid(&oid, sb))\n@@ -878,6 +897,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)\n \t\tOPT_BIT('w', NULL, &xdl_opts, N_(\"ignore whitespace differences\"), XDF_IGNORE_WHITESPACE),\n \t\tOPT_STRING_LIST(0, \"ignore-rev\", &ignore_rev_list, N_(\"rev\"), N_(\"ignore <rev> when blaming\")),\n \t\tOPT_STRING_LIST(0, \"ignore-revs-file\", &ignore_revs_file_list, N_(\"file\"), N_(\"ignore revisions from <file>\")),\n+\t\tOPT_STRING_LIST(0, \"ignore-revs-blob\", &ignore_revs_blob_list, N_(\"blob\"), N_(\"ignore revisions from <blob>\")),\n \t\tOPT_BIT(0, \"color-lines\", &output_option, N_(\"color redundant metadata from previous line differently\"), OUTPUT_COLOR_LINE),\n \t\tOPT_BIT(0, \"color-by-age\", &output_option, N_(\"color lines by age\"), OUTPUT_SHOW_AGE_WITH_COLOR),\n \t\tOPT_BIT(0, \"minimal\", &xdl_opts, N_(\"spend extra cycles to find better match\"), XDF_NEED_MINIMAL),\n@@ -1084,8 +1104,9 @@ parse_done:\n \tsb.reverse = reverse;\n \tsb.repo = the_repository;\n \tsb.path = path;\n-\tbuild_ignorelist(&sb, &ignore_revs_file_list, &ignore_rev_list);\n+\tbuild_ignorelist(&sb, &ignore_revs_file_list, &ignore_revs_blob_list, &ignore_rev_list);\n \tstring_list_clear(&ignore_revs_file_list, 0);\n+\tstring_list_clear(&ignore_revs_blob_list, 0);\n \tstring_list_clear(&ignore_rev_list, 0);\n \tsetup_scoreboard(&sb, &o);\n \ndiff --git a/oidset.c b/oidset.c\nindex b36a2bae864..0cca63700da 100644\n--- a/oidset.c\n+++ b/oidset.c\n@@ -1,5 +1,6 @@\n #include \"cache.h\"\n #include \"oidset.h\"\n+#include \"object-store.h\"\n \n void oidset_init(struct oidset *set, size_t initial_size)\n {\n@@ -41,6 +42,29 @@ void oidset_parse_file(struct oidset *set, const char *path)\n \toidset_parse_file_carefully(set, path, NULL, NULL);\n }\n \n+static int read_oidset_line(struct strbuf sb, struct object_id *oid)\n+{\n+       const char *p;\n+       const char *name;\n+\n+       /*\n+\t* Allow trailing comments, leading whitespace\n+\t* (including before commits), and empty or whitespace\n+\t* only lines.\n+\t*/\n+       name = strchr(sb.buf, '#');\n+       if (name)\n+\t       strbuf_setlen(&sb, name - sb.buf);\n+       strbuf_trim(&sb);\n+       if (!sb.len)\n+\t       return 0;\n+\n+       if (parse_oid_hex(sb.buf, oid, &p) || *p != '\\0')\n+\t       die(\"invalid object name: %s\", sb.buf);\n+\n+       return 1;\n+}\n+\n void oidset_parse_file_carefully(struct oidset *set, const char *path,\n \t\t\t\t oidset_parse_tweak_fn fn, void *cbdata)\n {\n@@ -52,23 +76,8 @@ void oidset_parse_file_carefully(struct oidset *set, const char *path,\n \tif (!fp)\n \t\tdie(\"could not open object name list: %s\", path);\n \twhile (!strbuf_getline(&sb, fp)) {\n-\t\tconst char *p;\n-\t\tconst char *name;\n-\n-\t\t/*\n-\t\t * Allow trailing comments, leading whitespace\n-\t\t * (including before commits), and empty or whitespace\n-\t\t * only lines.\n-\t\t */\n-\t\tname = strchr(sb.buf, '#');\n-\t\tif (name)\n-\t\t\tstrbuf_setlen(&sb, name - sb.buf);\n-\t\tstrbuf_trim(&sb);\n-\t\tif (!sb.len)\n+\t\tif (!read_oidset_line(sb, &oid))\n \t\t\tcontinue;\n-\n-\t\tif (parse_oid_hex(sb.buf, &oid, &p) || *p != '\\0')\n-\t\t\tdie(\"invalid object name: %s\", sb.buf);\n \t\tif (fn && fn(&oid, cbdata))\n \t\t\tcontinue;\n \t\toidset_insert(set, &oid);\n@@ -78,3 +87,46 @@ void oidset_parse_file_carefully(struct oidset *set, const char *path,\n \tfclose(fp);\n \tstrbuf_release(&sb);\n }\n+\n+static void read_oidset_string(struct oidset *set, oidset_parse_tweak_fn fn,\n+\t\t\t       void *cbdata, const char *buf, unsigned long size)\n+{\n+\tstruct object_id oid;\n+\tstruct strbuf **lines;\n+\tstruct strbuf **line;\n+\n+\tlines = strbuf_split_buf(buf, size, '\\n', 0);\n+\n+\tfor (line = lines; *line; line++) {\n+\t\tif (!read_oidset_line(**line, &oid))\n+\t\t\tcontinue;\n+\t\tif (fn && fn(&oid, cbdata))\n+\t\t\tcontinue;\n+\t\toidset_insert(set, &oid);\n+\t}\n+\tstrbuf_list_free(lines);\n+}\n+\n+void oidset_parse_blob(struct oidset *set, const char *name,\n+\t\t\t\t oidset_parse_tweak_fn fn, void *cbdata)\n+{\n+\tstruct object_id oid;\n+\tchar *buf;\n+\tunsigned long size;\n+\tenum object_type type;\n+\n+\tif (!name) {\n+\t\treturn;\n+\t}\n+\tif (get_oid(name, &oid) < 0) {\n+\t\tdie(\"unable to read object id for %s\", name);\n+\t}\n+\tbuf = read_object_file(&oid, &type, &size);\n+\tif (!buf)\n+\t\tdie(\"unable to read oidset file at %s\", name);\n+\tif (type != OBJ_BLOB)\n+\t\tdie(\"oidset file is not a blob: %s\", name);\n+\n+\tread_oidset_string(set, fn, cbdata, buf, size);\n+\tfree(buf);\n+}\ndiff --git a/oidset.h b/oidset.h\nindex ba4a5a2cd3a..bad9246a150 100644\n--- a/oidset.h\n+++ b/oidset.h\n@@ -84,6 +84,8 @@ void oidset_parse_file(struct oidset *set, const char *path);\n typedef int (*oidset_parse_tweak_fn)(struct object_id *, void *);\n void oidset_parse_file_carefully(struct oidset *set, const char *path,\n \t\t\t\t oidset_parse_tweak_fn fn, void *cbdata);\n+void oidset_parse_blob(struct oidset *set, const char *path,\n+\t\t\t\t oidset_parse_tweak_fn fn, void *cbdata);\n \n struct oidset_iter {\n \tkh_oid_set_t *set;\ndiff --git a/t/t8013-blame-ignore-revs.sh b/t/t8013-blame-ignore-revs.sh\nindex b18633dee1b..9d38a473a39 100755\n--- a/t/t8013-blame-ignore-revs.sh\n+++ b/t/t8013-blame-ignore-revs.sh\n@@ -115,6 +115,25 @@ test_expect_success ignore_revs_from_configs_and_files '\n \ttest_cmp expect actual\n '\n \n+# Ignore X from the config option, Y from a file.\n+test_expect_success ignore_revs_from_configs_and_blobs '\n+\tgit rev-parse X >ignore_x &&\n+\tgit rev-parse Y >ignore_y &&\n+\tgit add ignore_x ignore_y &&\n+\tgit commit -m ignore &&\n+\tgit config --add blame.ignoreRevsBlob HEAD:ignore_x &&\n+\tgit blame --line-porcelain file --ignore-revs-blob HEAD:ignore_y >blame_raw 2>&1 &&\n+\tgit config --unset blame.ignoreRevsBlob HEAD:ignore_x &&\n+\n+\tgrep -E \"^[0-9a-f]+ [0-9]+ 1\" blame_raw | sed -e \"s/ .*//\" >actual &&\n+\tgit rev-parse A >expect &&\n+\ttest_cmp expect actual &&\n+\n+\tgrep -E \"^[0-9a-f]+ [0-9]+ 2\" blame_raw | sed -e \"s/ .*//\" >actual &&\n+\tgit rev-parse B >expect &&\n+\ttest_cmp expect actual\n+'\n+\n # Override blame.ignoreRevsFile (ignore_x) with an empty string.  X should be\n # blamed now for lines 1 and 2, since we are no longer ignoring X.\n test_expect_success override_ignore_revs_file '\n\nbase-commit: 297ca895a27a6bbdb7906371d533f72a12ad25b2\n-- \ngitgitgadget\n"},{"id":"448668","messageId":"aa98051b-5cf4-c910-c410-04b75c948354@davebarr.dev","threadId":"57286","inReplyTo":"pull.1204.git.git.1642846032807.gitgitgadget@gmail.com","subject":"Re: [PATCH] blame: add --ignore-revs-blob and blame.ignoreRevsBlob","fromName":"David Barr","fromEmail":"git@davebarr.dev","sentAt":"2022-02-17T08:27:04Z","receivedAt":"2022-02-17T08:27:18Z","isPatch":true,"sender":{"key":"git@davebarr.dev","avatar":null},"body":"On 22/1/2022 21:07, David Barr via GitGitGadget wrote:\n> From: David Barr <git@davebarr.dev>\n> \n> In a bare repository, there isn't a simple way to\n> ignore revisions via a file without extracting it\n> to a temporary file.\n> \n> This patch provides a command-line option and\n> config variable, similar to --ignore-revs-file\n> and blame.ignoreRevsFile, which reads the list\n> of revisions to ignore from a blob in the\n> repository.\n> \n> Signed-off-by: David Barr <git@davebarr.dev>\n> ---\n\nLooks like this patch might have gotten lost in the noise.\n\nCould anyone take a look?\n\n>      blame: Add --ignore-revs-blob and blame.ignoreRevsBlob\n>      \n>      In a bare repository, there isn't a simple way to ignore revisions via a\n>      file without extracting it to a temporary file.\n>      \n>      This patch provides a command-line option and config variable, similar\n>      to --ignore-revs-file and blame.ignoreRevsFile, which reads the list of\n>      revisions to ignore from a blob in the repository.\n> \n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1204%2Fdavebarrau%2Fadd-ignore-revs-blob-v1\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1204/davebarrau/add-ignore-revs-blob-v1\n> Pull-Request: https://github.com/git/git/pull/1204\n> \n>   Documentation/blame-options.txt | 10 ++--\n>   Documentation/config/blame.txt  |  7 ++-\n>   builtin/blame.c                 | 23 ++++++++-\n>   oidset.c                        | 84 ++++++++++++++++++++++++++-------\n>   oidset.h                        |  2 +\n>   t/t8013-blame-ignore-revs.sh    | 19 ++++++++\n>   6 files changed, 124 insertions(+), 21 deletions(-)\n> \n> diff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt\n> index 9a663535f44..d6e59c57983 100644\n> --- a/Documentation/blame-options.txt\n> +++ b/Documentation/blame-options.txt\n> @@ -132,9 +132,13 @@ take effect.\n>   --ignore-revs-file <file>::\n>   \tIgnore revisions listed in `file`, which must be in the same format as an\n>   \t`fsck.skipList`.  This option may be repeated, and these files will be\n> -\tprocessed after any files specified with the `blame.ignoreRevsFile` config\n> -\toption.  An empty file name, `\"\"`, will clear the list of revs from\n> -\tpreviously processed files.\n> +\tprocessed after any files specified with the `blame.ignoreRevsFile` or\n> +\t`blame.ignoreRevsBlob` config options.  An empty file name, `\"\"`, will\n> +\tclear the list of revs from previously processed files.\n> +\n> +--ignore-revs-blob <blob>::\n> +\tLike `--ignore-revs-file`, but consider the value as a reference to a blob\n> +\tin the repository.\n>   \n>   --color-lines::\n>   \tColor line annotations in the default format differently if they come from\n> diff --git a/Documentation/config/blame.txt b/Documentation/config/blame.txt\n> index 4d047c17908..109ca796de0 100644\n> --- a/Documentation/config/blame.txt\n> +++ b/Documentation/config/blame.txt\n> @@ -25,7 +25,12 @@ blame.ignoreRevsFile::\n>   \tline, in linkgit:git-blame[1].  Whitespace and comments beginning with\n>   \t`#` are ignored.  This option may be repeated multiple times.  Empty\n>   \tfile names will reset the list of ignored revisions.  This option will\n> -\tbe handled before the command line option `--ignore-revs-file`.\n> +\tbe handled before the command line options `--ignore-revs-file` and\n> +\t`--ignore-revs-blob`.\n> +\n> +blame.ignoreRevsBlob::\n> +\tLike `blame.ignoreRevsFile`, but consider the value as a reference to\n> +\ta blob in the repository.\n>   \n>   blame.markUnblamableLines::\n>   \tMark lines that were changed by an ignored revision that we could not\n> diff --git a/builtin/blame.c b/builtin/blame.c\n> index 7fafeac4081..c70c99cfda5 100644\n> --- a/builtin/blame.c\n> +++ b/builtin/blame.c\n> @@ -54,6 +54,7 @@ static int show_progress;\n>   static char repeated_meta_color[COLOR_MAXLEN];\n>   static int coloring_mode;\n>   static struct string_list ignore_revs_file_list = STRING_LIST_INIT_NODUP;\n> +static struct string_list ignore_revs_blob_list = STRING_LIST_INIT_NODUP;\n>   static int mark_unblamable_lines;\n>   static int mark_ignored_lines;\n>   \n> @@ -711,6 +712,16 @@ static int git_blame_config(const char *var, const char *value, void *cb)\n>   \t\tstring_list_insert(&ignore_revs_file_list, str);\n>   \t\treturn 0;\n>   \t}\n> +\tif (!strcmp(var, \"blame.ignorerevsblob\")) {\n> +\t\tconst char *str;\n> +\t\tint ret;\n> +\n> +\t\tret = git_config_string(&str, var, value);\n> +\t\tif (ret)\n> +\t\t\treturn ret;\n> +\t\tstring_list_insert(&ignore_revs_blob_list, str);\n> +\t\treturn 0;\n> +\t}\n>   \tif (!strcmp(var, \"blame.markunblamablelines\")) {\n>   \t\tmark_unblamable_lines = git_config_bool(var, value);\n>   \t\treturn 0;\n> @@ -822,6 +833,7 @@ static int peel_to_commit_oid(struct object_id *oid_ret, void *cbdata)\n>   \n>   static void build_ignorelist(struct blame_scoreboard *sb,\n>   \t\t\t     struct string_list *ignore_revs_file_list,\n> +\t\t\t     struct string_list *ignore_revs_blob_list,\n>   \t\t\t     struct string_list *ignore_rev_list)\n>   {\n>   \tstruct string_list_item *i;\n> @@ -835,6 +847,13 @@ static void build_ignorelist(struct blame_scoreboard *sb,\n>   \t\t\toidset_parse_file_carefully(&sb->ignore_list, i->string,\n>   \t\t\t\t\t\t    peel_to_commit_oid, sb);\n>   \t}\n> +\tfor_each_string_list_item(i, ignore_revs_blob_list) {\n> +\t\tif (!strcmp(i->string, \"\"))\n> +\t\t\toidset_clear(&sb->ignore_list);\n> +\t\telse\n> +\t\t\toidset_parse_blob(&sb->ignore_list, i->string,\n> +\t\t\t\t\t\t    peel_to_commit_oid, sb);\n> +\t}\n>   \tfor_each_string_list_item(i, ignore_rev_list) {\n>   \t\tif (get_oid_committish(i->string, &oid) ||\n>   \t\t    peel_to_commit_oid(&oid, sb))\n> @@ -878,6 +897,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)\n>   \t\tOPT_BIT('w', NULL, &xdl_opts, N_(\"ignore whitespace differences\"), XDF_IGNORE_WHITESPACE),\n>   \t\tOPT_STRING_LIST(0, \"ignore-rev\", &ignore_rev_list, N_(\"rev\"), N_(\"ignore <rev> when blaming\")),\n>   \t\tOPT_STRING_LIST(0, \"ignore-revs-file\", &ignore_revs_file_list, N_(\"file\"), N_(\"ignore revisions from <file>\")),\n> +\t\tOPT_STRING_LIST(0, \"ignore-revs-blob\", &ignore_revs_blob_list, N_(\"blob\"), N_(\"ignore revisions from <blob>\")),\n>   \t\tOPT_BIT(0, \"color-lines\", &output_option, N_(\"color redundant metadata from previous line differently\"), OUTPUT_COLOR_LINE),\n>   \t\tOPT_BIT(0, \"color-by-age\", &output_option, N_(\"color lines by age\"), OUTPUT_SHOW_AGE_WITH_COLOR),\n>   \t\tOPT_BIT(0, \"minimal\", &xdl_opts, N_(\"spend extra cycles to find better match\"), XDF_NEED_MINIMAL),\n> @@ -1084,8 +1104,9 @@ parse_done:\n>   \tsb.reverse = reverse;\n>   \tsb.repo = the_repository;\n>   \tsb.path = path;\n> -\tbuild_ignorelist(&sb, &ignore_revs_file_list, &ignore_rev_list);\n> +\tbuild_ignorelist(&sb, &ignore_revs_file_list, &ignore_revs_blob_list, &ignore_rev_list);\n>   \tstring_list_clear(&ignore_revs_file_list, 0);\n> +\tstring_list_clear(&ignore_revs_blob_list, 0);\n>   \tstring_list_clear(&ignore_rev_list, 0);\n>   \tsetup_scoreboard(&sb, &o);\n>   \n> diff --git a/oidset.c b/oidset.c\n> index b36a2bae864..0cca63700da 100644\n> --- a/oidset.c\n> +++ b/oidset.c\n> @@ -1,5 +1,6 @@\n>   #include \"cache.h\"\n>   #include \"oidset.h\"\n> +#include \"object-store.h\"\n>   \n>   void oidset_init(struct oidset *set, size_t initial_size)\n>   {\n> @@ -41,6 +42,29 @@ void oidset_parse_file(struct oidset *set, const char *path)\n>   \toidset_parse_file_carefully(set, path, NULL, NULL);\n>   }\n>   \n> +static int read_oidset_line(struct strbuf sb, struct object_id *oid)\n> +{\n> +       const char *p;\n> +       const char *name;\n> +\n> +       /*\n> +\t* Allow trailing comments, leading whitespace\n> +\t* (including before commits), and empty or whitespace\n> +\t* only lines.\n> +\t*/\n> +       name = strchr(sb.buf, '#');\n> +       if (name)\n> +\t       strbuf_setlen(&sb, name - sb.buf);\n> +       strbuf_trim(&sb);\n> +       if (!sb.len)\n> +\t       return 0;\n> +\n> +       if (parse_oid_hex(sb.buf, oid, &p) || *p != '\\0')\n> +\t       die(\"invalid object name: %s\", sb.buf);\n> +\n> +       return 1;\n> +}\n> +\n>   void oidset_parse_file_carefully(struct oidset *set, const char *path,\n>   \t\t\t\t oidset_parse_tweak_fn fn, void *cbdata)\n>   {\n> @@ -52,23 +76,8 @@ void oidset_parse_file_carefully(struct oidset *set, const char *path,\n>   \tif (!fp)\n>   \t\tdie(\"could not open object name list: %s\", path);\n>   \twhile (!strbuf_getline(&sb, fp)) {\n> -\t\tconst char *p;\n> -\t\tconst char *name;\n> -\n> -\t\t/*\n> -\t\t * Allow trailing comments, leading whitespace\n> -\t\t * (including before commits), and empty or whitespace\n> -\t\t * only lines.\n> -\t\t */\n> -\t\tname = strchr(sb.buf, '#');\n> -\t\tif (name)\n> -\t\t\tstrbuf_setlen(&sb, name - sb.buf);\n> -\t\tstrbuf_trim(&sb);\n> -\t\tif (!sb.len)\n> +\t\tif (!read_oidset_line(sb, &oid))\n>   \t\t\tcontinue;\n> -\n> -\t\tif (parse_oid_hex(sb.buf, &oid, &p) || *p != '\\0')\n> -\t\t\tdie(\"invalid object name: %s\", sb.buf);\n>   \t\tif (fn && fn(&oid, cbdata))\n>   \t\t\tcontinue;\n>   \t\toidset_insert(set, &oid);\n> @@ -78,3 +87,46 @@ void oidset_parse_file_carefully(struct oidset *set, const char *path,\n>   \tfclose(fp);\n>   \tstrbuf_release(&sb);\n>   }\n> +\n> +static void read_oidset_string(struct oidset *set, oidset_parse_tweak_fn fn,\n> +\t\t\t       void *cbdata, const char *buf, unsigned long size)\n> +{\n> +\tstruct object_id oid;\n> +\tstruct strbuf **lines;\n> +\tstruct strbuf **line;\n> +\n> +\tlines = strbuf_split_buf(buf, size, '\\n', 0);\n> +\n> +\tfor (line = lines; *line; line++) {\n> +\t\tif (!read_oidset_line(**line, &oid))\n> +\t\t\tcontinue;\n> +\t\tif (fn && fn(&oid, cbdata))\n> +\t\t\tcontinue;\n> +\t\toidset_insert(set, &oid);\n> +\t}\n> +\tstrbuf_list_free(lines);\n> +}\n> +\n> +void oidset_parse_blob(struct oidset *set, const char *name,\n> +\t\t\t\t oidset_parse_tweak_fn fn, void *cbdata)\n> +{\n> +\tstruct object_id oid;\n> +\tchar *buf;\n> +\tunsigned long size;\n> +\tenum object_type type;\n> +\n> +\tif (!name) {\n> +\t\treturn;\n> +\t}\n> +\tif (get_oid(name, &oid) < 0) {\n> +\t\tdie(\"unable to read object id for %s\", name);\n> +\t}\n> +\tbuf = read_object_file(&oid, &type, &size);\n> +\tif (!buf)\n> +\t\tdie(\"unable to read oidset file at %s\", name);\n> +\tif (type != OBJ_BLOB)\n> +\t\tdie(\"oidset file is not a blob: %s\", name);\n> +\n> +\tread_oidset_string(set, fn, cbdata, buf, size);\n> +\tfree(buf);\n> +}\n> diff --git a/oidset.h b/oidset.h\n> index ba4a5a2cd3a..bad9246a150 100644\n> --- a/oidset.h\n> +++ b/oidset.h\n> @@ -84,6 +84,8 @@ void oidset_parse_file(struct oidset *set, const char *path);\n>   typedef int (*oidset_parse_tweak_fn)(struct object_id *, void *);\n>   void oidset_parse_file_carefully(struct oidset *set, const char *path,\n>   \t\t\t\t oidset_parse_tweak_fn fn, void *cbdata);\n> +void oidset_parse_blob(struct oidset *set, const char *path,\n> +\t\t\t\t oidset_parse_tweak_fn fn, void *cbdata);\n>   \n>   struct oidset_iter {\n>   \tkh_oid_set_t *set;\n> diff --git a/t/t8013-blame-ignore-revs.sh b/t/t8013-blame-ignore-revs.sh\n> index b18633dee1b..9d38a473a39 100755\n> --- a/t/t8013-blame-ignore-revs.sh\n> +++ b/t/t8013-blame-ignore-revs.sh\n> @@ -115,6 +115,25 @@ test_expect_success ignore_revs_from_configs_and_files '\n>   \ttest_cmp expect actual\n>   '\n>   \n> +# Ignore X from the config option, Y from a file.\n> +test_expect_success ignore_revs_from_configs_and_blobs '\n> +\tgit rev-parse X >ignore_x &&\n> +\tgit rev-parse Y >ignore_y &&\n> +\tgit add ignore_x ignore_y &&\n> +\tgit commit -m ignore &&\n> +\tgit config --add blame.ignoreRevsBlob HEAD:ignore_x &&\n> +\tgit blame --line-porcelain file --ignore-revs-blob HEAD:ignore_y >blame_raw 2>&1 &&\n> +\tgit config --unset blame.ignoreRevsBlob HEAD:ignore_x &&\n> +\n> +\tgrep -E \"^[0-9a-f]+ [0-9]+ 1\" blame_raw | sed -e \"s/ .*//\" >actual &&\n> +\tgit rev-parse A >expect &&\n> +\ttest_cmp expect actual &&\n> +\n> +\tgrep -E \"^[0-9a-f]+ [0-9]+ 2\" blame_raw | sed -e \"s/ .*//\" >actual &&\n> +\tgit rev-parse B >expect &&\n> +\ttest_cmp expect actual\n> +'\n> +\n>   # Override blame.ignoreRevsFile (ignore_x) with an empty string.  X should be\n>   # blamed now for lines 1 and 2, since we are no longer ignoring X.\n>   test_expect_success override_ignore_revs_file '\n> \n> base-commit: 297ca895a27a6bbdb7906371d533f72a12ad25b2\n\n"}]}