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

[PATCH v2 21/33] merge-recursive: add get_directory_renames()

From
Elijah Newren <newren@gmail.com>
Date
Nov 20, 2017, 22:01 UTC
Message-ID
<20171120220209.15111-22-newren@gmail.com>
In-Reply-To
<20171120220209.15111-1-newren@gmail.com>

This populates a list of directory renames for us. The list of directory renames is not yet used, but will be in subsequent commits.

Signed-off-by: Elijah Newren <newren@gmail.com>
---
 merge-recursive.c | 149 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 149 insertions(+)
diff --git a/merge-recursive.c b/merge-recursive.c
index 98bc177c4e..68907f7376 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -1384,6 +1384,129 @@ static struct diff_queue_struct *get_diffpairs(struct merge_options *o,
 	return ret;
 }
 
+static void get_renamed_dir_portion(const char *old_path, const char *new_path,
+				    char **old_dir, char **new_dir)
+{
+	*old_dir = NULL;
+	*new_dir = NULL;
+
+	/* For
+	 *    "a/b/c/d/foo.c" -> "a/b/something-else/d/foo.c"
+	 * the "d/foo.c" part is the same, we just want to know that
+	 *    "a/b/c" was renamed to "a/b/something-else"
+	 * so, for this example, this function returns "a/b/c" in
+	 * *old_dir and "a/b/something-else" in *new_dir.
+	 *
+	 * Also, if the basename of the file changed, we don't care.  We
+	 * want to know which portion of the directory, if any, changed.
+	 */
+	char *end_of_old = strrchr(old_path, '/');
+	char *end_of_new = strrchr(new_path, '/');
+
+	if (end_of_old == NULL || end_of_new == NULL)
+		return;
+	while (*--end_of_new == *--end_of_old &&
+	       end_of_old != old_path &&
+	       end_of_new != new_path)
+		; /* Do nothing; all in the while loop */
+	/*
+	 * We've found the first non-matching character in the directory
+	 * paths.  That means the current directory we were comparing
+	 * represents the rename.  Move end_of_old and end_of_new back
+	 * to the full directory name.
+	 */
+	if (*end_of_old == '/')
+		end_of_old++;
+	if (*end_of_old != '/')
+		end_of_new++;
+	end_of_old = strchr(end_of_old, '/');
+	end_of_new = strchr(end_of_new, '/');
+
+	/*
+	 * It may have been the case that old_path and new_path were the same
+	 * directory all along.  Don't claim a rename if they're the same.
+	 */
+	int old_len = end_of_old - old_path;
+	int new_len = end_of_new - new_path;
+
+	if (old_len != new_len || strncmp(old_path, new_path, old_len)) {
+		*old_dir = strndup(old_path, old_len);
+		*new_dir = strndup(new_path, new_len);
+	}
+}
+
+static struct hashmap *get_directory_renames(struct diff_queue_struct *pairs,
+					     struct tree *tree)
+{
+	struct hashmap *dir_renames;
+	struct hashmap_iter iter;
+	struct dir_rename_entry *entry;
+	int i;
+
+	dir_renames = malloc(sizeof(struct hashmap));
+	dir_rename_init(dir_renames);
+	for (i = 0; i < pairs->nr; ++i) {
+		struct string_list_item *item;
+		int *count;
+		struct diff_filepair *pair = pairs->queue[i];
+		char *old_dir, *new_dir;
+
+		get_renamed_dir_portion(pair->one->path, pair->two->path,
+					&old_dir,        &new_dir);
+		if (!old_dir)
+			/* Directory didn't change at all; ignore this one. */
+			continue;
+
+		entry = dir_rename_find_entry(dir_renames, old_dir);
+		if (!entry) {
+			entry = xmalloc(sizeof(struct dir_rename_entry));
+			dir_rename_entry_init(entry, old_dir);
+			hashmap_put(dir_renames, entry);
+		} else {
+			free(old_dir);
+		}
+		item = string_list_lookup(&entry->possible_new_dirs, new_dir);
+		if (!item) {
+			item = string_list_insert(&entry->possible_new_dirs,
+						  new_dir);
+			item->util = xcalloc(1, sizeof(int));
+		} else {
+			free(new_dir);
+		}
+		count = item->util;
+		*count += 1;
+	}
+
+	hashmap_iter_init(dir_renames, &iter);
+	while ((entry = hashmap_iter_next(&iter))) {
+		int max = 0;
+		int bad_max = 0;
+		char *best = NULL;
+
+		for (i = 0; i < entry->possible_new_dirs.nr; i++) {
+			int *count = entry->possible_new_dirs.items[i].util;
+
+			if (*count == max)
+				bad_max = max;
+			else if (*count > max) {
+				max = *count;
+				best = entry->possible_new_dirs.items[i].string;
+			}
+		}
+		if (bad_max == max)
+			entry->non_unique_new_dir = 1;
+		else
+			entry->new_dir = strdup(best);
+		/* Strings were strndup'ed before inserting into string-list,
+		 * so ask string_list to remove the entries for us.
+		 */
+		entry->possible_new_dirs.strdup_strings = 1;
+		string_list_clear(&entry->possible_new_dirs, 1);
+	}
+
+	return dir_renames;
+}
+
 /*
  * Get information of all renames which occurred in 'pairs', making use of
  * any implicit directory renames inferred from the other side of history.
@@ -1703,6 +1826,9 @@ static int handle_renames(struct merge_options *o,
 			  struct rename_info *ri)
 {
 	struct diff_queue_struct *head_pairs, *merge_pairs;
+	struct hashmap *dir_re_head, *dir_re_merge;
+	struct hashmap_iter iter;
+	struct dir_rename_entry *e;
 	int clean;
 
 	if (!o->detect_rename) {
@@ -1714,6 +1840,9 @@ static int handle_renames(struct merge_options *o,
 	head_pairs = get_diffpairs(o, common, head);
 	merge_pairs = get_diffpairs(o, common, merge);
 
+	dir_re_head = get_directory_renames(head_pairs, head);
+	dir_re_merge = get_directory_renames(merge_pairs, merge);
+
 	ri->head_renames  = get_renames(o, head_pairs, head,
 					 common, head, merge, entries);
 	ri->merge_renames = get_renames(o, merge_pairs, merge,
@@ -1727,6 +1856,26 @@ static int handle_renames(struct merge_options *o,
 	 * process_entry().  But there are a few things we can free now.
 	 */
 
+	hashmap_iter_init(dir_re_head, &iter);
+	while ((e = hashmap_iter_next(&iter))) {
+		free(e->dir);
+		if (e->new_dir)
+			free(e->new_dir);
+		/* possible_new_dirs already cleared in get_directory_renames */
+	}
+	hashmap_free(dir_re_head, 1);
+	free(dir_re_head);
+
+	hashmap_iter_init(dir_re_merge, &iter);
+	while ((e = hashmap_iter_next(&iter))) {
+		free(e->dir);
+		if (e->new_dir)
+			free(e->new_dir);
+		/* possible_new_dirs already cleared in get_directory_renames */
+	}
+	hashmap_free(dir_re_merge, 1);
+	free(dir_re_merge);
+
 	free(head_pairs->queue);
 	free(head_pairs);
 	free(merge_pairs->queue);
-- 
2.15.0.309.g00c152f825
Previous: Elijah NewrenNext: Elijah Newren
Message 17 of 34 in “Add directory rename detection to git”
  1. 00/33 Add directory rename detection to gitElijah Newren, Nov 20, 2017
  2. 04/33 directory rename detection: basic testcasesElijah Newren, Nov 20, 2017
  3. 06/33 directory rename detection: testcases to avoid taking detection too farElijah Newren, Nov 20, 2017
  4. 24/33 merge-recursive: add computation of collisions due to dir rename & mergingElijah Newren, Nov 20, 2017
  5. 22/33 merge-recursive: check for directory level conflictsElijah Newren, Nov 20, 2017
  6. 25/33 merge-recursive: check for file level conflicts then get new nameElijah Newren, Nov 20, 2017
  7. 29/33 merge-recursive: fix overwriting dirty files involved in renamesElijah Newren, Nov 20, 2017
  8. 30/33 merge-recursive: fix remaining directory rename + dirty overwrite casesElijah Newren, Nov 20, 2017
  9. 31/33 directory rename detection: new testcases showcasing a pair of bugsElijah Newren, Nov 20, 2017
  10. 17/33 merge-recursive: fix leaks of allocated renames and diff_filepairsElijah Newren, Nov 20, 2017
  11. 33/33 merge-recursive: ensure we write updates for directory-renamed fileElijah Newren, Nov 20, 2017
  12. 28/33 merge-recursive: avoid clobbering untracked files with directory renamesElijah Newren, Nov 20, 2017
  13. 18/33 merge-recursive: make !o->detect_rename codepath more obviousElijah Newren, Nov 20, 2017
  14. 26/33 merge-recursive: when comparing files, don't include treesElijah Newren, Nov 20, 2017
  15. 11/33 directory rename detection: testcases exploring possibly suboptimal mergesElijah Newren, Nov 20, 2017
  16. 32/33 merge-recursive: avoid spurious rename/rename conflict from dir renamesElijah Newren, Nov 20, 2017
  17. 21/33 merge-recursive: add get_directory_renames()Elijah Newren, Nov 20, 2017
  18. 10/33 directory rename detection: more involved edge/corner testcasesElijah Newren, Nov 20, 2017
  19. 27/33 merge-recursive: apply necessary modifications for directory renamesElijah Newren, Nov 20, 2017
  20. 23/33 merge-recursive: add a new hashmap for storing file collisionsElijah Newren, Nov 20, 2017
  21. 20/33 merge-recursive: add a new hashmap for storing directory renamesElijah Newren, Nov 20, 2017
  22. 02/33 merge-recursive: fix logic ordering issueElijah Newren, Nov 20, 2017
  23. 12/33 directory rename detection: miscellaneous testcases to complete coverageElijah Newren, Nov 20, 2017
  24. 05/33 directory rename detection: directory splitting testcasesElijah Newren, Nov 20, 2017
  25. 19/33 merge-recursive: split out code for determining diff_filepairsElijah Newren, Nov 20, 2017
  26. 15/33 merge-recursive: move the get_renames() functionElijah Newren, Nov 20, 2017
  27. 16/33 merge-recursive: introduce new functions to handle rename logicElijah Newren, Nov 20, 2017
  28. 13/33 directory rename detection: tests for handling overwriting untracked filesElijah Newren, Nov 20, 2017
  29. 14/33 directory rename detection: tests for handling overwriting dirty filesElijah Newren, Nov 20, 2017
  30. 03/33 merge-recursive: add explanation for src_entry and dst_entryElijah Newren, Nov 20, 2017
  31. 01/33 Tighten and correct a few testcases for merging and cherry-pickingElijah Newren, Nov 20, 2017
  32. 09/33 directory rename detection: testcases checking which side did the renameElijah Newren, Nov 20, 2017
  33. 07/33 directory rename detection: partially renamed directory testcase/discussionElijah Newren, Nov 20, 2017
  34. 08/33 directory rename detection: files/directories in the way of some renamesElijah Newren, Nov 20, 2017

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.