git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Elijah Newren <newren@gmail.com>
To: gitster@pobox.com
Cc: git@vger.kernel.org, Jim Foucar <jgfouca@sandia.gov>,
	Elijah Newren <newren@gmail.com>
Subject: [PATCHv2 30/56] merge-recursive: Fix deletion of untracked file in rename/delete conflicts
Date: Thu, 11 Aug 2011 23:20:03 -0600	[thread overview]
Message-ID: <1313126429-17368-31-git-send-email-newren@gmail.com> (raw)
In-Reply-To: <1313126429-17368-1-git-send-email-newren@gmail.com>

In the recursive case (o->call_depth > 0), we do not modify the working
directory.  However, when o->call_depth==0, file renames can mean we need
to delete the old filename from the working copy.  Since there have been
lots of changes and mistakes here, let's go through the details.  Let's
start with a simple explanation of what we are trying to achieve:

  Original goal: If a file is renamed on the side of history being merged
  into head, the filename serving as the source of that rename needs to be
  removed from the working directory.

The path to getting the above statement implemented in merge-recursive took
several steps.  The relevant bits of code may be instructive to keep in
mind for the explanation, especially since an English-only description
involves double negatives that are hard to follow.  These bits of code are:
  int remove_file(..., const char *path, int no_wd)
  {
    ...
    int update_working_directory = !o->call_depth && !no_wd;
and
  remove_file(o, 1, ren1_src, <expression>);
Where the choice for <expression> has morphed over time:

65ac6e9 (merge-recursive: adjust to loosened "working file clobbered"
check 2006-10-27), introduced the "no_wd" parameter to remove_file() and
used "1" for <expression>.  This meant ren1_src was never deleted, leaving
it around in the working copy.

In 8371234 (Remove uncontested renamed files during merge. 2006-12-13),
<expression> was changed to "index_only" (where index_only ==
!!o->call_depth; see b7fa51da).   This was equivalent to using "0" for
<expression> (due to the early logic in remove_file), and is orthogonal to
the condition we actually want to check at this point; it resulted in the
source file being removed except when index_only was false.  This was
problematic because the file could have been renamed on the side of history
including head, in which case ren1_src could correspond to an untracked
file that should not be deleted.

In 183d797 (Keep untracked files not involved in a merge. 2007-02-04),
<expression> was changed to "index_only || stage == 3".  While this gives
correct behavior, the "index_only ||" portion of <expression> is
unnecessary and makes the code slightly harder to follow.

There were also two further changes to this expression, though without
any change in behavior.  First in b7fa51d (merge-recursive: get rid of the
index_only global variable 2008-09-02), it was changed to "o->call_depth
|| stage == 3".  (index_only == !!o->call_depth).  Later, in 41d70bd6
(merge-recursive: Small code clarification -- variable name and comments),
this was changed to "o->call_depth || renamed_stage == 2" (where stage was
renamed to other_stage and renamed_stage == other_stage ^ 1).

So we ended with <expression> being "o->call_depth || renamed_stage == 2".
But the "o->call_depth ||" piece was unnecessary.  We can remove it,
leaving us with <expression> being "renamed_stage == 2".  This doesn't
change behavior at all, but it makes the code clearer.  Which is good,
because it's about to get uglier.

  Corrected goal: If a file is renamed on the side of history being merged
  into head, the filename serving as the source of that rename needs to be
  removed from the working directory *IF* that file is tracked in head AND
  the file tracked in head is related to the original file.

Note that the only difference between the original goal and the corrected
goal is the two extra conditions added at the end.  The first condition is
relevant in a rename/delete conflict.  If the file was deleted on the
HEAD side of the merge and an untracked file of the same name was added to
the working copy, then without that extra condition the untracked file
will be erroneously deleted.  This changes <expression> to "renamed_stage
== 2 || !was_tracked(ren1_src)".

The second additional condition is relevant in two cases.

The first case the second condition can occur is when a file is deleted
and a completely different file is added with the same name.  To my
knowledge, merge-recursive has no mechanism for detecting deleted-and-
replaced-by-different-file cases, so I am simply punting on this
possibility.

The second case for the second condition to occur is when there is a
rename/rename/add-source conflict.  That is, when the original file was
renamed on both sides of history AND the original filename is being
re-used by some unrelated (but tracked) content.  This case also presents
some additional difficulties for us since we cannot currently detect these
rename/rename/add-source conflicts; as long as the rename detection logic
"optimizes" by ignoring filenames that are present at both ends of the
diff, these conflicts will go unnoticed.  However, rename/rename conflicts
are handled by an entirely separate codepath not being discussed here, so
this case is not relevant for the line of code under consideration.

In summary:
  Change <expression> from "o->call_depth || renamed_stage == 2" to
  "renamed_stage == 2 || !was_tracked(ren1_src)", in order to remove
  unnecessary code and avoid deleting untracked files.

96 lines of explanation in the changelog to describe a one-line fix...

Signed-off-by: Elijah Newren <newren@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
No changes since v1.

 merge-recursive.c                    |    3 ++-
 t/t6042-merge-rename-corner-cases.sh |    2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/merge-recursive.c b/merge-recursive.c
index 650d5ec..f395d20 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -1117,7 +1117,8 @@ static int process_renames(struct merge_options *o,
 			int renamed_stage = a_renames == renames1 ? 2 : 3;
 			int other_stage =   a_renames == renames1 ? 3 : 2;
 
-			remove_file(o, 1, ren1_src, o->call_depth || renamed_stage == 2);
+			remove_file(o, 1, ren1_src,
+				    renamed_stage == 2 || !was_tracked(ren1_src));
 
 			hashcpy(src_other.sha1, ren1->src_entry->stages[other_stage].sha);
 			src_other.mode = ren1->src_entry->stages[other_stage].mode;
diff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh
index 427fe1c..668ec6d 100755
--- a/t/t6042-merge-rename-corner-cases.sh
+++ b/t/t6042-merge-rename-corner-cases.sh
@@ -26,7 +26,7 @@ test_expect_success 'setup rename/delete + untracked file' '
 	echo "Myyy PRECIOUSSS" >ring
 '
 
-test_expect_failure "Does git preserve Gollum's precious artifact?" '
+test_expect_success "Does git preserve Gollum's precious artifact?" '
 	test_must_fail git merge -s recursive rename-the-ring &&
 
 	# Make sure git did not delete an untracked file
-- 
1.7.6.100.gac5c1

  parent reply	other threads:[~2011-08-12  5:22 UTC|newest]

Thread overview: 65+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2011-08-12  5:19 [PATCHv2 00/57] Re-roll of en/merge-recursive from pu Elijah Newren
2011-08-12  5:19 ` [PATCHv2 01/56] t6042: Add a testcase where git deletes an untracked file Elijah Newren
2011-08-12  5:19 ` [PATCHv2 02/56] t6042: Add failing testcase for rename/modify/add-source conflict Elijah Newren
2011-08-12  5:19 ` [PATCHv2 03/56] t6042: Add a pair of cases where undetected renames cause issues Elijah Newren
2011-08-12  5:19 ` [PATCHv2 04/56] t6042: Add a testcase where undetected rename causes silent file deletion Elijah Newren
2011-08-12  5:19 ` [PATCHv2 05/56] t6042: Add tests for content issues with modify/rename/directory conflicts Elijah Newren
2011-08-12  5:19 ` [PATCHv2 06/56] t6042: Ensure rename/rename conflicts leave index and workdir in sane state Elijah Newren
2011-08-12  5:19 ` [PATCHv2 07/56] t6042: Add failing testcases for rename/rename/add-{source,dest} conflicts Elijah Newren
2011-08-12  5:19 ` [PATCHv2 08/56] t6036: Add differently resolved modify/delete conflict in criss-cross test Elijah Newren
2011-08-12  5:19 ` [PATCHv2 09/56] t6036: criss-cross with weird content can fool git into clean merge Elijah Newren
2011-08-12  5:19 ` [PATCHv2 10/56] t6036: tests for criss-cross merges with various directory/file conflicts Elijah Newren
2011-08-12  5:19 ` [PATCHv2 11/56] t6036: criss-cross w/ rename/rename(1to2)/modify+rename/rename(2to1)/modify Elijah Newren
2011-08-12  5:19 ` [PATCHv2 12/56] t6036: criss-cross + rename/rename(1to2)/add-source + modify/modify Elijah Newren
2011-08-12  5:19 ` [PATCHv2 13/56] t6022: Remove unnecessary untracked files to make test cleaner Elijah Newren
2011-08-12  5:19 ` [PATCHv2 14/56] t6022: New tests checking for unnecessary updates of files Elijah Newren
2011-08-12  5:19 ` [PATCHv2 15/56] t6022: Add testcase for merging a renamed file with a simple change Elijah Newren
2011-08-12  5:19 ` [PATCHv2 16/56] merge-recursive: Make BUG message more legible by adding a newline Elijah Newren
2011-08-12  5:19 ` [PATCHv2 17/56] merge-recursive: Correct a comment Elijah Newren
2011-08-12  5:19 ` [PATCHv2 18/56] merge-recursive: Mark some diff_filespec struct arguments const Elijah Newren
2011-08-12  5:19 ` [PATCHv2 19/56] merge-recursive: Consolidate different update_stages functions Elijah Newren
2011-08-12  5:19 ` [PATCHv2 20/56] merge-recursive: Remember to free generated unique path names Elijah Newren
2011-08-12  5:19 ` [PATCHv2 21/56] merge-recursive: Avoid working directory changes during recursive case Elijah Newren
2011-08-12  5:19 ` [PATCHv2 22/56] merge-recursive: Fix recursive case with D/F conflict via add/add conflict Elijah Newren
2011-08-12  5:19 ` [PATCHv2 23/56] merge-recursive: Fix sorting order and directory change assumptions Elijah Newren
2011-08-12  5:19 ` [PATCHv2 24/56] merge-recursive: Fix code checking for D/F conflicts still being present Elijah Newren
2011-08-12  5:19 ` [PATCHv2 25/56] merge-recursive: Save D/F conflict filenames instead of unlinking them Elijah Newren
2011-08-12  5:19 ` [PATCHv2 26/56] merge-recursive: Split was_tracked() out of would_lose_untracked() Elijah Newren
2011-08-12  5:20 ` [PATCHv2 27/56] string-list: Add API to remove an item from an unsorted list Elijah Newren
2011-08-12  7:00   ` Johannes Sixt
2011-08-12  9:27     ` Alex Riesen
2011-08-12 22:14     ` Elijah Newren
2011-08-13  9:08       ` Johannes Sixt
2011-08-12  5:20 ` [PATCHv2 28/56] merge-recursive: Allow make_room_for_path() to remove D/F entries Elijah Newren
2011-08-12  5:20 ` [PATCHv2 29/56] merge-recursive: Split update_stages_and_entry; only update stages at end Elijah Newren
2011-08-12  5:20 ` Elijah Newren [this message]
2011-08-12  5:20 ` [PATCHv2 31/56] merge-recursive: Make dead code for rename/rename(2to1) conflicts undead Elijah Newren
2011-08-12  5:20 ` [PATCHv2 32/56] merge-recursive: Add comments about handling rename/add-source cases Elijah Newren
2011-08-12  5:20 ` [PATCHv2 33/56] merge-recursive: Improve handling of rename target vs. directory addition Elijah Newren
2011-08-12  5:20 ` [PATCHv2 34/56] merge-recursive: Consolidate process_entry() and process_df_entry() Elijah Newren
2011-08-12  5:20 ` [PATCHv2 35/56] merge-recursive: Cleanup and consolidation of rename_conflict_info Elijah Newren
2011-08-12  5:20 ` [PATCHv2 36/56] merge-recursive: Provide more info in conflict markers with file renames Elijah Newren
2011-08-12  5:20 ` [PATCHv2 37/56] merge-recursive: When we detect we can skip an update, actually skip it Elijah Newren
2011-08-12  5:20 ` [PATCHv2 38/56] merge-recursive: Fix modify/delete resolution in the recursive case Elijah Newren
2011-08-12  5:20 ` [PATCHv2 39/56] merge-recursive: Introduce a merge_file convenience function Elijah Newren
2011-08-12  5:20 ` [PATCHv2 40/56] merge-recursive: Fix rename/rename(1to2) resolution for virtual merge base Elijah Newren
2011-08-12  5:20 ` [PATCHv2 41/56] merge-recursive: Small cleanups for conflict_rename_rename_1to2 Elijah Newren
2011-08-12  5:20 ` [PATCHv2 42/56] merge-recursive: Defer rename/rename(2to1) handling until process_entry Elijah Newren
2011-08-12  5:20 ` [PATCHv2 43/56] merge-recursive: Record more data needed for merging with dual renames Elijah Newren
2011-08-12  5:20 ` [PATCHv2 44/56] merge-recursive: Create function for merging with branchname:file markers Elijah Newren
2011-08-12  5:20 ` [PATCHv2 45/56] merge-recursive: Consider modifications in rename/rename(2to1) conflicts Elijah Newren
2011-08-12  5:20 ` [PATCHv2 46/56] merge-recursive: Make modify/delete handling code reusable Elijah Newren
2011-08-12  5:20 ` [PATCHv2 47/56] merge-recursive: Have conflict_rename_delete reuse modify/delete code Elijah Newren
2011-08-12  5:20 ` [PATCHv2 48/56] merge-recursive: add handling for rename/rename/add-dest/add-dest Elijah Newren
2011-08-12  5:20 ` [PATCHv2 49/56] merge-recursive: Fix working copy handling for rename/rename/add/add Elijah Newren
2011-08-12  5:20 ` [PATCHv2 50/56] t3030: fix accidental success in symlink rename Elijah Newren
2011-08-12  5:20 ` [PATCHv2 51/56] t6022: Add testcase for spurious "refusing to lose untracked" messages Elijah Newren
2011-08-12  5:20 ` [PATCHv2 52/56] merge-recursive: Fix spurious 'refusing to lose untracked file...' messages Elijah Newren
2011-08-12  5:20 ` [PATCHv2 53/56] t6022: Additional tests checking for unnecessary updates of files Elijah Newren
2011-08-12  5:20 ` [PATCHv2 54/56] merge-recursive: Avoid unnecessary file rewrites Elijah Newren
2011-08-12  5:20 ` [PATCHv2 55/56] t6036: criss-cross + rename/rename(1to2)/add-dest + simple modify Elijah Newren
2011-08-12  5:20 ` [PATCHv2 56/56] merge-recursive: Fix virtual merge base for rename/rename(1to2)/add-dest Elijah Newren
2011-08-12  5:48 ` [PATCHv2 00/57] Re-roll of en/merge-recursive from pu Junio C Hamano
2011-08-12 21:59   ` Elijah Newren
2011-08-13  2:37     ` Elijah Newren
2011-08-13  2:23 ` [PATCHv2 57/57] merge-recursive: Don't re-sort a list whose order we depend upon Elijah Newren

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=1313126429-17368-31-git-send-email-newren@gmail.com \
    --to=newren@gmail.com \
    --cc=git@vger.kernel.org \
    --cc=gitster@pobox.com \
    --cc=jgfouca@sandia.gov \
    /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;
as well as URLs for NNTP newsgroup(s).