Re: [PATCH] revision: make get_commit_action() a pure predicate

Junio C Hamano <[email protected]>
Newsgroups org.kernel.vger.git
Message-ID <[email protected]>
"Michael Montalbo via GitGitGadget" <[email protected]> writes:

> commit_early_ignore() runs twice on the -L path, once for that gate and
> once inside get_commit_action(), but it reads only object flags and pack
> membership, disjoint from the TREESAME flag the fold sets, so the repeat
> is harmless.

This one confused me a bit, so I'll think aloud below to see if you
can spot where I am misunderstanding your code.

> +/*
> + * Whether the commit is ignored by the cheap checks that read only its
> + * traversal flags and pack membership (e.g. already shown, or marked
> + * uninteresting), before any check that examines the commit's date,
> + * parents, message, or diff.
> + */
> +static int commit_early_ignore(struct rev_info *revs, struct commit *commit)
>  {
>  	if (commit->object.flags & SHOWN)
> -		return commit_ignore;
> +		return 1;
>  	if (revs->maximal_only && (commit->object.flags & CHILD_VISITED))
> -		return commit_ignore;
> +		return 1;
>  	if (revs->unpacked && has_object_pack(revs->repo, &commit->object.oid))
> -		return commit_ignore;
> -	if (revs->no_kept_objects) {
> -		if (has_object_kept_pack(revs->repo, &commit->object.oid,
> -					 revs->keep_pack_cache_flags))
> -			return commit_ignore;
> -	}
> +		return 1;
> +	if (revs->no_kept_objects &&
> +	    has_object_kept_pack(revs->repo, &commit->object.oid,
> +				 revs->keep_pack_cache_flags))
> +		return 1;
>  	if (commit->object.flags & UNINTERESTING)
> +		return 1;
> +	return 0;
> +}

This mirrors what the original get_commit_action() did to return
early with 'commit_ignore'.  Collapsing the nested 'if' for the
kept-objects case is a nice touch that makes the result easier to
follow.

> +/*
> + * Decide whether this commit is shown or ignored.  Keep it a pure
> + * predicate: callers such as the commit graph depend on it having no
> + * side effects, so per-commit mutations (such as -L range tracking)
> + * belong in the caller, simplify_commit(), not here.
> + */
> +enum commit_action get_commit_action(struct rev_info *revs, struct commit *commit)
> +{
> +	if (commit_early_ignore(revs, commit))
>  		return commit_ignore;
> -	if (revs->line_level_traverse && !want_ancestry(revs)) {
> -		/*
> -		 * In case of line-level log with parent rewriting
> -		 * prepare_revision_walk() already took care of all line-level
> -		 * log filtering, and there is nothing left to do here.
> -		 *
> -		 * If parent rewriting was not requested, then this is the
> -		 * place to perform the line-level log filtering.  Notably,
> -		 * this check, though expensive, must come before the other,
> -		 * cheaper filtering conditions, because the tracked line
> -		 * ranges must be adjusted even when the commit will end up
> -		 * being ignored based on other conditions.
> -		 */
> -		if (!line_log_process_ranges_arbitrary_commit(revs, commit))
> -			return commit_ignore;
> -	}
>  	if (revs->min_age != -1 &&
>  	    comparison_date(revs, commit) > revs->min_age)
>  			return commit_ignore;
> @@ -4314,7 +4316,23 @@ struct commit_list *get_saved_parents(struct rev_info *revs, const struct commit
>  
>  enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)
>  {
> -	enum commit_action action = get_commit_action(revs, commit);
> +	enum commit_action action;
> +
> +	/*
> +	 * For a line-level log without parent rewriting, fold each commit's
> +	 * ranges as the walk reaches it (parent rewriting does this eagerly in
> +	 * prepare_revision_walk()).  Fold before get_commit_action() so the
> +	 * ranges carry across a commit that a later, cheaper check ignores;
> +	 * the commit_early_ignore() guard skips a commit get_commit_action()
> +	 * would ignore outright.
> +	 */
> +	if (revs->line_level_traverse && !want_ancestry(revs) &&
> +	    !commit_early_ignore(revs, commit)) {
> +		if (!line_log_process_ranges_arbitrary_commit(revs, commit))
> +			return commit_ignore;
> +	}
> +
> +	action = get_commit_action(revs, commit);

The primary change in the patch is to lift the "line-level" code out
of get_commit_action() and move it to one of its callers (namely
simplify_commit()).  The other caller is known not to trigger the
affected parts of the function, which was discussed previously at
https://lore.kernel.org/git/[email protected]/ and started
this leftover bit.

We used to call get_commit_action() to decide the fate of the
commit.  If get_commit_action() returned anything other than
'commit_show', simplify_commit() simply returned that action without
doing anything further.

The original get_commit_action(), when on the code path that calls
line_log_process_ranges_arbitrary_commit() to check if we want to
ignore this commit, did what the commit_early_ignore() helper does
in this version before reaching that point.  So this updated caller
in simplify_commit() recreates the exact same logic.

We do end up executing the commit_early_ignore() logic twice if
line_log_process_ranges_arbitrary_commit() does not tell us to ignore
this commit.  With only two callers of get_commit_action(), we could
easily reuse the result of commit_early_ignore() if we wanted to, but
it is probably not worth it.

So the patch looks good.  Will queue.  Thanks.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.