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

[PATCH 2/2] merge-recursive: fix merging a subdirectory into the root directory

From
Elijah Newren via GitGitGadget <gitgitgadget@gmail.com>
Date
Oct 11, 2019, 20:42 UTC
Message-ID
<37aee862e14b1352eb08485f15ea06bab33679df.1570826543.git.gitgitgadget@gmail.com>
In-Reply-To
<pull.390.git.gitgitgadget@gmail.com>
From: Elijah Newren <newren@gmail.com>

We allow renaming all entries in e.g. a directory named z/ into a directory named y/ to be detected as a z/ -> y/ rename, so that if the other side of history adds any files to the directory z/ in the mean time, we can provide the hint that they should be moved to y/.

There is no reason to not allow 'y/' to be the root directory, but the code did not handle that case correctly. Add a testcase and the necessary special checks to support this case.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 merge-recursive.c                   | 29 +++++++++++++++
 t/t6043-merge-rename-directories.sh | 56 +++++++++++++++++++++++++++++
 2 files changed, 85 insertions(+)
diff --git a/merge-recursive.c b/merge-recursive.c
index f80e48f623..7bd4a7cf10 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -1931,6 +1931,16 @@ static char *apply_dir_rename(struct dir_rename_entry *entry,
 		return NULL;
 
 	oldlen = strlen(entry->dir);
+	if (entry->new_dir.len == 0)
+		/*
+		 * If someone renamed/merged a subdirectory into the root
+		 * directory (e.g. 'some/subdir' -> ''), then we want to
+		 * avoid returning
+		 *     '' + '/filename'
+		 * as the rename; we need to make old_path + oldlen advance
+		 * past the '/' character.
+		 */
+		oldlen++;
 	newlen = entry->new_dir.len + (strlen(old_path) - oldlen) + 1;
 	strbuf_grow(&new_path, newlen);
 	strbuf_addbuf(&new_path, &entry->new_dir);
@@ -1980,6 +1990,25 @@ static void get_renamed_dir_portion(const char *old_path, const char *new_path,
 	    *end_of_old == *end_of_new)
 		return; /* We haven't modified *old_dir or *new_dir yet. */
 
+	/*
+	 * If end_of_new got back to the beginning of its string, and
+	 * end_of_old got back to the beginning of some subdirectory, then
+	 * we have a rename/merge of a subdirectory into the root, which
+	 * needs slightly special handling.
+	 *
+	 * Note: There is no need to consider the opposite case, with a
+	 * rename/merge of the root directory into some subdirectory.
+	 * Our rule elsewhere that a directory which still exists is not
+	 * considered to have been renamed means the root directory can
+	 * never be renamed (because the root directory always exists).
+	 */
+	if (end_of_new == new_path &&
+	    end_of_old != old_path && end_of_old[-1] == '/') {
+		*old_dir = xstrndup(old_path, end_of_old-1 - old_path);
+		*new_dir = xstrndup(new_path, end_of_new - new_path);
+		return;
+	}
+
 	/*
 	 * We've found the first non-matching character in the directory
 	 * paths.  That means the current characters we were looking at
diff --git a/t/t6043-merge-rename-directories.sh b/t/t6043-merge-rename-directories.sh
index c966147d5d..b920bb0850 100755
--- a/t/t6043-merge-rename-directories.sh
+++ b/t/t6043-merge-rename-directories.sh
@@ -4051,6 +4051,62 @@ test_expect_success '12c-check: Moving one directory hierarchy into another w/ c
 	)
 '
 
+# Testcase 12d, Rename/merge of subdirectory into the root
+#   Commit O: a/b/{foo.c}
+#   Commit A: foo.c
+#   Commit B: a/b/{foo.c,bar.c}
+#   Expected: a/b/{foo.c,bar.c}
+
+test_expect_success '12d-setup: Rename (merge) of subdirectory into the root' '
+	test_create_repo 12d &&
+	(
+		cd 12d &&
+
+		mkdir -p a/b/subdir &&
+		test_commit a/b/subdir/foo.c &&
+
+		git branch O &&
+		git branch A &&
+		git branch B &&
+
+		git checkout A &&
+		mkdir subdir &&
+		git mv a/b/subdir/foo.c.t subdir/foo.c.t &&
+		test_tick &&
+		git commit -m "A" &&
+
+		git checkout B &&
+		test_commit a/b/bar.c
+	)
+'
+
+test_expect_success '12d-check: Rename (merge) of subdirectory into the root' '
+	(
+		cd 12d &&
+
+		git checkout A^0 &&
+
+		git -c merge.directoryRenames=true merge -s recursive B^0 &&
+
+		git ls-files -s >out &&
+		test_line_count = 2 out &&
+
+		git rev-parse >actual \
+			HEAD:subdir/foo.c.t   HEAD:bar.c.t &&
+		git rev-parse >expect \
+			O:a/b/subdir/foo.c.t  B:a/b/bar.c.t &&
+		test_cmp expect actual &&
+
+		git hash-object bar.c.t >actual &&
+		git rev-parse B:a/b/bar.c.t >expect &&
+		test_cmp expect actual &&
+
+		test_must_fail git rev-parse HEAD:a/b/subdir/foo.c.t &&
+		test_must_fail git rev-parse HEAD:a/b/bar.c.t &&
+		test_path_is_missing a/
+	)
+'
+
 ###########################################################################
 # SECTION 13: Checking informational and conflict messages
 #
-- 
gitgitgadget
Previous: Johannes SchindelinNext: Johannes Schindelin
Message 4 of 21 in “Dir rename fixes”
  1. 0/2 Dir rename fixesElijah Newren via GitGitGadget, Oct 11, 2019
  2. 1/2 merge-recursive: clean up get_renamed_dir_portion()Elijah Newren via GitGitGadget, Oct 11, 2019
  3. Johannes SchindelinOct 12, 2019
  4. 2/2 merge-recursive: fix merging a subdirectory into the root directoryElijah Newren via GitGitGadget, Oct 11, 2019
  5. Johannes SchindelinOct 12, 2019
  6. Elijah NewrenOct 13, 2019
  7. Johannes SchindelinOct 14, 2019
  8. Elijah NewrenOct 22, 2019
  9. Johannes SchindelinOct 24, 2019
  10. Elijah NewrenOct 25, 2019
  11. Johannes SchindelinOct 25, 2019
  12. Junio C HamanoOct 29, 2019
  13. Elijah NewrenOct 30, 2019
  14. Johannes SchindelinOct 30, 2019
  15. Elijah NewrenOct 30, 2019
  16. Johannes SchindelinOct 31, 2019
  17. Johannes SchindelinOct 12, 2019
  18. 0/3 Dir rename fixesElijah Newren via GitGitGadget, Oct 22, 2019
  19. 1/3 merge-recursive: clean up get_renamed_dir_portion()Elijah Newren via GitGitGadget, Oct 22, 2019
  20. 2/3 merge-recursive: fix merging a subdirectory into the root directoryElijah Newren via GitGitGadget, Oct 22, 2019
  21. 3/3 t604[236]: do not run setup in separate testsElijah Newren via GitGitGadget, Oct 22, 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.