From: David Barr <git@xxxxxxxxxxxx> In a bare repository, there isn't a simple way to ignore revisions via a file without extracting it to a temporary file. This patch provides a command-line option and config variable, similar to --ignore-revs-file and blame.ignoreRevsFile, which reads the list of revisions to ignore from a blob in the repository. Signed-off-by: David Barr <git@xxxxxxxxxxxx> --- blame: Add --ignore-revs-blob and blame.ignoreRevsBlob In a bare repository, there isn't a simple way to ignore revisions via a file without extracting it to a temporary file. This patch provides a command-line option and config variable, similar to --ignore-revs-file and blame.ignoreRevsFile, which reads the list of revisions to ignore from a blob in the repository. Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1204%2Fdavebarrau%2Fadd-ignore-revs-blob-v1 Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1204/davebarrau/add-ignore-revs-blob-v1 Pull-Request: https://github.com/git/git/pull/1204 Documentation/blame-options.txt | 10 ++-- Documentation/config/blame.txt | 7 ++- builtin/blame.c | 23 ++++++++- oidset.c | 84 ++++++++++++++++++++++++++------- oidset.h | 2 + t/t8013-blame-ignore-revs.sh | 19 ++++++++ 6 files changed, 124 insertions(+), 21 deletions(-) diff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt index 9a663535f44..d6e59c57983 100644 --- a/Documentation/blame-options.txt +++ b/Documentation/blame-options.txt @@ -132,9 +132,13 @@ take effect. --ignore-revs-file <file>:: Ignore revisions listed in `file`, which must be in the same format as an `fsck.skipList`. This option may be repeated, and these files will be - processed after any files specified with the `blame.ignoreRevsFile` config - option. An empty file name, `""`, will clear the list of revs from - previously processed files. + processed after any files specified with the `blame.ignoreRevsFile` or + `blame.ignoreRevsBlob` config options. An empty file name, `""`, will + clear the list of revs from previously processed files. + +--ignore-revs-blob <blob>:: + Like `--ignore-revs-file`, but consider the value as a reference to a blob + in the repository. --color-lines:: Color line annotations in the default format differently if they come from diff --git a/Documentation/config/blame.txt b/Documentation/config/blame.txt index 4d047c17908..109ca796de0 100644 --- a/Documentation/config/blame.txt +++ b/Documentation/config/blame.txt @@ -25,7 +25,12 @@ blame.ignoreRevsFile:: line, in linkgit:git-blame[1]. Whitespace and comments beginning with `#` are ignored. This option may be repeated multiple times. Empty file names will reset the list of ignored revisions. This option will - be handled before the command line option `--ignore-revs-file`. + be handled before the command line options `--ignore-revs-file` and + `--ignore-revs-blob`. + +blame.ignoreRevsBlob:: + Like `blame.ignoreRevsFile`, but consider the value as a reference to + a blob in the repository. blame.markUnblamableLines:: Mark lines that were changed by an ignored revision that we could not diff --git a/builtin/blame.c b/builtin/blame.c index 7fafeac4081..c70c99cfda5 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -54,6 +54,7 @@ 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 struct string_list ignore_revs_blob_list = STRING_LIST_INIT_NODUP; static int mark_unblamable_lines; static int mark_ignored_lines; @@ -711,6 +712,16 @@ static int git_blame_config(const char *var, const char *value, void *cb) string_list_insert(&ignore_revs_file_list, str); return 0; } + if (!strcmp(var, "blame.ignorerevsblob")) { + const char *str; + int ret; + + ret = git_config_string(&str, var, value); + if (ret) + return ret; + string_list_insert(&ignore_revs_blob_list, str); + return 0; + } if (!strcmp(var, "blame.markunblamablelines")) { mark_unblamable_lines = git_config_bool(var, value); return 0; @@ -822,6 +833,7 @@ static int peel_to_commit_oid(struct object_id *oid_ret, void *cbdata) static void build_ignorelist(struct blame_scoreboard *sb, struct string_list *ignore_revs_file_list, + struct string_list *ignore_revs_blob_list, struct string_list *ignore_rev_list) { struct string_list_item *i; @@ -835,6 +847,13 @@ static void build_ignorelist(struct blame_scoreboard *sb, oidset_parse_file_carefully(&sb->ignore_list, i->string, peel_to_commit_oid, sb); } + for_each_string_list_item(i, ignore_revs_blob_list) { + if (!strcmp(i->string, "")) + oidset_clear(&sb->ignore_list); + else + oidset_parse_blob(&sb->ignore_list, i->string, + peel_to_commit_oid, sb); + } for_each_string_list_item(i, ignore_rev_list) { if (get_oid_committish(i->string, &oid) || peel_to_commit_oid(&oid, sb)) @@ -878,6 +897,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) 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_STRING_LIST(0, "ignore-revs-blob", &ignore_revs_blob_list, N_("blob"), N_("ignore revisions from <blob>")), 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), OPT_BIT(0, "minimal", &xdl_opts, N_("spend extra cycles to find better match"), XDF_NEED_MINIMAL), @@ -1084,8 +1104,9 @@ parse_done: sb.reverse = reverse; sb.repo = the_repository; sb.path = path; - build_ignorelist(&sb, &ignore_revs_file_list, &ignore_rev_list); + build_ignorelist(&sb, &ignore_revs_file_list, &ignore_revs_blob_list, &ignore_rev_list); string_list_clear(&ignore_revs_file_list, 0); + string_list_clear(&ignore_revs_blob_list, 0); string_list_clear(&ignore_rev_list, 0); setup_scoreboard(&sb, &o); diff --git a/oidset.c b/oidset.c index b36a2bae864..0cca63700da 100644 --- a/oidset.c +++ b/oidset.c @@ -1,5 +1,6 @@ #include "cache.h" #include "oidset.h" +#include "object-store.h" void oidset_init(struct oidset *set, size_t initial_size) { @@ -41,6 +42,29 @@ void oidset_parse_file(struct oidset *set, const char *path) oidset_parse_file_carefully(set, path, NULL, NULL); } +static int read_oidset_line(struct strbuf sb, struct object_id *oid) +{ + const char *p; + const char *name; + + /* + * Allow trailing comments, leading whitespace + * (including before commits), and empty or whitespace + * only lines. + */ + name = strchr(sb.buf, '#'); + if (name) + strbuf_setlen(&sb, name - sb.buf); + strbuf_trim(&sb); + if (!sb.len) + return 0; + + if (parse_oid_hex(sb.buf, oid, &p) || *p != '\0') + die("invalid object name: %s", sb.buf); + + return 1; +} + void oidset_parse_file_carefully(struct oidset *set, const char *path, oidset_parse_tweak_fn fn, void *cbdata) { @@ -52,23 +76,8 @@ void oidset_parse_file_carefully(struct oidset *set, const char *path, if (!fp) die("could not open object name list: %s", path); while (!strbuf_getline(&sb, fp)) { - const char *p; - const char *name; - - /* - * Allow trailing comments, leading whitespace - * (including before commits), and empty or whitespace - * only lines. - */ - name = strchr(sb.buf, '#'); - if (name) - strbuf_setlen(&sb, name - sb.buf); - strbuf_trim(&sb); - if (!sb.len) + if (!read_oidset_line(sb, &oid)) continue; - - if (parse_oid_hex(sb.buf, &oid, &p) || *p != '\0') - die("invalid object name: %s", sb.buf); if (fn && fn(&oid, cbdata)) continue; oidset_insert(set, &oid); @@ -78,3 +87,46 @@ void oidset_parse_file_carefully(struct oidset *set, const char *path, fclose(fp); strbuf_release(&sb); } + +static void read_oidset_string(struct oidset *set, oidset_parse_tweak_fn fn, + void *cbdata, const char *buf, unsigned long size) +{ + struct object_id oid; + struct strbuf **lines; + struct strbuf **line; + + lines = strbuf_split_buf(buf, size, '\n', 0); + + for (line = lines; *line; line++) { + if (!read_oidset_line(**line, &oid)) + continue; + if (fn && fn(&oid, cbdata)) + continue; + oidset_insert(set, &oid); + } + strbuf_list_free(lines); +} + +void oidset_parse_blob(struct oidset *set, const char *name, + oidset_parse_tweak_fn fn, void *cbdata) +{ + struct object_id oid; + char *buf; + unsigned long size; + enum object_type type; + + if (!name) { + return; + } + if (get_oid(name, &oid) < 0) { + die("unable to read object id for %s", name); + } + buf = read_object_file(&oid, &type, &size); + if (!buf) + die("unable to read oidset file at %s", name); + if (type != OBJ_BLOB) + die("oidset file is not a blob: %s", name); + + read_oidset_string(set, fn, cbdata, buf, size); + free(buf); +} diff --git a/oidset.h b/oidset.h index ba4a5a2cd3a..bad9246a150 100644 --- a/oidset.h +++ b/oidset.h @@ -84,6 +84,8 @@ void oidset_parse_file(struct oidset *set, const char *path); typedef int (*oidset_parse_tweak_fn)(struct object_id *, void *); void oidset_parse_file_carefully(struct oidset *set, const char *path, oidset_parse_tweak_fn fn, void *cbdata); +void oidset_parse_blob(struct oidset *set, const char *path, + oidset_parse_tweak_fn fn, void *cbdata); struct oidset_iter { kh_oid_set_t *set; diff --git a/t/t8013-blame-ignore-revs.sh b/t/t8013-blame-ignore-revs.sh index b18633dee1b..9d38a473a39 100755 --- a/t/t8013-blame-ignore-revs.sh +++ b/t/t8013-blame-ignore-revs.sh @@ -115,6 +115,25 @@ test_expect_success ignore_revs_from_configs_and_files ' test_cmp expect actual ' +# Ignore X from the config option, Y from a file. +test_expect_success ignore_revs_from_configs_and_blobs ' + git rev-parse X >ignore_x && + git rev-parse Y >ignore_y && + git add ignore_x ignore_y && + git commit -m ignore && + git config --add blame.ignoreRevsBlob HEAD:ignore_x && + git blame --line-porcelain file --ignore-revs-blob HEAD:ignore_y >blame_raw 2>&1 && + git config --unset blame.ignoreRevsBlob HEAD:ignore_x && + + grep -E "^[0-9a-f]+ [0-9]+ 1" blame_raw | sed -e "s/ .*//" >actual && + git rev-parse A >expect && + test_cmp expect actual && + + grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual && + git rev-parse B >expect && + test_cmp expect actual +' + # Override blame.ignoreRevsFile (ignore_x) with an empty string. X should be # blamed now for lines 1 and 2, since we are no longer ignoring X. test_expect_success override_ignore_revs_file ' base-commit: 297ca895a27a6bbdb7906371d533f72a12ad25b2 -- gitgitgadget