Skip to content

Commit c67de74

Browse files
committed
Merge branch 'en/rename-directory-detection-reboot'
Rename detection logic in "diff" family that is used in "merge" has learned to guess when all of x/a, x/b and x/c have moved to z/a, z/b and z/c, it is likely that x/d added in the meantime would also want to move to z/d by taking the hint that the entire directory 'x' moved to 'z'. A bug causing dirty files involved in a rename to be overwritten during merge has also been fixed as part of this work. Incidentally, this also avoids updating a file in the working tree after a (non-trivial) merge whose result matches what our side originally had. * en/rename-directory-detection-reboot: (36 commits) merge-recursive: fix check for skipability of working tree updates merge-recursive: make "Auto-merging" comment show for other merges merge-recursive: fix remainder of was_dirty() to use original index merge-recursive: fix was_tracked() to quit lying with some renamed paths t6046: testcases checking whether updates can be skipped in a merge merge-recursive: avoid triggering add_cacheinfo error with dirty mod merge-recursive: move more is_dirty handling to merge_content merge-recursive: improve add_cacheinfo error handling merge-recursive: avoid spurious rename/rename conflict from dir renames directory rename detection: new testcases showcasing a pair of bugs merge-recursive: fix remaining directory rename + dirty overwrite cases merge-recursive: fix overwriting dirty files involved in renames merge-recursive: avoid clobbering untracked files with directory renames merge-recursive: apply necessary modifications for directory renames merge-recursive: when comparing files, don't include trees merge-recursive: check for file level conflicts then get new name merge-recursive: add computation of collisions due to dir rename & merging merge-recursive: check for directory level conflicts merge-recursive: add get_directory_renames() merge-recursive: make a helper function for cleanup for handle_renames ...
2 parents c9aac55 + 1de70db commit c67de74

11 files changed

+6092
-178
lines changed

merge-recursive.c

Lines changed: 1262 additions & 170 deletions
Large diffs are not rendered by default.

merge-recursive.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#ifndef MERGE_RECURSIVE_H
22
#define MERGE_RECURSIVE_H
33

4+
#include "unpack-trees.h"
45
#include "string-list.h"
56

67
struct merge_options {
@@ -27,6 +28,33 @@ struct merge_options {
2728
struct strbuf obuf;
2829
struct hashmap current_file_dir_set;
2930
struct string_list df_conflict_file_set;
31+
struct unpack_trees_options unpack_opts;
32+
struct index_state orig_index;
33+
};
34+
35+
/*
36+
* For dir_rename_entry, directory names are stored as a full path from the
37+
* toplevel of the repository and do not include a trailing '/'. Also:
38+
*
39+
* dir: original name of directory being renamed
40+
* non_unique_new_dir: if true, could not determine new_dir
41+
* new_dir: final name of directory being renamed
42+
* possible_new_dirs: temporary used to help determine new_dir; see comments
43+
* in get_directory_renames() for details
44+
*/
45+
struct dir_rename_entry {
46+
struct hashmap_entry ent; /* must be the first member! */
47+
char *dir;
48+
unsigned non_unique_new_dir:1;
49+
struct strbuf new_dir;
50+
struct string_list possible_new_dirs;
51+
};
52+
53+
struct collision_entry {
54+
struct hashmap_entry ent; /* must be the first member! */
55+
char *target_file;
56+
struct string_list source_files;
57+
unsigned reported_already:1;
3058
};
3159

3260
/* merge_trees() but with recursive ancestor consolidation */

strbuf.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "cache.h"
22
#include "refs.h"
3+
#include "string-list.h"
34
#include "utf8.h"
45

56
int starts_with(const char *str, const char *prefix)
@@ -180,6 +181,21 @@ struct strbuf **strbuf_split_buf(const char *str, size_t slen,
180181
return ret;
181182
}
182183

184+
void strbuf_add_separated_string_list(struct strbuf *str,
185+
const char *sep,
186+
struct string_list *slist)
187+
{
188+
struct string_list_item *item;
189+
int sep_needed = 0;
190+
191+
for_each_string_list_item(item, slist) {
192+
if (sep_needed)
193+
strbuf_addstr(str, sep);
194+
strbuf_addstr(str, item->string);
195+
sep_needed = 1;
196+
}
197+
}
198+
183199
void strbuf_list_free(struct strbuf **sbs)
184200
{
185201
struct strbuf **s = sbs;

strbuf.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#ifndef STRBUF_H
22
#define STRBUF_H
33

4+
struct string_list;
5+
46
/**
57
* strbuf's are meant to be used with all the usual C string and memory
68
* APIs. Given that the length of the buffer is known, it's often better to
@@ -537,6 +539,20 @@ static inline struct strbuf **strbuf_split(const struct strbuf *sb,
537539
return strbuf_split_max(sb, terminator, 0);
538540
}
539541

542+
/*
543+
* Adds all strings of a string list to the strbuf, separated by the given
544+
* separator. For example, if sep is
545+
* ', '
546+
* and slist contains
547+
* ['element1', 'element2', ..., 'elementN'],
548+
* then write:
549+
* 'element1, element2, ..., elementN'
550+
* to str. If only one element, just write "element1" to str.
551+
*/
552+
extern void strbuf_add_separated_string_list(struct strbuf *str,
553+
const char *sep,
554+
struct string_list *slist);
555+
540556
/**
541557
* Free a NULL-terminated list of strbufs (for example, the return
542558
* values of the strbuf_split*() functions).

t/t3501-revert-cherry-pick.sh

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ test_expect_success 'cherry-pick "-" works with arguments' '
141141
test_cmp expect actual
142142
'
143143

144-
test_expect_failure 'cherry-pick works with dirty renamed file' '
144+
test_expect_success 'cherry-pick works with dirty renamed file' '
145145
test_commit to-rename &&
146146
git checkout -b unrelated &&
147147
test_commit unrelated &&
@@ -150,9 +150,8 @@ test_expect_failure 'cherry-pick works with dirty renamed file' '
150150
test_tick &&
151151
git commit -m renamed &&
152152
echo modified >renamed &&
153-
test_must_fail git cherry-pick refs/heads/unrelated >out &&
154-
test_i18ngrep "Refusing to lose dirty file at renamed" out &&
155-
test $(git rev-parse :0:renamed) = $(git rev-parse HEAD^:to-rename.t) &&
153+
git cherry-pick refs/heads/unrelated >out &&
154+
test $(git rev-parse :0:renamed) = $(git rev-parse HEAD~2:to-rename.t) &&
156155
grep -q "^modified$" renamed
157156
'
158157

t/t6022-merge-rename.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ test_expect_success 'merge of identical changes in a renamed file' '
247247
git reset --hard HEAD^ &&
248248
git checkout change &&
249249
GIT_MERGE_VERBOSITY=3 git merge change+rename >out &&
250-
test_i18ngrep "^Skipped B" out
250+
test_i18ngrep ! "^Skipped B" out
251251
'
252252

253253
test_expect_success 'setup for rename + d/f conflicts' '

0 commit comments

Comments
 (0)