git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [BUG] All files in folder are moved when cherry-picking commit that moves fewer files

From
Junio C Hamano <gitster@pobox.com>
Date
Mar 6, 2019, 04:43 UTC
Message-ID
<xmqqh8cgr4tf.fsf@gitster-ct.c.googlers.com>
In-Reply-To
<20190306002744.14418-1-newren@gmail.com>
Elijah Newren <newren@gmail.com> writes:
Show 10 quoted lines
> Note that there is also a third possibility here:
>
>   C) There are different answers depending on the context and content
>      that cannot be determined by git, so this is a conflict.  Use a
>      higher stage in the index to record the conflict and notify the
>      user of the potential issue instead of silently selecting a
>      resolution for them.
>
> Add an option for users to specify their preference for whether to use
> directory rename detection, and default to (C).

Yeah, (c) is the sanest default that can give the best of both worlds, I would have to say.

Sometimes the heuristics guess that the new one may want to go to the same place as all the neighbours, and other times the new one may want to stay. By using stage #2 and stage #3 appropriately, the user can see where the final result wants to be in. Because by definition there won't be any auto-resolvable content-level merge for such paths (after all, one side is adding it as a new file), I think the contents for these two higher-stage entries would actually be the same?

Show 7 quoted lines
> +	if (mark_conflicted)
> +		output(o, 1, _("CONFLICT (directory possibly renamed): %s "
> +			       "added in %s, but that directory was renamed "
> +			       "in %s suggesting it should perhaps be moved "
> +			       "to %s."),
> +		       pair->one->path, other_branch, rename_branch,
> +		       dest->path);

What confused the end user is that it is not always the case, and that is because the only thing we know at this point is that the other branch moved all the files in that directory in their branch to the other place, not that they "moved the directory".

In other words, if "that directory was renamed" in this message were definitive and agreed with end-user perception, we won't be issuing this conflict with a label that says "possibly" renamed.

Perhaps something along this line?
	%(pair->one->path)s added in %(other_branch)s, but the other
	branch %(rename_branch)s moved all the files in the same
	directory to %(dirname(dest->path))s, so this file may also
	want to move there, too.
Show 27 quoted lines
>  	if (!o->call_depth && would_lose_untracked(o, dest->path)) {
> -		char *alt_path = unique_path(o, dest->path, rename_branch);
> -
> +		mark_conflicted = 1;
> +		file_path = unique_path(o, dest->path, rename_branch);
>  		output(o, 1, _("Error: Refusing to lose untracked file at %s; "
>  			       "writing to %s instead."),
> -		       dest->path, alt_path);
> +		       dest->path, file_path);
> +	}
> +
> +	if (mark_conflicted) {
>  		/*
> -		 * Write the file in worktree at alt_path, but not in the
> -		 * index.  Instead, write to dest->path for the index but
> -		 * only at the higher appropriate stage.
> +		 * Write the file in worktree at file_path.  In the index,
> +		 * only record the file at dest->path in the appropriate
> +		 * higher stage.
>  		 */
> -		if (update_file(o, 0, &dest->oid, dest->mode, alt_path))
> +		if (update_file(o, 0, &dest->oid, dest->mode, file_path))
>  			return -1;
> +		if (update_stages(o, dest->path, NULL,
> +				  rename_branch == o->branch1 ? dest : NULL,
> +				  rename_branch == o->branch1 ? NULL : dest))
> +			return -1;

This one does not issue "may want to stay here" comment of its own, so the earlier "CONFLICT (directory possibly renamed)" message would need to cover both of these two higher-stage entries. I think the message mentions the two paths, so it would be good already.

The earlier "CONFLICT (directory possibly renamed)" message may be sufficient to explain the situation, but I wonder if the user would need hints in resolving either of the two possible ways. I do not know if it should be in the same message, or in the manpage update, but first let's make sure what we want the end users to understand. IIUC, the hint for resolving in favor of either would be to do these two commands?

	git checkout --ours $path_the_user_wants_to_use
	git rm --cached $the_other_path

If the path the user wants to use matches what the branch being merged has, then replace "--ours" above with "--theirs".

Would that be a good explanation?

Regarding the implementation (not the use of stages, but how "mark_conflicted" variable gets used), I am not sure if I agree that only choice (c) should give the hint. Regardless of the settings, we know both paths this file may want to end up with, and I suspect that the alternative a/b/c you gave are how by default the tool automatically places the result, giving a chance to the end user to correct mistakes. I wonder if it is possible to somehow show "the other possibilty" in the merge log even when choice (a) or (b) are in effect? E.g. "path X/B moved to Y/B because the other side moved X/A to Y/A" when choice (b) is in use, and "path X/B kept there but the other side moved neighbouring A/X to Y/A" when choice (a) is in use.

Another thing that makes me wonder is if this should be treated similar to rerere.autoupdate; even when rerere kicks in to resolve otherwise conflicting tricky merge, without being explicitly allowed with the config, it does not register the resolution to the index at stage #0. Either choices (a) that ignores neighbouring files' movement or (b) that follows the movement lets this tricky heuristics not just suggest the end result, but allows it to register the result at stage #0, which somehow feels too aggressive (which was what triggered my suggestion to go with (c)). With that attitude, we may be able to get rid of choice (a) altogether, which may make things a tad simpler. IOW, we assume the heuristics would suggest the right solution most of the time, just like we assume rerere gives a reasonable resolution most of the time, with a knob to accept the result blindly, together with hints to recover when heuristics makes mistakes.

Previous: Elijah NewrenNext: Elijah Newren
Message 11 of 49 in “[BUG] All files in folder are moved when cherry-picking commit that moves fewer files”
  1. Linus NilssonFeb 27, 2019
  2. Phillip WoodFeb 27, 2019
  3. Elijah NewrenFeb 27, 2019
  4. Jeff KingFeb 27, 2019
  5. Elijah NewrenFeb 27, 2019
  6. Linus NilssonFeb 28, 2019
  7. Junio C HamanoMar 1, 2019
  8. Elijah NewrenMar 2, 2019
  9. Junio C HamanoMar 3, 2019
  10. Elijah NewrenMar 6, 2019
  11. Junio C HamanoMar 6, 2019
  12. Elijah NewrenMar 7, 2019
  13. Junio C HamanoMar 7, 2019
  14. Junio C HamanoMar 7, 2019
  15. 00/15 Switch directory rename detection defaultElijah Newren, Mar 30, 2019
  16. 08/15 merge-recursive: remove ren[12]_other fields from rename_conflict_infoElijah Newren, Mar 30, 2019
  17. 10/15 merge-recursive: cleanup handle_rename_* function signaturesElijah Newren, Mar 30, 2019
  18. 01/15 Use 'unsigned short' for mode, like diff_filespec doesElijah Newren, Mar 30, 2019
  19. 04/15 merge-recursive: rename locals 'o' and 'a' to 'obuf' and 'abuf'Elijah Newren, Mar 30, 2019
  20. 14/15 merge-recursive: give callers of handle_content_merge() access to contentsElijah Newren, Mar 30, 2019
  21. 11/15 merge-recursive: switch from (oid,mode) pairs to a diff_filespecElijah Newren, Mar 30, 2019
  22. 07/15 merge-recursive: shrink rename_conflict_infoElijah Newren, Mar 30, 2019
  23. 12/15 t6043: fix copied test description to match its purposeElijah Newren, Mar 30, 2019
  24. 09/15 merge-recursive: track branch where rename occurred in rename structElijah Newren, Mar 30, 2019
  25. 13/15 merge-recursive: track information associated with directory renamesElijah Newren, Mar 30, 2019
  26. 15/15 merge-recursive: switch directory rename detection defaultElijah Newren, Mar 30, 2019
  27. Ævar Arnfjörð BjarmasonMar 30, 2019
  28. Elijah NewrenApr 1, 2019
  29. 02/15 merge-recursive: rename merge_options argument from 'o' to 'opt'Elijah Newren, Mar 30, 2019
  30. 05/15 merge-recursive: use 'ci' for rename_conflict_info variable nameElijah Newren, Mar 30, 2019
  31. 03/15 merge-recursive: rename diff_filespec 'one' to 'o'Elijah Newren, Mar 30, 2019
  32. 06/15 merge-recursive: move some struct declarations togetherElijah Newren, Mar 30, 2019
  33. 00/15 Switch directory rename detection defaultElijah Newren, Apr 5, 2019
  34. 03/15 merge-recursive: rename diff_filespec 'one' to 'o'Elijah Newren, Apr 5, 2019
  35. 05/15 merge-recursive: use 'ci' for rename_conflict_info variable nameElijah Newren, Apr 5, 2019
  36. 09/15 merge-recursive: track branch where rename occurred in rename structElijah Newren, Apr 5, 2019
  37. 06/15 merge-recursive: move some struct declarations togetherElijah Newren, Apr 5, 2019
  38. 07/15 merge-recursive: shrink rename_conflict_infoElijah Newren, Apr 5, 2019
  39. 08/15 merge-recursive: remove ren[12]_other fields from rename_conflict_infoElijah Newren, Apr 5, 2019
  40. 04/15 merge-recursive: rename locals 'o' and 'a' to 'obuf' and 'abuf'Elijah Newren, Apr 5, 2019
  41. 15/15 merge-recursive: switch directory rename detection defaultElijah Newren, Apr 5, 2019
  42. 14/15 merge-recursive: give callers of handle_content_merge() access to contentsElijah Newren, Apr 5, 2019
  43. 12/15 t6043: fix copied test description to match its purposeElijah Newren, Apr 5, 2019
  44. 13/15 merge-recursive: track information associated with directory renamesElijah Newren, Apr 5, 2019
  45. 02/15 merge-recursive: rename merge_options argument from 'o' to 'opt'Elijah Newren, Apr 5, 2019
  46. 10/15 merge-recursive: cleanup handle_rename_* function signaturesElijah Newren, Apr 5, 2019
  47. 01/15 Use 'unsigned short' for mode, like diff_filespec doesElijah Newren, Apr 5, 2019
  48. 11/15 merge-recursive: switch from (oid,mode) pairs to a diff_filespecElijah Newren, Apr 5, 2019
  49. Jacob KellerApr 5, 2019

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.