From: Toon Claes <toon@iotcl.com>
To: Miklos Vajna <vmiklos@collabora.com>, gitster@pobox.com
Cc: szeder.dev@gmail.com, ps@pks.im, git@vger.kernel.org
Subject: Re: [PATCH v5 4/5] log: simplify --follow following renames for non-linear history
Date: Tue, 10 Nov 2026 17:45:50 +0100 [thread overview]
Message-ID: <877bik63ep.fsf@dev.null.iotcl.com.invalid> (raw)
In-Reply-To: <5ba93e49cff31e61621ac166f2d60b8133937f20.1794078554.git.vmiklos@collabora.com>
Miklos Vajna <vmiklos@collabora.com> writes:
> Code added in 304812ed33 (log: improve --follow following renames for
> non-linear history, 2026-06-22) implements the feature by introducing a
> new propagate_follow_pathspec_to_parent() function which adds a new call
> to diff_tree_oid(). Also it introduces a new loop to record what path
> each parent of a merge commit should use when following a path.
>
> This works, but it causes a performance regression: e.g. 'git log
> --stat --follow -- include/linux/rsc_table.h' in linux.git takes 6m43s,
> while it took 28s before that commit. The new code diffs each merge
> against each of its parents with rename detection, which is expensive
> when a parent doesn't have the followed path yet. Fixing this would
> require adding even more custom code, instead of just extending the
> existing code in log_tree_diff(). So first simplify the code here, and
> fix the performance regression in a follow-up commit.
>
> Remove propagate_follow_pathspec_to_parent() and the loop calling it
> for each parent. Instead, record the path to follow for each parent in
> the parent loop of log_tree_diff(), right after diff_tree_oid() updates
> the pathspec in case of a rename. Merges are not diffed by default, so
> with --follow, run the same loop for them as well, but without showing
> anything. With -c, --cc or --remerge-diff, show the merge diff after the
> loop. With --diff-merges=first-parent, keep diffing the other parents
> silently, unless the walk only follows first parents. This gets rid of
> the additional call to diff_tree_oid(). The rework has no positive
> performance impact yet, it just simplifies the code.
>
> The previous commit restores the path of the merge before diffing each
> parent, and also after the loop, for
> propagate_follow_pathspec_to_parent(). Keep restoring it between
> parents, but not after the loop, since there is no later propagation
> anymore.
>
> Signed-off-by: Miklos Vajna <vmiklos@collabora.com>
> ---
> log-tree.c | 96 +++++++++++++++++++++++-------------------------------
> 1 file changed, 41 insertions(+), 55 deletions(-)
>
> diff --git a/log-tree.c b/log-tree.c
> index befc5c8b22..0aa27fb210 100644
> --- a/log-tree.c
> +++ b/log-tree.c
> @@ -1153,33 +1153,6 @@ void release_follow_pathspec_slab(struct rev_info *opt)
> FREE_AND_NULL(opt->follow_pathspec_slab);
> }
>
> -/* Compute a path to follow in parent, if there is one */
> -static void propagate_follow_pathspec_to_parent(struct rev_info *opt,
> - struct commit *commit,
> - struct commit *parent)
> -{
> - struct diff_options diff_opts;
> - const char *path;
> -
> - parse_commit_or_die(parent);
> - repo_diff_setup(opt->diffopt.repo, &diff_opts);
> - copy_pathspec(&diff_opts.pathspec, &opt->diffopt.pathspec);
> - diff_opts.flags.recursive = 1;
> - diff_opts.flags.follow_renames = 1;
> - diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
> - diff_setup_done(&diff_opts);
> - diff_tree_oid(get_commit_tree_oid(parent),
> - get_commit_tree_oid(commit),
> - "", &diff_opts);
> -
> - path = pathspec_single_path(&diff_opts.pathspec);
> - if (path)
> - remember_follow_pathspec(opt, parent, path);
> -
> - diff_queue_clear(&diff_queued_diff);
> - diff_free(&diff_opts);
> -}
> -
> /* Show the remerge or combined diff of a merge commit */
> static int log_tree_merge_diff(struct rev_info *opt, struct commit *commit,
> struct commit_list *parents,
> @@ -1208,10 +1181,11 @@ static int log_tree_merge_diff(struct rev_info *opt, struct commit *commit,
> static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log_info *log)
> {
> int showed_log;
> - struct commit_list *parents;
> + struct commit_list *parents, *all_parents;
> struct object_id *oid;
> int is_merge;
> int all_need_diff = opt->diff || opt->diffopt.flags.exit_with_status;
> + int follow_only = 0;
> char *path = NULL;
>
> if (!all_need_diff && !opt->merges_need_diff)
> @@ -1241,46 +1215,73 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log
> }
>
> if (is_merge) {
> - if (opt->remerge_diff || opt->combine_merges)
> + int merge_diff = opt->remerge_diff || opt->combine_merges;
> +
> + if (merge_diff && !opt->diffopt.flags.follow_renames)
> return log_tree_merge_diff(opt, commit, parents, oid);
> - if (opt->separate_merges) {
> + if (opt->separate_merges && !merge_diff) {
> if (!opt->first_parent_merges) {
> /* Show parent info for multiple diffs */
> log->parent = parents->item;
> }
> + } else if (opt->diffopt.flags.follow_renames) {
> + /* Diff only to find the path to follow in parents */
> + follow_only = 1;
> } else
> return 0;
This part of code has become pretty tough to read if you ask me. I've
tried to rewrite it:
if (is_merge) {
/* -c, --cc or --remerge-diff: show one diff for the whole merge. */
int merge_diff = opt->remerge_diff || opt->combine_merges;
/* Without --follow the parent loop is only needed for -m. */
if (!opt->diffopt.flags.follow_renames) {
/* -c, --cc or --remerge-diff: show it, no parent loop. */
if (merge_diff)
return log_tree_merge_diff(opt, commit, parents, oid);
/* No -m either, i.e. merge diffs are off: show nothing. */
if (!opt->separate_merges)
return 0;
}
/* Run the loop silently, unless -m shows a diff per parent. */
follow_only = !opt->separate_merges || merge_diff;
/* -m showing all parents: label each diff "(from <parent>)". */
if (!follow_only && !opt->first_parent_merges)
/* Label the first diff, others are labeled in parent loop. */
log->parent = parents->item;
}
The comments I've added are possibly too verbose for the Git project,
but they helped me understand what happens at each line. I hope it's
functionally the same?
(This is as far as I have gotten in the review today, but I wanted to
send this out already)
> }
>
> - if (is_merge && opt->diffopt.flags.follow_renames)
> + if (opt->diffopt.flags.follow_renames)
> path = xstrdup_or_null(
> pathspec_single_path(&opt->diffopt.pathspec));
>
> showed_log = 0;
> + all_parents = parents;
> for (;;) {
> struct commit *parent = parents->item;
>
> - /* Diff each parent with the path of the merge */
> - if (path)
> - set_pathspec_to_single_path(&opt->diffopt.pathspec,
> - path);
> parse_commit_or_die(parent);
> diff_tree_oid(get_commit_tree_oid(parent),
> oid, "", &opt->diffopt);
> - log_tree_diff_flush(opt);
> + if (follow_only) {
> + diff_queue_clear(&diff_queued_diff);
> + opt->diffopt.found_follow = 0;
> + } else {
> + log_tree_diff_flush(opt);
> + }
>
> showed_log |= !opt->loginfo;
>
> + /* The diff updated the pathspec in case of a rename */
> + if (path)
> + remember_follow_pathspec(opt, parent,
> + pathspec_single_path(&opt->diffopt.pathspec));
> +
> /* Set up the log info for the next parent, if any.. */
> parents = parents->next;
> - if (!parents || opt->first_parent_merges)
> + if (!parents)
> break;
> + if (opt->first_parent_merges) {
> + if (!path || opt->first_parent_only)
> + break;
> + /* Only find the path to follow in the other parents */
> + follow_only = 1;
> + }
> + if (path)
> + set_pathspec_to_single_path(&opt->diffopt.pathspec,
> + path);
> + if (follow_only)
> + continue;
> log->parent = parents->item;
> opt->loginfo = log;
> }
> - /* Propagate to parents starting from the path of the merge, too */
> - if (path)
> - set_pathspec_to_single_path(&opt->diffopt.pathspec, path);
> + if (follow_only && (opt->remerge_diff || opt->combine_merges)) {
> + /* Show the merge diff with the path of this commit */
> + if (path)
> + set_pathspec_to_single_path(&opt->diffopt.pathspec,
> + path);
> + showed_log = log_tree_merge_diff(opt, commit, all_parents, oid);
> + }
> free(path);
> return showed_log;
> }
> @@ -1319,21 +1320,6 @@ int log_tree_commit(struct rev_info *opt, struct commit *commit)
> fprintf(opt->diffopt.file, "\n%s\n", opt->break_bar);
> if (shown)
> show_diff_of_diff(opt);
> -
> - /* Record what path each parent of this commit should use */
> - if (opt->diffopt.flags.follow_renames) {
> - struct commit_list *parents = get_saved_parents(opt, commit);
> - if (parents && parents->next) {
> - struct commit_list *p;
> - for (p = parents; p; p = p->next)
> - propagate_follow_pathspec_to_parent(opt, commit,
> - p->item);
> - } else if (parents) {
> - remember_follow_pathspec(opt, parents->item,
> - pathspec_single_path(&opt->diffopt.pathspec));
> - }
> - }
> -
> opt->loginfo = NULL;
> maybe_flush_or_die(opt->diffopt.file, "stdout");
> opt->diffopt.no_free = no_free;
> --
> 2.51.0
>
--
Laters,
Toon
next prev parent reply other threads:[~2026-11-10 16:45 UTC|newest]
Thread overview: 43+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-13 13:55 Performance regression in `git log --follow` Patrick Steinhardt
2026-10-13 21:24 ` SZEDER Gábor
2026-10-15 14:48 ` [PATCH] log: speed up --follow following renames for non-linear history Miklos Vajna
2026-10-15 16:57 ` Junio C Hamano
2026-10-19 6:10 ` [PATCH v2] " Miklos Vajna
2026-10-22 20:45 ` [PATCH v3 0/4] " Miklos Vajna
2026-10-22 20:45 ` [PATCH v3 1/4] t4219: test more --follow cases across merges Miklos Vajna
2026-10-22 20:46 ` [PATCH v3 2/4] log: extract showing the remerge/combined diff of a merge commit Miklos Vajna
2026-10-22 20:46 ` [PATCH v3 3/4] log: simplify --follow following renames for non-linear history Miklos Vajna
2026-10-26 11:40 ` Patrick Steinhardt
2026-10-22 20:47 ` [PATCH v3 4/4] log: speed up " Miklos Vajna
2026-10-26 11:40 ` Patrick Steinhardt
2026-10-26 11:42 ` [PATCH v3 0/4] " Patrick Steinhardt
2026-10-30 20:07 ` [PATCH v4 0/6] " Miklos Vajna
2026-10-30 20:07 ` [PATCH v4 1/6] t4219: test more --follow cases across merges Miklos Vajna
2026-10-30 20:07 ` [PATCH v4 2/6] log: extract showing the remerge/combined diff of a merge commit Miklos Vajna
2026-10-30 20:08 ` [PATCH v4 3/6] log: fix -m --follow losing the history of later parents Miklos Vajna
2026-10-30 20:08 ` [PATCH v4 4/6] log: simplify --follow following renames for non-linear history Miklos Vajna
2026-10-30 20:09 ` [PATCH v4 5/6] log: speed up " Miklos Vajna
2026-11-06 14:14 ` Patrick Steinhardt
2026-11-09 8:02 ` Miklos Vajna
2026-10-30 20:10 ` [PATCH v4 6/6] log: fix --follow following an edit on a side the merge did not take Miklos Vajna
2026-11-06 14:14 ` Patrick Steinhardt
2026-11-09 8:03 ` Miklos Vajna
2026-11-06 14:14 ` [PATCH v4 0/6] log: speed up --follow following renames for non-linear history Patrick Steinhardt
2026-11-09 8:01 ` Miklos Vajna
2026-11-09 8:03 ` [PATCH v5 0/5] " Miklos Vajna
2026-11-09 8:04 ` [PATCH v5 1/5] t4219: test more --follow cases across merges Miklos Vajna
2026-11-10 23:06 ` Justin Tobler
2026-11-09 8:05 ` [PATCH v5 2/5] log: extract showing the remerge/combined diff of a merge commit Miklos Vajna
2026-11-09 8:05 ` [PATCH v5 3/5] log: fix -m --follow losing the history of later parents Miklos Vajna
2026-11-09 8:05 ` [PATCH v5 4/5] log: simplify --follow following renames for non-linear history Miklos Vajna
2026-11-10 16:45 ` Toon Claes [this message]
2026-11-09 8:06 ` [PATCH v5 5/5] log: speed up " Miklos Vajna
2026-11-11 0:40 ` [PATCH v5 0/5] " Justin Tobler
2026-11-11 10:43 ` Patrick Steinhardt
2026-11-11 12:41 ` Miklos Vajna
2026-11-11 13:29 ` Patrick Steinhardt
2026-11-12 9:05 ` [PATCH] log: make --follow following renames for non-linear history optional Miklos Vajna
2026-11-12 19:09 ` Junio C Hamano
2026-11-11 16:44 ` [PATCH v5 0/5] log: speed up --follow following renames for non-linear history Junio C Hamano
2026-10-16 20:37 ` [PATCH] " Junio C Hamano
2026-10-14 6:16 ` Performance regression in `git log --follow` Miklos Vajna
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=877bik63ep.fsf@dev.null.iotcl.com.invalid \
--to=toon@iotcl.com \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
--cc=ps@pks.im \
--cc=szeder.dev@gmail.com \
--cc=vmiklos@collabora.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.