Git development
 help / color / mirror / Atom feed
From: Miklos Vajna <vmiklos@collabora.com>
To: gitster@pobox.com
Cc: szeder.dev@gmail.com, ps@pks.im, git@vger.kernel.org
Subject: [PATCH v5 0/5] log: speed up --follow following renames for non-linear history
Date: Mon, 9 Nov 2026 09:03:33 +0100	[thread overview]
Message-ID: <cover.1794078554.git.vmiklos@collabora.com> (raw)
In-Reply-To: <cover.1793298331.git.vmiklos@collabora.com>

Hi Patrick,

Thanks for the reviews of v4. Changes in v5:

- The fix of the old 6/6 is now squashed into the speedup patch (5/5),
  so no commit in the series loses history, and its test is moved to
  1/5. While at it, I simplified 5/5: instead of two passes of diffs
  against the parents, it first finds a parent where the path is
  unchanged, and then only diffs against the other parents if they
  still need renames to be followed. Because of the squash, the
  range-diff pairs the new 5/5 with the old 6/6.

- 5/5 now also follows renames when a parent has no merge base with
  the parent where the path is unchanged, e.g. a subtree merge of
  unrelated histories. Added a test for this to 1/5.

- 5/5 now also follows copies, not only renames, when the merge dropped
  the edit of the copy source. Added your test for this to 1/5. With
  this, the linux.git case takes about 42s, instead of 37s.

Thanks,

Miklos

Miklos Vajna (5):
  t4219: test more --follow cases across merges
  log: extract showing the remerge/combined diff of a merge commit
  log: fix -m --follow losing the history of later parents
  log: simplify --follow following renames for non-linear history
  log: speed up --follow following renames for non-linear history

 log-tree.c                  | 258 +++++++++++++++++++++++++++++-------
 t/t4219-log-follow-merge.sh | 230 ++++++++++++++++++++++++++++++++
 2 files changed, 438 insertions(+), 50 deletions(-)

Range-diff against v4:
1:  aabf5652c9 ! 1:  5512aa682f t4219: test more --follow cases across merges
    @@ Commit message
         t4219: test more --follow cases across merges
     
         A later commit in this series will speed up 'git log --follow' for
    -    merge commits. Add tests for cases which already work, but had no
    +    merge commits. Add tests for cases which already work, but have no
         test coverage, so it's easy to break them while optimizing:
     
    +    - a subtree merge adding the history of a file that was copied before
         - --follow through a fork with renames on both sides, with -c, --cc
           and --remerge-diff
         - a --no-ff merge taking a rename from the second parent, in both commit
    @@ Commit message
         - a merge keeping the path, while the other side renamed it
         - --diff-merges=first-parent, which diffs merges only against their
           first parent, but still walks all parents
    +    - a merge keeping the renamed path, while the other side edited the old
    +      one
    +    - a merge keeping the copied path, while the other side edited the
    +      original
     
    +    Helped-by: Patrick Steinhardt <ps@pks.im>
         Signed-off-by: Miklos Vajna <vmiklos@collabora.com>
     
      ## t/t4219-log-follow-merge.sh ##
    +@@ t/t4219-log-follow-merge.sh: test_expect_success '--follow finds the pre-merge commit through a subtree merge
    + 	test_cmp expect actual
    + '
    + 
    ++test_expect_success 'setup subtree merge adding the history of a copied file' '
    ++	git init graft &&
    ++	echo outer >graft/outer.txt &&
    ++	git -C graft add outer.txt &&
    ++	test_tick &&
    ++	git -C graft commit -m "outer init" &&
    ++	mkdir graft/inner &&
    ++	cp inner/inner.txt graft/inner/ &&
    ++	git -C graft add inner &&
    ++	test_tick &&
    ++	git -C graft commit -m "copy inner.txt to inner/" &&
    ++
    ++	git -C graft fetch ../inner master &&
    ++	test_tick &&
    ++	git -C graft merge -s ours --allow-unrelated-histories \
    ++		-m "Merge the history of inner/" FETCH_HEAD
    ++'
    ++
    ++test_expect_success '--follow finds the history of a copied file through a subtree merge' '
    ++	git -C graft log --follow --pretty=tformat:%s inner/inner.txt >actual &&
    ++	cat >expect <<-\EOF &&
    ++	copy inner.txt to inner/
    ++	inner init
    ++	EOF
    ++	test_cmp expect actual
    ++'
    ++
    + test_expect_success 'setup merge of two branches that both renamed a file to README' '
    + 	git init foo &&
    + 	mkdir foo/foo &&
     @@ t/t4219-log-follow-merge.sh: test_expect_success '--follow follows renames through a fork in a single history
      	test_cmp expect actual.sorted
      '
    @@ t/t4219-log-follow-merge.sh: test_expect_success '--follow follows renames throu
     +	EOF
     +	test_cmp expect actual
     +'
    ++
    ++test_expect_success 'setup merge keeping the renamed path, other side edited the old one' '
    ++	git init keep-renamed &&
    ++	printf "line 1\nline 2\nline 3\nline 4\nline 5\n" >keep-renamed/F &&
    ++	git -C keep-renamed add F &&
    ++	test_tick &&
    ++	git -C keep-renamed commit -m "O: add F" &&
    ++	git -C keep-renamed branch B &&
    ++	git -C keep-renamed mv F G &&
    ++	test_tick &&
    ++	git -C keep-renamed commit -m "A1: rename F to G on master" &&
    ++
    ++	git -C keep-renamed checkout B &&
    ++	printf "line 1\nline 2\nline 3\nline 4 T\nline 5\n" >keep-renamed/F &&
    ++	test_tick &&
    ++	git -C keep-renamed commit -am "B2: update F on B" &&
    ++
    ++	git -C keep-renamed checkout master &&
    ++	test_tick &&
    ++	git -C keep-renamed merge -s ours -m "M: merge B, keep G, drop F" B
    ++'
    ++
    ++test_expect_success '--follow follows an edit on the side the merge did not take' '
    ++	git -C keep-renamed log --follow --pretty=tformat:%s G >actual &&
    ++	cat >expect <<-\EOF &&
    ++	B2: update F on B
    ++	A1: rename F to G on master
    ++	O: add F
    ++	EOF
    ++	test_cmp expect actual
    ++'
    ++
    ++test_expect_success 'setup merge keeping the copied path, other side edited the original' '
    ++	git init keep-copied &&
    ++	printf "line 1\nline 2\nline 3\nline 4\nline 5\n" >keep-copied/F &&
    ++	git -C keep-copied add F &&
    ++	test_tick &&
    ++	git -C keep-copied commit -m "O: add F" &&
    ++	git -C keep-copied branch B &&
    ++	cp keep-copied/F keep-copied/G &&
    ++	git -C keep-copied add G &&
    ++	test_tick &&
    ++	git -C keep-copied commit -m "A1: copy F to G on master" &&
    ++
    ++	git -C keep-copied checkout B &&
    ++	printf "line 1\nline 2\nline 3\nline 4 T\nline 5\n" >keep-copied/F &&
    ++	test_tick &&
    ++	git -C keep-copied commit -am "B2: update F on B" &&
    ++
    ++	git -C keep-copied checkout master &&
    ++	test_tick &&
    ++	git -C keep-copied merge -s ours -m "M: merge B, keep F and G" B
    ++'
    ++
    ++test_expect_success '--follow follows an edit of the copy source on the side the merge did not take' '
    ++	git -C keep-copied log --follow --pretty=tformat:%s G >actual &&
    ++	cat >expect <<-\EOF &&
    ++	B2: update F on B
    ++	A1: copy F to G on master
    ++	O: add F
    ++	EOF
    ++	test_cmp expect actual
    ++'
     +
      test_done
2:  b172890bf4 = 2:  f36129714b log: extract showing the remerge/combined diff of a merge commit
3:  6586e8f004 = 3:  85b6067fbd log: fix -m --follow losing the history of later parents
4:  b903152bc4 ! 4:  5ba93e49cf log: simplify --follow following renames for non-linear history
    @@ Commit message
         log: simplify --follow following renames for non-linear history
     
         Code added in 304812ed33 (log: improve --follow following renames for
    -    non-linear history, 2026-06-22) implemented the feature by introducing a
    +    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 was working, but it caused a performance regression: e.g. 'git log
    -    --stat --follow -- include/linux/rsc_table.h' in linux.git went from 28s
    -    to 6m43s. 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.
    +    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() updated
    +    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
5:  072d90b865 < -:  ---------- log: speed up --follow following renames for non-linear history
6:  7be8cfb3ba ! 5:  ddcac2115e log: fix --follow following an edit on a side the merge did not take
    @@ Metadata
     Author: Miklos Vajna <vmiklos@collabora.com>
     
      ## Commit message ##
    -    log: fix --follow following an edit on a side the merge did not take
    +    log: speed up --follow following renames for non-linear history
     
    -    Take a history where F is renamed to G on master, F is edited on a side
    -    branch, and the side branch is merged with '-s ours', so the merge keeps
    -    G and drops F. Here 'git log --follow G' doesn't show the edit of F on
    -    the side branch, while it did before the previous commit.
    +    Test the performance of 'git log --stat --follow --
    +    include/linux/rsc_table.h' on linux.git: since commit 304812ed33 (log:
    +    improve --follow following renames for non-linear history, 2026-06-22),
    +    it takes 6m43s, while it took about 28 seconds before.
     
    -    The problem is that follow_needs_renames() only searches for renames in
    -    a parent without the path if the merge base still has the path, which
    -    covers the case when the parent renamed the path. Here the other side
    -    of the merge did the rename, so the merge base only has the old name.
    -    The parent is then told to follow G, which it doesn't have, and the
    -    history of F on its side is lost. Searching for renames in each parent
    -    that lacks the path would fix this, but that is the expensive search
    -    that the previous commit avoids, e.g. 148 merges hit this case for
    -    include/linux/rsc_table.h in linux.git.
    +    What happens is that log_tree_diff() diffs each merge against all of its
    +    parents with rename following. diff_tree_oid() only does the expensive
    +    rename detection when the path is added relative to a parent, but that
    +    is common for merges, e.g. when a branch forked before the path was
    +    created merges mainline, and this slows down 'git log --follow'.
     
    -    Fix the problem by also searching for renames when the merge base
    -    doesn't have the path, but the parent changed a file since the merge
    -    base that the merge no longer has: only such a file can be the old name
    -    of the path that still needs to be followed in the parent. This check
    -    is cheap, since it only diffs the changes of the side branch, and it
    -    rules out 146 of the 148 merges in linux.git. Then do a plain rename
    -    detection from the parent to the merge, which only looks at the files
    -    deleted by the merge, not at all files of the parent like --follow does.
    +    Fix the problem by first diffing the merge against its parents without
    +    rename following, to find a parent where the path is unchanged. If there
    +    is such a parent, then skip the diff against the other parents and just
    +    follow the same path there. The exception is a parent that doesn't have
    +    the path, but still needs renames to be followed:
    +    - The merge base has the path, i.e. that side renamed or removed it, or
    +      there is no merge base, e.g. a subtree merge of unrelated histories.
    +    - The merge base doesn't have the path, but the parent changed a file
    +      since then, and the merge dropped that change. A copy detection also
    +      finds that file as the source of the path, i.e. the other side renamed
    +      or copied the path from that file.
    +    If the path changed relative to all parents, then diff against them with
    +    rename following, as before. Given that this way a parent may be asked
    +    to follow a path that it doesn't have, also adjust
    +    remember_follow_pathspec(): if children disagree on what should be the
    +    path for a commit, then prefer the path that exists in that commit.
     
    -    Reported-by: Patrick Steinhardt <ps@pks.im>
    +    Now the above use case takes 42s, somewhat close to the original cost.
    +
    +    Helped-by: SZEDER Gábor <szeder.dev@gmail.com>
    +    Helped-by: Patrick Steinhardt <ps@pks.im>
         Signed-off-by: Miklos Vajna <vmiklos@collabora.com>
     
      ## log-tree.c ##
    +@@
    + #include "range-diff.h"
    + #include "strmap.h"
    + #include "tree.h"
    ++#include "tree-walk.h"
    + #include "wildmatch.h"
    + #include "write-or-die.h"
    + #include "pager.h"
     @@ log-tree.c: static void set_pathspec_to_single_path(struct pathspec *ps, const char *path)
      		       PATHSPEC_LITERAL_PATH, "", paths);
      }
      
    --static int commit_has_path(struct rev_info *opt, struct commit *c,
    --			   const char *path)
     +static int tree_has_path(struct rev_info *opt, struct object_id *tree,
     +			 const char *path)
    - {
    - 	struct object_id oid;
    - 	unsigned short mode;
    - 
    ++{
    ++	struct object_id oid;
    ++	unsigned short mode;
    ++
     +	return !get_tree_entry(opt->diffopt.repo, tree, path, &oid, &mode);
     +}
     +
     +static int commit_has_path(struct rev_info *opt, struct commit *c,
     +			   const char *path)
     +{
    - 	parse_commit_or_die(c);
    --	return !get_tree_entry(opt->diffopt.repo, get_commit_tree_oid(c), path,
    --			       &oid, &mode);
    ++	parse_commit_or_die(c);
     +	return tree_has_path(opt, get_commit_tree_oid(c), path);
    - }
    - 
    ++}
    ++
      static void remember_follow_pathspec(struct rev_info *opt,
    + 				     struct commit *c, const char *path)
    + {
    +@@ log-tree.c: static void remember_follow_pathspec(struct rev_info *opt,
    + 		init_follow_pathspec_slab(opt->follow_pathspec_slab);
    + 	}
    + 	slot = follow_pathspec_slab_at(opt->follow_pathspec_slab, c);
    +-	if (*slot && !strcmp(*slot, path))
    +-		return;
    ++	/* If children of 'c' disagree on the path, prefer the one 'c' has. */
    ++	if (*slot) {
    ++		if (!strcmp(*slot, path) || commit_has_path(opt, c, *slot) ||
    ++		    !commit_has_path(opt, c, path))
    ++			return;
    ++	}
    + 	free(*slot);
    + 	*slot = xstrdup(path);
    + }
     @@ log-tree.c: void release_follow_pathspec_slab(struct rev_info *opt)
      	FREE_AND_NULL(opt->follow_pathspec_slab);
      }
      
    ++/* Find a parent where 'path' is the same as in the merge. */
    ++static struct commit *follow_same_parent(struct rev_info *opt,
    ++					 struct commit_list *parents,
    ++					 struct object_id *merge_tree)
    ++{
    ++	struct commit *same = NULL;
    ++
    ++	opt->diffopt.flags.follow_renames = 0;
    ++	for (; parents && !same; parents = parents->next) {
    ++		parse_commit_or_die(parents->item);
    ++		diff_tree_oid(get_commit_tree_oid(parents->item), merge_tree,
    ++			      "", &opt->diffopt);
    ++		if (!diff_queued_diff.nr)
    ++			same = parents->item;
    ++		diff_queue_clear(&diff_queued_diff);
    ++	}
    ++	opt->diffopt.flags.follow_renames = 1;
    ++	return same;
    ++}
    ++
     +/*
    -+ * Did 'parent' change a file since 'base' that the merge (with tree 'oid') no
    -+ * longer has? Only such a file can be the source of a rename on the other side
    -+ * of the merge that still needs to be followed in 'parent'.
    ++ * Did 'parent' change a file since 'base', but the merge tree dropped that
    ++ * change? I.e. the merge doesn't have the file or it's the same as in 'same'.
     + */
     +static int follow_parent_changed_dropped_file(struct rev_info *opt,
     +					      struct commit *base,
     +					      struct commit *parent,
    -+					      struct object_id *oid)
    ++					      struct commit *same,
    ++					      struct object_id *merge_tree)
     +{
     +	struct diff_options diff_opts;
     +	int i, ret = 0;
    @@ log-tree.c: void release_follow_pathspec_slab(struct rev_info *opt)
     +		      "", &diff_opts);
     +	for (i = 0; i < diff_queued_diff.nr && !ret; i++) {
     +		struct diff_filepair *p = diff_queued_diff.queue[i];
    ++		struct object_id merge_oid, same_oid;
    ++		unsigned short mode;
     +
    -+		ret = DIFF_FILE_VALID(p->two) &&
    -+		      !tree_has_path(opt, oid, p->two->path);
    ++		if (!DIFF_FILE_VALID(p->two))
    ++			continue;
    ++		if (get_tree_entry(opt->diffopt.repo, merge_tree, p->two->path,
    ++				   &merge_oid, &mode))
    ++			ret = 1;
    ++		else
    ++			ret = !oideq(&merge_oid, &p->two->oid) &&
    ++			      !get_tree_entry(opt->diffopt.repo,
    ++					      get_commit_tree_oid(same),
    ++					      p->two->path, &same_oid, &mode) &&
    ++			      oideq(&merge_oid, &same_oid);
     +	}
     +	diff_queue_clear(&diff_queued_diff);
     +	diff_free(&diff_opts);
     +	return ret;
     +}
     +
    -+/*
    -+ * Is 'path' in the merge (with tree 'oid') renamed from a file of 'parent'?
    -+ * Only look at the files that the merge deleted, not at all files of 'parent'
    -+ * like --follow does.
    -+ */
    ++/* Is 'path' in the merge tree renamed or copied from a file of 'parent'? */
     +static int follow_has_rename_source(struct rev_info *opt,
     +				    struct commit *parent,
    -+				    struct object_id *oid, const char *path)
    ++				    struct object_id *merge_tree,
    ++				    const char *path)
     +{
     +	struct diff_options diff_opts;
     +	int i, ret = 0;
     +
     +	repo_diff_setup(opt->diffopt.repo, &diff_opts);
     +	diff_opts.flags.recursive = 1;
    -+	diff_opts.detect_rename = DIFF_DETECT_RENAME;
    ++	diff_opts.detect_rename = DIFF_DETECT_COPY;
     +	diff_opts.rename_score = opt->diffopt.rename_score;
     +	diff_opts.single_follow = path;
     +	diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
     +	diff_setup_done(&diff_opts);
    -+	diff_tree_oid(get_commit_tree_oid(parent), oid, "", &diff_opts);
    ++	diff_tree_oid(get_commit_tree_oid(parent), merge_tree, "", &diff_opts);
     +	diffcore_std(&diff_opts);
     +	for (i = 0; i < diff_queued_diff.nr && !ret; i++) {
     +		struct diff_filepair *p = diff_queued_diff.queue[i];
     +
    -+		ret = p->status == DIFF_STATUS_RENAMED &&
    ++		ret = (p->status == DIFF_STATUS_RENAMED ||
    ++		       p->status == DIFF_STATUS_COPIED) &&
     +		      !strcmp(p->two->path, path);
     +	}
     +	diff_queue_clear(&diff_queued_diff);
    @@ log-tree.c: void release_follow_pathspec_slab(struct rev_info *opt)
     +	return ret;
     +}
     +
    - /*
    -  * When the path in a merge is the same as in 'same', does the path still need
    -  * renames to be followed in 'parent'? Only if the merge has the path, 'parent'
    -- * doesn't have it, but its fork point with 'same' had it, e.g. 'parent'
    -- * renamed the path.
    -+ * doesn't have it, and either:
    -+ *
    -+ * - its fork point with 'same' had it, e.g. 'parent' renamed the path, or
    -+ * - 'same' renamed the path from a file that 'parent' changed.
    -  */
    - static int follow_needs_renames(struct rev_info *opt, struct commit *parent,
    --				struct commit *same, const char *path)
    -+				struct commit *same, struct object_id *oid,
    -+				const char *path)
    - {
    - 	struct commit_list *bases = NULL, *base;
    --	int ret = 0;
    ++/*
    ++ * When the path in a merge tree is the same as in 'same', does the path still
    ++ * need renames to be followed in 'parent'?
    ++ */
    ++static int follow_needs_renames(struct rev_info *opt, struct commit *parent,
    ++				struct commit *same,
    ++				struct object_id *merge_tree, const char *path)
    ++{
    ++	struct commit_list *bases = NULL, *base;
     +	int ret = 0, changed_dropped = 0;
    - 
    - 	if (commit_has_path(opt, parent, path) ||
    - 	    !commit_has_path(opt, same, path))
    - 		return 0;
    - 	if (repo_get_merge_bases(opt->diffopt.repo, parent, same, &bases) < 0)
    - 		exit(128);
    --	for (base = bases; base && !ret; base = base->next)
    ++
    ++	if (parent == same || commit_has_path(opt, parent, path) ||
    ++	    !commit_has_path(opt, same, path))
    ++		return 0;
    ++	if (repo_get_merge_bases(opt->diffopt.repo, parent, same, &bases) < 0)
    ++		exit(128);
    ++	/* Unrelated histories, e.g. a subtree merge */
    ++	ret = !bases;
     +	for (base = bases; base && !ret; base = base->next) {
    - 		ret = commit_has_path(opt, base->item, path);
    ++		ret = commit_has_path(opt, base->item, path);
     +		if (!ret && !changed_dropped)
     +			changed_dropped = follow_parent_changed_dropped_file(
    -+				opt, base->item, parent, oid);
    ++				opt, base->item, parent, same, merge_tree);
     +	}
    - 	commit_list_free(bases);
    ++	commit_list_free(bases);
     +	if (!ret && changed_dropped)
    -+		ret = follow_has_rename_source(opt, parent, oid, path);
    - 	return ret;
    - }
    ++		ret = follow_has_rename_source(opt, parent, merge_tree, path);
    ++	return ret;
    ++}
    ++
    + /* 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,
    +@@ log-tree.c: static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log
    + 	int all_need_diff = opt->diff || opt->diffopt.flags.exit_with_status;
    + 	int follow_only = 0;
    + 	char *path = NULL;
    ++	struct commit *same = NULL;
      
    + 	if (!all_need_diff && !opt->merges_need_diff)
    + 		return 0;
     @@ log-tree.c: static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log
    - 			     parents = parents->next) {
    - 				if (parents->item != parent &&
    - 				    follow_needs_renames(opt, parents->item,
    --							 parent, path))
    -+							 parent, oid, path))
    - 					tail = &commit_list_insert(parents->item,
    - 								   tail)->next;
    - 				else
    -
    - ## t/t4219-log-follow-merge.sh ##
    -@@ t/t4219-log-follow-merge.sh: test_expect_success '--follow with --diff-merges=first-parent follows all parent
    - 	test_cmp expect actual
    - '
    + 	if (opt->diffopt.flags.follow_renames)
    + 		path = xstrdup_or_null(
    + 			pathspec_single_path(&opt->diffopt.pathspec));
    ++	if (path && is_merge && (follow_only || opt->first_parent_merges))
    ++		same = follow_same_parent(opt, parents, oid);
      
    -+test_expect_success 'setup merge keeping the renamed path, other side edited the old one' '
    -+	git init keep-renamed &&
    -+	printf "line 1\nline 2\nline 3\nline 4\nline 5\n" >keep-renamed/F &&
    -+	git -C keep-renamed add F &&
    -+	test_tick &&
    -+	git -C keep-renamed commit -m "O: add F" &&
    -+	git -C keep-renamed branch B &&
    -+	git -C keep-renamed mv F G &&
    -+	test_tick &&
    -+	git -C keep-renamed commit -m "A1: rename F to G on master" &&
    -+
    -+	git -C keep-renamed checkout B &&
    -+	printf "line 1\nline 2\nline 3\nline 4 T\nline 5\n" >keep-renamed/F &&
    -+	test_tick &&
    -+	git -C keep-renamed commit -am "B2: update F on B" &&
    -+
    -+	git -C keep-renamed checkout master &&
    -+	test_tick &&
    -+	git -C keep-renamed merge -s ours -m "M: merge B, keep G, drop F" B
    -+'
    -+
    -+test_expect_success '--follow follows an edit on the side the merge did not take' '
    -+	git -C keep-renamed log --follow --pretty=tformat:%s G >actual &&
    -+	cat >expect <<-\EOF &&
    -+	B2: update F on B
    -+	A1: rename F to G on master
    -+	O: add F
    -+	EOF
    -+	test_cmp expect actual
    -+'
    -+
    - test_done
    + 	showed_log = 0;
    + 	all_parents = parents;
    +@@ log-tree.c: static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log
    + 		struct commit *parent = parents->item;
    + 
    + 		parse_commit_or_die(parent);
    +-		diff_tree_oid(get_commit_tree_oid(parent),
    +-			      oid, "", &opt->diffopt);
    ++		/*
    ++		 * Same as in a parent: follow the path as-is, unless renames
    ++		 * still need to be followed in this parent.
    ++		 */
    ++		if (!follow_only || !same ||
    ++		    follow_needs_renames(opt, parent, same, oid, path))
    ++			diff_tree_oid(get_commit_tree_oid(parent),
    ++				      oid, "", &opt->diffopt);
    + 		if (follow_only) {
    + 			diff_queue_clear(&diff_queued_diff);
    + 			opt->diffopt.found_follow = 0;
-- 
2.51.0


  parent reply	other threads:[~2026-11-09  8:03 UTC|newest]

Thread overview: 42+ 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             ` Miklos Vajna [this message]
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
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-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=cover.1794078554.git.vmiklos@collabora.com \
    --to=vmiklos@collabora.com \
    --cc=git@vger.kernel.org \
    --cc=gitster@pobox.com \
    --cc=ps@pks.im \
    --cc=szeder.dev@gmail.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox