All of lore.kernel.org
 help / color / mirror / Atom feed
From: Junio C Hamano <gitster@pobox.com>
To: "Ravi Mistry via GitGitGadget" <gitgitgadget@gmail.com>
Cc: git@vger.kernel.org,
	Abhijeetsingh Meena <abhijeet040403@gmail.com>,
	Kristoffer Haugsbakk <code@khaugsbakk.name>,
	Phillip Wood <phillip.wood@dunelm.org.uk>,
	Eric Sunshine <sunshine@sunshineco.com>,
	 Ravi Mistry <rmistry@google.com>
Subject: Re: [PATCH v2 2/2] blame: ignore revs in HEAD:.git-blame-ignore-revs
Date: Fri, 09 Oct 2026 13:17:09 -0700	[thread overview]
Message-ID: <xmqqbj92ochm.fsf@gitster.g> (raw)
In-Reply-To: <35e303d65bc378e733b1e9e8d6908a829352e857.1791493644.git.gitgitgadget@gmail.com> (Ravi Mistry via GitGitGadget's message of "Thu, 08 Oct 2026 21:07:24 +0000")

"Ravi Mistry via GitGitGadget" <gitgitgadget@gmail.com> writes:

> From: Ravi Mistry <rmistry@google.com>
>
> git-blame(1) can ignore a list of commits specified via
> --ignore-revs-file or the blame.ignoreRevsFile configuration option.
> This is useful for skipping uninteresting revisions such as tree-wide
> formatting changes, large-scale refactors, and code modernizations that
> would otherwise obscure genuine historical authorship.
>
> When revision-ignoring was introduced in commit ae3f36dea1 ("blame: add
> blame.ignoreRevsFile config option", 2019-10-18), it intentionally
> avoided adopting a default ignore file. At the time, the capability was
> new and unproven, so avoiding unrequested filesystem I/O or unexpected
> attribution shifts took priority over a project-wide default.
> Requiring explicit opt-in per clone was therefore the prudent design.
>
> Since then, maintaining a .git-blame-ignore-revs file in the repository
> root has become the de facto standard across the Git ecosystem, adopted
> by major hosting platforms (GitHub, GitLab, Gerrit) and prominent open
> source projects (such as Chromium and LLVM). As a consequence,
> developers frequently encounter a jarring mismatch: web interfaces
> seamlessly ignore formatting commits, but local git-blame(1) and
> git-annotate(1) runs do not, unless each user manually configures
> blame.ignoreRevsFile for every local checkout.
>
> Teach git-blame(1) and git-annotate(1) to automatically add the
> HEAD:.git-blame-ignore-revs blob, if it exists, as the initial element
> in the list of ignore-revs files in both bare and non-bare
> repositories. Reading the committed blob from HEAD rather than the
> working tree ensures that local runs match hosting platforms even when
> an untracked .git-blame-ignore-revs file is present or a tracked one
> has uncommitted local changes.
>
> To ensure consistent precedence and override semantics:
> - The default HEAD:.git-blame-ignore-revs entry is added before reading
>   configuration and CLI options, preserving user and repository config
>   overrides.
> - In git_blame_config(), blame.ignoreRevsFile entries are appended via
>   string_list_append() rather than inserted in sorted order via
>   string_list_insert() so that configuration entries preserve their
>   order relative to the initial default entry.
> - The HEAD:.git-blame-ignore-revs tree entry is resolved quietly via
>   get_oid_with_context(). Its mode is checked with S_ISREG() before
>   reading the object so that non-regular tree entries (such as a
>   committed symbolic link whose blob stores a target path rather than
>   revision IDs, a subdirectory, or a gitlink) are skipped instead of
>   being read and rejected as malformed object names. The blob is parsed
>   in memory via a new oidset_parse_buffer_carefully() helper in
>   oidset.c that shares line parsing with oidset_parse_file_carefully().
> - In build_ignorelist(), ignore-revs entries are processed starting
>   after the last empty string entry. This ensures setting
>   blame.ignoreRevsFile to "" or passing --ignore-revs-file "" or
>   --no-ignore-revs-file cleanly discards the default blob without
>   attempting to read or parse it, allowing users to bypass a malformed
>   default blob.
>
> Update documentation in blame-options.adoc and config/blame.adoc, and
> add comprehensive test coverage in t8013 for the default blob lookup,
> subdirectory invocations, bare repositories, uncommitted and untracked
> working-tree files, CLI and config overrides, committed symlink
> entries, and comments and whitespace handling.
>
> Based-on-patch-by: Abhijeetsingh Meena <abhijeet040403@gmail.com>
> Helped-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
> Helped-by: Phillip Wood <phillip.wood@dunelm.org.uk>
> Helped-by: Eric Sunshine <sunshine@sunshineco.com>

These Helped-by: drew my attention as none of these folks commented
on v1 of this series.  I do see they have helped the original series
<pull.1809.v2.git.1728707867.gitgitgadget@gmail.com>, but it is not
clear how much their inputs have survivied to this version.

They are all CC'ed so they can give their Acked-by: or Reviewed-by: 
on this round if they want.


[...]

> diff --git a/builtin/blame.c b/builtin/blame.c
> index 6741a7b9df..a730ee87ea 100644
> --- a/builtin/blame.c
> +++ b/builtin/blame.c
> @@ -769,7 +769,7 @@ static int git_blame_config(const char *var, const char *value,
>  		if (ret)
>  			return ret;
>  		if (str)
> -			string_list_insert(&ignore_revs_file_list, str);
> +			string_list_append(&ignore_revs_file_list, str);
>  		free(str);
>  		return 0;
>  	}

Good, and the log message is clear why we make this change.

> @@ -946,17 +946,52 @@ static int peel_to_commit_oid(struct object_id *oid_ret, void *cbdata)
>  	}
>  }
>  
> +static void parse_default_ignore_revs_blob(struct blame_scoreboard *sb,
> +					   const char *name)
> +{
> +	struct object_context oc;
> +	struct object_id oid;
> +	enum object_type type;
> +	size_t size;
> +	char *buf;
> +
> +	if (get_oid_with_context(the_repository, name, GET_OID_QUIETLY,
> +				 &oid, &oc))
> +		goto out;
> +	if (!S_ISREG(oc.mode))
> +		goto out;
> +
> +	buf = odb_read_object(the_repository->objects, &oid, &type, &size);
> +	if (!buf)
> +		goto out;
> +	if (type == OBJ_BLOB)
> +		oidset_parse_buffer_carefully(&sb->ignore_list, buf, size,
> +					      the_repository->hash_algo,
> +					      peel_to_commit_oid, sb);
> +	free(buf);
> +
> +out:
> +	object_context_release(&oc);
> +}

OK.

>  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;
> +	size_t start_idx = 0, idx;
> +
> +	for (idx = 0; idx < ignore_revs_file_list->nr; idx++) {
> +		if (!*ignore_revs_file_list->items[idx].string)
> +			start_idx = idx + 1;
> +	}

OK, we make two passes, and during the first pass, we find where the
last "empty" entry that signals "forget everything you have seen" is.

>  	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);
> +	for (idx = start_idx; idx < ignore_revs_file_list->nr; idx++) {

And we scan starting from there.  Very clean.

> +		i = &ignore_revs_file_list->items[idx];
> +		if (i->util)
> +			parse_default_ignore_revs_blob(sb, i->string);
>  		else
>  			oidset_parse_file_carefully(&sb->ignore_list, i->string,
>  						    the_repository->hash_algo,
> @@ -1036,6 +1071,8 @@ int cmd_blame(int argc,
>  	const char *const *opt_usage = cmd_is_annotate ? annotate_opt_usage : blame_opt_usage;
>  
>  	setup_default_color_by_age();
> +	string_list_append(&ignore_revs_file_list,
> +			   "HEAD:.git-blame-ignore-revs")->util = &sb;

Cute.  This takes advantage of the fact that everybody else just
appends to the string_list without populating the .util member.

> diff --git a/oidset.c b/oidset.c
> index 90d39204d3..8469d03b9b 100644
> --- a/oidset.c
> +++ b/oidset.c
> @@ -70,44 +70,76 @@ void oidset_parse_file(struct oidset *set, const char *path,
>  	oidset_parse_file_carefully(set, path, algop, NULL, NULL);
>  }
>  
> +static void parse_oidset_line(struct oidset *set, struct strbuf *sb,
> +			      const struct git_hash_algo *algop,
> +			      oidset_parse_tweak_fn fn, void *cbdata)
> +{
> +	const char *p;
> +	const char *name;
> +	struct object_id oid;
> +
> +	if (memchr(sb->buf, '\0', sb->len))
> +		die("invalid object name: %s", sb->buf);
> +
> +	/*
> +	 * 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;
> +
> +	if (parse_oid_hex_algop(sb->buf, &oid, &p, algop) || *p != '\0')
> +		die("invalid object name: %s", sb->buf);
> +	if (fn && fn(&oid, cbdata))
> +		return;
> +	oidset_insert(set, &oid);
> +}
> +
>  void oidset_parse_file_carefully(struct oidset *set, const char *path,
>  				 const struct git_hash_algo *algop,
>  				 oidset_parse_tweak_fn fn, void *cbdata)
>  {
>  	FILE *fp;
>  	struct strbuf sb = STRBUF_INIT;
> -	struct object_id oid;
>  
>  	fp = fopen(path, "r");
>  	if (!fp)
>  		die("could not open object name list: %s", path);
> -	while (!strbuf_getline(&sb, fp)) {
> -		const char *p;
> -		const char *name;
> -
> -		if (memchr(sb.buf, '\0', sb.len))
> -			die("invalid object name: %s", sb.buf);
> -
> -		/*
> -		 * 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)
> -			continue;
> -
> -		if (parse_oid_hex_algop(sb.buf, &oid, &p, algop) || *p != '\0')
> -			die("invalid object name: %s", sb.buf);
> -		if (fn && fn(&oid, cbdata))
> -			continue;
> -		oidset_insert(set, &oid);
> -	}
> +	while (!strbuf_getline(&sb, fp))
> +		parse_oidset_line(set, &sb, algop, fn, cbdata);
>  	if (ferror(fp))
>  		die_errno("Could not read '%s'", path);
>  	fclose(fp);
>  	strbuf_release(&sb);
>  }

Shouldn't the above refactoring have been part of the previous step
instead?

Thanks.

  reply	other threads:[~2026-10-09 20:17 UTC|newest]

Thread overview: 33+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-11 23:29 [PATCH] blame: default to ignoring revisions in .git-blame-ignore-revs Ravi Mistry via GitGitGadget
2026-09-30 14:59 ` Ravi Mistry
2026-10-05 15:37 ` Junio C Hamano
2026-10-05 21:12   ` Ravi Mistry
2026-10-07 17:43     ` Junio C Hamano
2026-10-07 18:07       ` Ravi Mistry
2026-10-08 21:07 ` [PATCH v2 0/2] blame: ignore revs in HEAD:.git-blame-ignore-revs by default Ravi Mistry via GitGitGadget
2026-10-08 21:07   ` [PATCH v2 1/2] blame: harden ignore-revs parser and tag peeling Ravi Mistry via GitGitGadget
2026-10-09  5:07     ` Junio C Hamano
2026-10-10 19:25       ` Ravi Mistry
2026-10-08 21:07   ` [PATCH v2 2/2] blame: ignore revs in HEAD:.git-blame-ignore-revs Ravi Mistry via GitGitGadget
2026-10-09 20:17     ` Junio C Hamano [this message]
2026-10-10  6:12       ` Eric Sunshine
2026-10-10 18:40       ` Ravi Mistry
2026-10-11 18:58     ` Junio C Hamano
2026-10-11 22:29       ` Ravi Mistry
2026-10-11 23:07         ` Junio C Hamano
2026-10-12  9:13           ` Phillip Wood
2026-10-12 16:38             ` Junio C Hamano
2026-10-12 19:21               ` Ravi Mistry
2026-10-12 21:13                 ` Junio C Hamano
2026-10-13 14:48                   ` Ravi Mistry
2026-10-11 22:21 ` [PATCH v3 0/2] blame: ignore revs in HEAD:.git-blame-ignore-revs by default Ravi Mistry via GitGitGadget
2026-10-11 22:21   ` [PATCH v3 1/2] blame: harden ignore-revs parser and tag peeling Ravi Mistry via GitGitGadget
2026-10-11 22:21   ` [PATCH v3 2/2] blame: ignore revs in HEAD:.git-blame-ignore-revs Ravi Mistry via GitGitGadget
2026-10-12  9:10     ` Phillip Wood
2026-10-12 15:48       ` Ravi Mistry
2026-10-12 19:17 ` [PATCH v4 0/2] blame: ignore revs in HEAD:.git-blame-ignore-revs by default Ravi Mistry via GitGitGadget
2026-10-12 19:17   ` [PATCH v4 1/2] blame: harden ignore-revs parser and tag peeling Ravi Mistry via GitGitGadget
2026-10-12 19:17   ` [PATCH v4 2/2] blame: ignore revs in HEAD:.git-blame-ignore-revs Ravi Mistry via GitGitGadget
2026-10-13 14:36 ` [PATCH v5 0/2] blame: ignore revs in .git-blame-ignore-revs by default Ravi Mistry via GitGitGadget
2026-10-13 14:36   ` [PATCH v5 1/2] blame: harden ignore-revs parser and tag peeling Ravi Mistry via GitGitGadget
2026-10-13 14:36   ` [PATCH v5 2/2] blame: ignore revs in .git-blame-ignore-revs by default Ravi Mistry via GitGitGadget

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=xmqqbj92ochm.fsf@gitster.g \
    --to=gitster@pobox.com \
    --cc=abhijeet040403@gmail.com \
    --cc=code@khaugsbakk.name \
    --cc=git@vger.kernel.org \
    --cc=gitgitgadget@gmail.com \
    --cc=phillip.wood@dunelm.org.uk \
    --cc=rmistry@google.com \
    --cc=sunshine@sunshineco.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.