Merge branch 'en/merge-directory-renames'

"git merge-recursive" backend recently learned a new heuristics to
infer file movement based on how other files in the same directory
moved.  As this is inherently less robust heuristics than the one
based on the content similarity of the file itself (rather than
based on what its neighbours are doing), it sometimes gives an
outcome unexpected by the end users.  This has been toned down to
leave the renamed paths in higher/conflicted stages in the index so
that the user can examine and confirm the result.

* en/merge-directory-renames:
  merge-recursive: switch directory rename detection default
  merge-recursive: give callers of handle_content_merge() access to contents
  merge-recursive: track information associated with directory renames
  t6043: fix copied test description to match its purpose
  merge-recursive: switch from (oid,mode) pairs to a diff_filespec
  merge-recursive: cleanup handle_rename_* function signatures
  merge-recursive: track branch where rename occurred in rename struct
  merge-recursive: remove ren[12]_other fields from rename_conflict_info
  merge-recursive: shrink rename_conflict_info
  merge-recursive: move some struct declarations together
  merge-recursive: use 'ci' for rename_conflict_info variable name
  merge-recursive: rename locals 'o' and 'a' to 'obuf' and 'abuf'
  merge-recursive: rename diff_filespec 'one' to 'o'
  merge-recursive: rename merge_options argument from 'o' to 'opt'
  Use 'unsigned short' for mode, like diff_filespec does
This commit is contained in:
Junio C Hamano 2019-05-09 00:37:22 +09:00
commit 96379f043f
19 changed files with 1363 additions and 1021 deletions

View File

@ -39,9 +39,22 @@ merge.renameLimit::
is turned off.
merge.renames::
Whether and how Git detects renames. If set to "false",
rename detection is disabled. If set to "true", basic rename
detection is enabled. Defaults to the value of diff.renames.
Whether Git detects renames. If set to "false", rename detection
is disabled. If set to "true", basic rename detection is enabled.
Defaults to the value of diff.renames.
merge.directoryRenames::
Whether Git detects directory renames, affecting what happens at
merge time to new files added to a directory on one side of
history when that directory was renamed on the other side of
history. If merge.directoryRenames is set to "false", directory
rename detection is disabled, meaning that such new files will be
left behind in the old directory. If set to "true", directory
rename detection is enabled, meaning that such new files will be
moved into the new directory. If set to "conflict", a conflict
will be reported for such paths. If merge.renames is false,
merge.directoryRenames is ignored and treated as false. Defaults
to "conflict".
merge.renormalize::
Tell Git that canonical representation of files in the

View File

@ -415,7 +415,7 @@ static void parse_treeish_arg(const char **argv,
if (prefix) {
struct object_id tree_oid;
unsigned int mode;
unsigned short mode;
int err;
err = get_tree_entry(&tree->object.oid, prefix, &tree_oid,

View File

@ -99,7 +99,7 @@ static void verify_working_tree_path(struct repository *r,
for (parents = work_tree->parents; parents; parents = parents->next) {
const struct object_id *commit_oid = &parents->item->object.oid;
struct object_id blob_oid;
unsigned mode;
unsigned short mode;
if (!get_tree_entry(commit_oid, path, &blob_oid, &mode) &&
oid_object_info(r, &blob_oid, NULL) == OBJ_BLOB)

View File

@ -52,7 +52,7 @@ struct blame_origin {
struct blame_entry *suspects;
mmfile_t file;
struct object_id blob_oid;
unsigned mode;
unsigned short mode;
/* guilty gets set when shipping any suspects to the final
* blame list instead of other commits
*/

View File

@ -110,7 +110,7 @@ static int check_local_mod(struct object_id *head, int index_only)
const struct cache_entry *ce;
const char *name = list.entry[i].name;
struct object_id oid;
unsigned mode;
unsigned short mode;
int local_changes = 0;
int staged_changes = 0;

View File

@ -597,7 +597,7 @@ static struct cache_entry *read_one_ent(const char *which,
struct object_id *ent, const char *path,
int namelen, int stage)
{
unsigned mode;
unsigned short mode;
struct object_id oid;
struct cache_entry *ce;

View File

@ -1333,7 +1333,7 @@ static inline int hex2chr(const char *s)
#define FALLBACK_DEFAULT_ABBREV 7
struct object_context {
unsigned mode;
unsigned short mode;
/*
* symlink_path is only used by get_tree_entry_follow_symlinks,
* and only for symlinks that point outside the repository.

2
fsck.c
View File

@ -604,7 +604,7 @@ static int fsck_tree(struct tree *item, struct fsck_options *options)
o_name = NULL;
while (desc.size) {
unsigned mode;
unsigned short mode;
const char *name;
const struct object_id *oid;

View File

@ -498,7 +498,7 @@ static struct commit *check_single_commit(struct rev_info *revs)
static void fill_blob_sha1(struct commit *commit, struct diff_filespec *spec)
{
unsigned mode;
unsigned short mode;
struct object_id oid;
if (get_tree_entry(&commit->object.oid, spec->path, &oid, &mode))

View File

@ -140,7 +140,7 @@ static void match_trees(const struct object_id *hash1,
while (one.size) {
const char *path;
const struct object_id *elem;
unsigned mode;
unsigned short mode;
int score;
elem = tree_entry_extract(&one, &path, &mode);
@ -196,7 +196,7 @@ static int splice_tree(const struct object_id *oid1, const char *prefix,
rewrite_here = NULL;
while (desc.size) {
const char *name;
unsigned mode;
unsigned short mode;
tree_entry_extract(&desc, &name, &mode);
if (strlen(name) == toplen &&
@ -285,7 +285,7 @@ void shift_tree(const struct object_id *hash1,
if (add_score < del_score) {
/* We need to pick a subtree of two */
unsigned mode;
unsigned short mode;
if (!*del_prefix)
return;
@ -313,7 +313,7 @@ void shift_tree_by(const struct object_id *hash1,
const char *shift_prefix)
{
struct object_id sub1, sub2;
unsigned mode1, mode2;
unsigned short mode1, mode2;
unsigned candidate = 0;
/* Can hash2 be a tree at shift_prefix in tree hash1? */

File diff suppressed because it is too large Load Diff

View File

@ -988,7 +988,7 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
combine_notes_fn combine_notes, int flags)
{
struct object_id oid, object_oid;
unsigned mode;
unsigned short mode;
struct leaf_node root_tree;
if (!t)

View File

@ -1608,7 +1608,7 @@ static void diagnose_invalid_oid_path(const char *prefix,
int object_name_len)
{
struct object_id oid;
unsigned mode;
unsigned short mode;
if (!prefix)
prefix = "";

View File

@ -42,7 +42,7 @@ test_expect_success 'rebase --interactive: directory rename detected' '
git checkout B^0 &&
set_fake_editor &&
FAKE_LINES="1" git rebase --interactive A &&
FAKE_LINES="1" git -c merge.directoryRenames=true rebase --interactive A &&
git ls-files -s >out &&
test_line_count = 5 out &&
@ -58,7 +58,7 @@ test_expect_failure 'rebase (am): directory rename detected' '
git checkout B^0 &&
git rebase A &&
git -c merge.directoryRenames=true rebase A &&
git ls-files -s >out &&
test_line_count = 5 out &&
@ -74,7 +74,7 @@ test_expect_success 'rebase --merge: directory rename detected' '
git checkout B^0 &&
git rebase --merge A &&
git -c merge.directoryRenames=true rebase --merge A &&
git ls-files -s >out &&
test_line_count = 5 out &&
@ -92,7 +92,7 @@ test_expect_failure 'am: directory rename detected' '
git format-patch -1 B &&
git am --3way 0001*.patch &&
git -c merge.directoryRenames=true am --3way 0001*.patch &&
git ls-files -s >out &&
test_line_count = 5 out &&

View File

@ -75,7 +75,7 @@ test_expect_success '1a-check: Simple directory rename detection' '
git checkout A^0 &&
git merge -s recursive B^0 &&
git -c merge.directoryRenames=true merge -s recursive B^0 >out &&
git ls-files -s >out &&
test_line_count = 4 out &&
@ -142,7 +142,7 @@ test_expect_success '1b-check: Merge a directory with another' '
git checkout A^0 &&
git merge -s recursive B^0 &&
git -c merge.directoryRenames=true merge -s recursive B^0 &&
git ls-files -s >out &&
test_line_count = 4 out &&
@ -201,7 +201,7 @@ test_expect_success '1c-check: Transitive renaming' '
git checkout A^0 &&
git merge -s recursive B^0 &&
git -c merge.directoryRenames=true merge -s recursive B^0 >out &&
git ls-files -s >out &&
test_line_count = 3 out &&
@ -270,7 +270,7 @@ test_expect_success '1d-check: Directory renames cause a rename/rename(2to1) con
git checkout A^0 &&
test_must_fail git merge -s recursive B^0 >out &&
test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out &&
test_i18ngrep "CONFLICT (rename/rename)" out &&
git ls-files -s >out &&
@ -350,7 +350,7 @@ test_expect_success '1e-check: Renamed directory, with all files being renamed t
git checkout A^0 &&
git merge -s recursive B^0 &&
git -c merge.directoryRenames=true merge -s recursive B^0 &&
git ls-files -s >out &&
test_line_count = 3 out &&
@ -416,7 +416,7 @@ test_expect_success '1f-check: Split a directory into two other directories' '
git checkout A^0 &&
git merge -s recursive B^0 &&
git -c merge.directoryRenames=true merge -s recursive B^0 &&
git ls-files -s >out &&
test_line_count = 6 out &&
@ -497,7 +497,7 @@ test_expect_success '2a-check: Directory split into two on one side, with equal
git checkout A^0 &&
test_must_fail git merge -s recursive B^0 >out &&
test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out &&
test_i18ngrep "CONFLICT.*directory rename split" out &&
git ls-files -s >out &&
@ -559,7 +559,7 @@ test_expect_success '2b-check: Directory split into two on one side, with equal
git checkout A^0 &&
git merge -s recursive B^0 >out &&
git -c merge.directoryRenames=true merge -s recursive B^0 >out &&
git ls-files -s >out &&
test_line_count = 3 out &&
@ -640,7 +640,7 @@ test_expect_success '3a-check: Avoid implicit rename if involved as source on ot
git checkout A^0 &&
git merge -s recursive B^0 &&
git -c merge.directoryRenames=true merge -s recursive B^0 &&
git ls-files -s >out &&
test_line_count = 3 out &&
@ -705,7 +705,7 @@ test_expect_success '3b-check: Avoid implicit rename if involved as source on cu
git checkout A^0 &&
test_must_fail git merge -s recursive B^0 >out &&
test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out &&
test_i18ngrep CONFLICT.*rename/rename.*z/d.*x/d.*w/d out &&
test_i18ngrep ! CONFLICT.*rename/rename.*y/d out &&
@ -826,7 +826,7 @@ test_expect_success '4a-check: Directory split, with original directory still pr
git checkout A^0 &&
git merge -s recursive B^0 &&
git -c merge.directoryRenames=true merge -s recursive B^0 &&
git ls-files -s >out &&
test_line_count = 5 out &&
@ -915,7 +915,7 @@ test_expect_success '5a-check: Merge directories, other side adds files to origi
git checkout A^0 &&
test_must_fail git merge -s recursive B^0 >out &&
test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out &&
test_i18ngrep "CONFLICT.*implicit dir rename" out &&
git ls-files -s >out &&
@ -989,7 +989,7 @@ test_expect_success '5b-check: Rename/delete in order to get add/add/add conflic
git checkout A^0 &&
test_must_fail git merge -s recursive B^0 >out &&
test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out &&
test_i18ngrep "CONFLICT (add/add).* y/d" out &&
git ls-files -s >out &&
@ -1069,7 +1069,7 @@ test_expect_success '5c-check: Transitive rename would cause rename/rename/renam
git checkout A^0 &&
test_must_fail git merge -s recursive B^0 >out &&
test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out &&
test_i18ngrep "CONFLICT (rename/rename).*x/d.*w/d.*z/d" out &&
test_i18ngrep "CONFLICT (add/add).* y/d" out &&
@ -1153,7 +1153,7 @@ test_expect_success '5d-check: Directory/file/file conflict due to directory ren
git checkout A^0 &&
test_must_fail git merge -s recursive B^0 >out &&
test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out &&
test_i18ngrep "CONFLICT (file/directory).*y/d" out &&
git ls-files -s >out &&
@ -1243,7 +1243,7 @@ test_expect_success '6a-check: Tricky rename/delete' '
git checkout A^0 &&
test_must_fail git merge -s recursive B^0 >out &&
test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out &&
test_i18ngrep "CONFLICT (rename/delete).*z/c.*y/c" out &&
git ls-files -s >out &&
@ -1308,7 +1308,7 @@ test_expect_success '6b-check: Same rename done on both sides' '
git checkout A^0 &&
git merge -s recursive B^0 &&
git -c merge.directoryRenames=true merge -s recursive B^0 &&
git ls-files -s >out &&
test_line_count = 3 out &&
@ -1370,7 +1370,7 @@ test_expect_success '6c-check: Rename only done on same side' '
git checkout A^0 &&
git merge -s recursive B^0 &&
git -c merge.directoryRenames=true merge -s recursive B^0 &&
git ls-files -s >out &&
test_line_count = 3 out &&
@ -1432,7 +1432,7 @@ test_expect_success '6d-check: We do not always want transitive renaming' '
git checkout A^0 &&
git merge -s recursive B^0 &&
git -c merge.directoryRenames=true merge -s recursive B^0 &&
git ls-files -s >out &&
test_line_count = 3 out &&
@ -1495,7 +1495,7 @@ test_expect_success '6e-check: Add/add from one side' '
git checkout A^0 &&
git merge -s recursive B^0 &&
git -c merge.directoryRenames=true merge -s recursive B^0 &&
git ls-files -s >out &&
test_line_count = 4 out &&
@ -1591,7 +1591,7 @@ test_expect_success '7a-check: rename-dir vs. rename-dir (NOT split evenly) PLUS
git checkout A^0 &&
test_must_fail git merge -s recursive B^0 >out &&
test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out &&
test_i18ngrep "CONFLICT (rename/rename).*z/b.*y/b.*w/b" out &&
test_i18ngrep "CONFLICT (rename/rename).*z/c.*y/c.*x/c" out &&
@ -1663,7 +1663,7 @@ test_expect_success '7b-check: rename/rename(2to1), but only due to transitive r
git checkout A^0 &&
test_must_fail git merge -s recursive B^0 >out &&
test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out &&
test_i18ngrep "CONFLICT (rename/rename)" out &&
git ls-files -s >out &&
@ -1740,7 +1740,7 @@ test_expect_success '7c-check: rename/rename(1to...2or3); transitive rename may
git checkout A^0 &&
test_must_fail git merge -s recursive B^0 >out &&
test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out &&
test_i18ngrep "CONFLICT (rename/rename).*x/d.*w/d.*y/d" out &&
git ls-files -s >out &&
@ -1804,7 +1804,7 @@ test_expect_success '7d-check: transitive rename involved in rename/delete; how
git checkout A^0 &&
test_must_fail git merge -s recursive B^0 >out &&
test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out &&
test_i18ngrep "CONFLICT (rename/delete).*x/d.*y/d" out &&
git ls-files -s >out &&
@ -1894,7 +1894,7 @@ test_expect_success '7e-check: transitive rename in rename/delete AND dirs in th
git checkout A^0 &&
test_must_fail git merge -s recursive B^0 >out &&
test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out &&
test_i18ngrep "CONFLICT (rename/delete).*x/d.*y/d" out &&
git ls-files -s >out &&
@ -1985,7 +1985,7 @@ test_expect_success '8a-check: Dual-directory rename, one into the others way' '
git checkout A^0 &&
git merge -s recursive B^0 &&
git -c merge.directoryRenames=true merge -s recursive B^0 &&
git ls-files -s >out &&
test_line_count = 6 out &&
@ -2063,7 +2063,7 @@ test_expect_success '8b-check: Dual-directory rename, one into the others way, w
git checkout A^0 &&
git merge -s recursive B^0 &&
git -c merge.directoryRenames=true merge -s recursive B^0 &&
git ls-files -s >out &&
test_line_count = 6 out &&
@ -2135,7 +2135,7 @@ test_expect_success '8c-check: modify/delete or rename+modify/delete' '
git checkout A^0 &&
test_must_fail git merge -s recursive B^0 >out &&
test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out &&
test_i18ngrep "CONFLICT (modify/delete).* z/d" out &&
git ls-files -s >out &&
@ -2212,7 +2212,7 @@ test_expect_success '8d-check: rename/delete...or not?' '
git checkout A^0 &&
git merge -s recursive B^0 &&
git -c merge.directoryRenames=true merge -s recursive B^0 &&
git ls-files -s >out &&
test_line_count = 3 out &&
@ -2287,7 +2287,7 @@ test_expect_success '8e-check: Both sides rename, one side adds to original dire
git checkout A^0 &&
test_must_fail git merge -s recursive B^0 >out 2>err &&
test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err &&
test_i18ngrep CONFLICT.*rename/rename.*z/c.*y/c.*w/c out &&
test_i18ngrep CONFLICT.*rename/rename.*z/b.*y/b.*w/b out &&
@ -2374,7 +2374,7 @@ test_expect_success '9a-check: Inner renamed directory within outer renamed dire
git checkout A^0 &&
git merge -s recursive B^0 &&
git -c merge.directoryRenames=true merge -s recursive B^0 &&
git ls-files -s >out &&
test_line_count = 7 out &&
@ -2444,7 +2444,7 @@ test_expect_success '9b-check: Transitive rename with content merge' '
git checkout A^0 &&
git merge -s recursive B^0 &&
git -c merge.directoryRenames=true merge -s recursive B^0 &&
git ls-files -s >out &&
test_line_count = 3 out &&
@ -2534,7 +2534,7 @@ test_expect_success '9c-check: Doubly transitive rename?' '
git checkout A^0 &&
git merge -s recursive B^0 >out &&
git -c merge.directoryRenames=true merge -s recursive B^0 >out &&
test_i18ngrep "WARNING: Avoiding applying x -> z rename to x/f" out &&
git ls-files -s >out &&
@ -2622,7 +2622,7 @@ test_expect_success '9d-check: N-way transitive rename?' '
git checkout A^0 &&
git merge -s recursive B^0 >out &&
git -c merge.directoryRenames=true merge -s recursive B^0 >out &&
test_i18ngrep "WARNING: Avoiding applying z -> y rename to z/t" out &&
test_i18ngrep "WARNING: Avoiding applying y -> x rename to y/a" out &&
test_i18ngrep "WARNING: Avoiding applying x -> w rename to x/b" out &&
@ -2704,7 +2704,7 @@ test_expect_success C_LOCALE_OUTPUT '9e-check: N-to-1 whammo' '
git checkout A^0 &&
test_must_fail git merge -s recursive B^0 >out &&
test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out &&
grep "CONFLICT (implicit dir rename): Cannot map more than one path to combined/yo" out >error_line &&
grep -q dir1/yo error_line &&
grep -q dir2/yo error_line &&
@ -2782,7 +2782,7 @@ test_expect_success '9f-check: Renamed directory that only contained immediate s
git checkout A^0 &&
git merge -s recursive B^0 &&
git -c merge.directoryRenames=true merge -s recursive B^0 &&
git ls-files -s >out &&
test_line_count = 4 out &&
@ -2849,7 +2849,7 @@ test_expect_failure '9g-check: Renamed directory that only contained immediate s
git checkout A^0 &&
git merge -s recursive B^0 &&
git -c merge.directoryRenames=true merge -s recursive B^0 &&
git ls-files -s >out &&
test_line_count = 4 out &&
@ -2918,7 +2918,7 @@ test_expect_success '9h-check: Avoid dir rename on merely modified path' '
git checkout A^0 &&
git merge -s recursive B^0 &&
git -c merge.directoryRenames=true merge -s recursive B^0 &&
git ls-files -s >out &&
test_line_count = 3 out &&
@ -2993,7 +2993,7 @@ test_expect_success '10a-check: Overwrite untracked with normal rename/delete' '
echo very >z/c &&
echo important >z/d &&
test_must_fail git merge -s recursive B^0 >out 2>err &&
test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err &&
test_i18ngrep "The following untracked working tree files would be overwritten by merge" err &&
git ls-files -s >out &&
@ -3061,7 +3061,7 @@ test_expect_success '10b-check: Overwrite untracked with dir rename + delete' '
echo important >y/d &&
echo contents >y/e &&
test_must_fail git merge -s recursive B^0 >out 2>err &&
test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err &&
test_i18ngrep "CONFLICT (rename/delete).*Version B\^0 of y/d left in tree at y/d~B\^0" out &&
test_i18ngrep "Error: Refusing to lose untracked file at y/e; writing to y/e~B\^0 instead" out &&
@ -3137,7 +3137,7 @@ test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2)
git checkout A^0 &&
echo important >y/c &&
test_must_fail git merge -s recursive B^0 >out 2>err &&
test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err &&
test_i18ngrep "CONFLICT (rename/rename)" out &&
test_i18ngrep "Refusing to lose untracked file at y/c; adding as y/c~B\^0 instead" out &&
@ -3174,7 +3174,7 @@ test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2)
mkdir y &&
echo important >y/c &&
test_must_fail git merge -s recursive A^0 >out 2>err &&
test_must_fail git -c merge.directoryRenames=true merge -s recursive A^0 >out 2>err &&
test_i18ngrep "CONFLICT (rename/rename)" out &&
test_i18ngrep "Refusing to lose untracked file at y/c; adding as y/c~HEAD instead" out &&
@ -3249,7 +3249,7 @@ test_expect_success '10d-check: Delete untracked with dir rename/rename(2to1)' '
git checkout A^0 &&
echo important >y/wham &&
test_must_fail git merge -s recursive B^0 >out 2>err &&
test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err &&
test_i18ngrep "CONFLICT (rename/rename)" out &&
test_i18ngrep "Refusing to lose untracked file at y/wham" out &&
@ -3327,7 +3327,7 @@ test_expect_failure '10e-check: Does git complain about untracked file that is n
mkdir z &&
echo random >z/c &&
git merge -s recursive B^0 >out 2>err &&
git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err &&
test_i18ngrep ! "following untracked working tree files would be overwritten by merge" err &&
git ls-files -s >out &&
@ -3407,7 +3407,7 @@ test_expect_success '11a-check: Avoid losing dirty contents with simple rename'
git checkout A^0 &&
echo stuff >>z/c &&
test_must_fail git merge -s recursive B^0 >out 2>err &&
test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err &&
test_i18ngrep "Refusing to lose dirty file at z/c" out &&
test_seq 1 10 >expected &&
@ -3479,7 +3479,7 @@ test_expect_success '11b-check: Avoid losing dirty file involved in directory re
git checkout A^0 &&
echo stuff >>z/c &&
git merge -s recursive B^0 >out 2>err &&
git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err &&
test_i18ngrep "Refusing to lose dirty file at z/c" out &&
grep -q stuff z/c &&
@ -3554,7 +3554,7 @@ test_expect_success '11c-check: Avoid losing not-uptodate with rename + D/F conf
git checkout A^0 &&
echo stuff >>y/c &&
test_must_fail git merge -s recursive B^0 >out 2>err &&
test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err &&
test_i18ngrep "following files would be overwritten by merge" err &&
grep -q stuff y/c &&
@ -3621,7 +3621,7 @@ test_expect_success '11d-check: Avoid losing not-uptodate with rename + D/F conf
git checkout A^0 &&
echo stuff >>z/c &&
test_must_fail git merge -s recursive B^0 >out 2>err &&
test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err &&
test_i18ngrep "Refusing to lose dirty file at z/c" out &&
grep -q stuff z/c &&
@ -3700,7 +3700,7 @@ test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rena
git checkout A^0 &&
echo mods >>y/c &&
test_must_fail git merge -s recursive B^0 >out 2>err &&
test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err &&
test_i18ngrep "CONFLICT (rename/rename)" out &&
test_i18ngrep "Refusing to lose dirty file at y/c" out &&
@ -3782,7 +3782,7 @@ test_expect_success '11f-check: Avoid deleting not-uptodate with dir rename/rena
git checkout A^0 &&
echo important >>y/wham &&
test_must_fail git merge -s recursive B^0 >out 2>err &&
test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err &&
test_i18ngrep "CONFLICT (rename/rename)" out &&
test_i18ngrep "Refusing to lose dirty file at y/wham" out &&
@ -3870,7 +3870,7 @@ test_expect_success '12a-check: Moving one directory hierarchy into another' '
git checkout A^0 &&
git merge -s recursive B^0 &&
git -c merge.directoryRenames=true merge -s recursive B^0 &&
git ls-files -s >out &&
test_line_count = 6 out &&
@ -3910,7 +3910,7 @@ test_expect_success '12a-check: Moving one directory hierarchy into another' '
# To which, I can do no more than shrug my shoulders and say that
# even simple rules give weird results when given weird inputs.
test_expect_success '12b-setup: Moving one directory hierarchy into another' '
test_expect_success '12b-setup: Moving two directory hierarchies into each other' '
test_create_repo 12b &&
(
cd 12b &&
@ -3940,13 +3940,13 @@ test_expect_success '12b-setup: Moving one directory hierarchy into another' '
)
'
test_expect_success '12b-check: Moving one directory hierarchy into another' '
test_expect_success '12b-check: Moving two directory hierarchies into each other' '
(
cd 12b &&
git checkout A^0 &&
git merge -s recursive B^0 &&
git -c merge.directoryRenames=true merge -s recursive B^0 &&
git ls-files -s >out &&
test_line_count = 4 out &&
@ -4016,7 +4016,7 @@ test_expect_success '12c-check: Moving one directory hierarchy into another w/ c
git checkout A^0 &&
test_must_fail git merge -s recursive B^0 &&
test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 &&
git ls-files -u >out &&
test_line_count = 12 out &&
@ -4051,4 +4051,356 @@ test_expect_success '12c-check: Moving one directory hierarchy into another w/ c
)
'
###########################################################################
# SECTION 13: Checking informational and conflict messages
#
# A year after directory rename detection became the default, it was
# instead decided to report conflicts on the pathname on the basis that
# some users may expect the new files added or moved into a directory to
# be unrelated to all the other files in that directory, and thus that
# directory rename detection is unexpected. Test that the messages printed
# match our expectation.
###########################################################################
# Testcase 13a, Basic directory rename with newly added files
# Commit O: z/{b,c}
# Commit A: y/{b,c}
# Commit B: z/{b,c,d,e/f}
# Expected: y/{b,c,d,e/f}, with notices/conflicts for both y/d and y/e/f
test_expect_success '13a-setup: messages for newly added files' '
test_create_repo 13a &&
(
cd 13a &&
mkdir z &&
echo b >z/b &&
echo c >z/c &&
git add z &&
test_tick &&
git commit -m "O" &&
git branch O &&
git branch A &&
git branch B &&
git checkout A &&
git mv z y &&
test_tick &&
git commit -m "A" &&
git checkout B &&
echo d >z/d &&
mkdir z/e &&
echo f >z/e/f &&
git add z/d z/e/f &&
test_tick &&
git commit -m "B"
)
'
test_expect_success '13a-check(conflict): messages for newly added files' '
(
cd 13a &&
git checkout A^0 &&
test_must_fail git merge -s recursive B^0 >out 2>err &&
test_i18ngrep CONFLICT..file.location.*z/e/f.added.in.B^0.*y/e/f out &&
test_i18ngrep CONFLICT..file.location.*z/d.added.in.B^0.*y/d out &&
git ls-files >paths &&
! grep z/ paths &&
grep "y/[de]" paths &&
test_path_is_missing z/d &&
test_path_is_file y/d &&
test_path_is_missing z/e/f &&
test_path_is_file y/e/f
)
'
test_expect_success '13a-check(info): messages for newly added files' '
(
cd 13a &&
git reset --hard &&
git checkout A^0 &&
git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err &&
test_i18ngrep Path.updated:.*z/e/f.added.in.B^0.*y/e/f out &&
test_i18ngrep Path.updated:.*z/d.added.in.B^0.*y/d out &&
git ls-files >paths &&
! grep z/ paths &&
grep "y/[de]" paths &&
test_path_is_missing z/d &&
test_path_is_file y/d &&
test_path_is_missing z/e/f &&
test_path_is_file y/e/f
)
'
# Testcase 13b, Transitive rename with conflicted content merge and default
# "conflict" setting
# (Related to testcase 1c, 9b)
# Commit O: z/{b,c}, x/d_1
# Commit A: y/{b,c}, x/d_2
# Commit B: z/{b,c,d_3}
# Expected: y/{b,c,d_merged}, with two conflict messages for y/d,
# one about content, and one about file location
test_expect_success '13b-setup: messages for transitive rename with conflicted content' '
test_create_repo 13b &&
(
cd 13b &&
mkdir x &&
mkdir z &&
test_seq 1 10 >x/d &&
echo b >z/b &&
echo c >z/c &&
git add x z &&
test_tick &&
git commit -m "O" &&
git branch O &&
git branch A &&
git branch B &&
git checkout A &&
git mv z y &&
echo 11 >>x/d &&
git add x/d &&
test_tick &&
git commit -m "A" &&
git checkout B &&
echo eleven >>x/d &&
git mv x/d z/d &&
git add z/d &&
test_tick &&
git commit -m "B"
)
'
test_expect_success '13b-check(conflict): messages for transitive rename with conflicted content' '
(
cd 13b &&
git checkout A^0 &&
test_must_fail git merge -s recursive B^0 >out 2>err &&
test_i18ngrep CONFLICT.*content.*Merge.conflict.in.y/d out &&
test_i18ngrep CONFLICT..file.location.*x/d.renamed.to.z/d.*moved.to.y/d out &&
git ls-files >paths &&
! grep z/ paths &&
grep "y/d" paths &&
test_path_is_missing z/d &&
test_path_is_file y/d
)
'
test_expect_success '13b-check(info): messages for transitive rename with conflicted content' '
(
cd 13b &&
git reset --hard &&
git checkout A^0 &&
test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err &&
test_i18ngrep CONFLICT.*content.*Merge.conflict.in.y/d out &&
test_i18ngrep Path.updated:.*x/d.renamed.to.z/d.in.B^0.*moving.it.to.y/d out &&
git ls-files >paths &&
! grep z/ paths &&
grep "y/d" paths &&
test_path_is_missing z/d &&
test_path_is_file y/d
)
'
# Testcase 13c, Rename/rename(1to1) due to directory rename
# Commit O: z/{b,c}, x/{d,e}
# Commit A: y/{b,c,d}, x/e
# Commit B: z/{b,c,d}, x/e
# Expected: y/{b,c,d}, with info or conflict messages for d (
# A: renamed x/d -> z/d; B: renamed z/ -> y/ AND renamed x/d to y/d
# One could argue A had partial knowledge of what was done with
# d and B had full knowledge, but that's a slippery slope as
# shown in testcase 13d.
test_expect_success '13c-setup: messages for rename/rename(1to1) via transitive rename' '
test_create_repo 13c &&
(
cd 13c &&
mkdir x &&
mkdir z &&
test_seq 1 10 >x/d &&
echo e >x/e &&
echo b >z/b &&
echo c >z/c &&
git add x z &&
test_tick &&
git commit -m "O" &&
git branch O &&
git branch A &&
git branch B &&
git checkout A &&
git mv z y &&
git mv x/d y/ &&
test_tick &&
git commit -m "A" &&
git checkout B &&
git mv x/d z/d &&
git add z/d &&
test_tick &&
git commit -m "B"
)
'
test_expect_success '13c-check(conflict): messages for rename/rename(1to1) via transitive rename' '
(
cd 13c &&
git checkout A^0 &&
test_must_fail git merge -s recursive B^0 >out 2>err &&
test_i18ngrep CONFLICT..file.location.*x/d.renamed.to.z/d.*moved.to.y/d out &&
git ls-files >paths &&
! grep z/ paths &&
grep "y/d" paths &&
test_path_is_missing z/d &&
test_path_is_file y/d
)
'
test_expect_success '13c-check(info): messages for rename/rename(1to1) via transitive rename' '
(
cd 13c &&
git reset --hard &&
git checkout A^0 &&
git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err &&
test_i18ngrep Path.updated:.*x/d.renamed.to.z/d.in.B^0.*moving.it.to.y/d out &&
git ls-files >paths &&
! grep z/ paths &&
grep "y/d" paths &&
test_path_is_missing z/d &&
test_path_is_file y/d
)
'
# Testcase 13d, Rename/rename(1to1) due to directory rename on both sides
# Commit O: a/{z,y}, b/x, c/w
# Commit A: a/z, b/{y,x}, d/w
# Commit B: a/z, d/x, c/{y,w}
# Expected: a/z, d/{y,x,w} with no file location conflict for x
# Easy cases:
# * z is always in a; so it stays in a.
# * x starts in b, only modified on one side to move into d/
# * w starts in c, only modified on one side to move into d/
# Hard case:
# * A renames a/y to b/y, and B renames b/->d/ => a/y -> d/y
# * B renames a/y to c/y, and A renames c/->d/ => a/y -> d/y
# No conflict in where a/y ends up, so put it in d/y.
test_expect_success '13d-setup: messages for rename/rename(1to1) via dual transitive rename' '
test_create_repo 13d &&
(
cd 13d &&
mkdir a &&
mkdir b &&
mkdir c &&
echo z >a/z &&
echo y >a/y &&
echo x >b/x &&
echo w >c/w &&
git add a b c &&
test_tick &&
git commit -m "O" &&
git branch O &&
git branch A &&
git branch B &&
git checkout A &&
git mv a/y b/ &&
git mv c/ d/ &&
test_tick &&
git commit -m "A" &&
git checkout B &&
git mv a/y c/ &&
git mv b/ d/ &&
test_tick &&
git commit -m "B"
)
'
test_expect_success '13d-check(conflict): messages for rename/rename(1to1) via dual transitive rename' '
(
cd 13d &&
git checkout A^0 &&
test_must_fail git merge -s recursive B^0 >out 2>err &&
test_i18ngrep CONFLICT..file.location.*a/y.renamed.to.b/y.*moved.to.d/y out &&
test_i18ngrep CONFLICT..file.location.*a/y.renamed.to.c/y.*moved.to.d/y out &&
git ls-files >paths &&
! grep b/ paths &&
! grep c/ paths &&
grep "d/y" paths &&
test_path_is_missing b/y &&
test_path_is_missing c/y &&
test_path_is_file d/y
)
'
test_expect_success '13d-check(info): messages for rename/rename(1to1) via dual transitive rename' '
(
cd 13d &&
git reset --hard &&
git checkout A^0 &&
git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err &&
test_i18ngrep Path.updated.*a/y.renamed.to.b/y.*moving.it.to.d/y out &&
test_i18ngrep Path.updated.*a/y.renamed.to.c/y.*moving.it.to.d/y out &&
git ls-files >paths &&
! grep b/ paths &&
! grep c/ paths &&
grep "d/y" paths &&
test_path_is_missing b/y &&
test_path_is_missing c/y &&
test_path_is_file d/y
)
'
test_done

View File

@ -466,7 +466,7 @@ test_expect_success '3a-check-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
git checkout A^0 &&
GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
GIT_MERGE_VERBOSITY=3 git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err &&
test_i18ngrep ! "Skipped bar/bq" out &&
test_must_be_empty err &&
@ -495,7 +495,7 @@ test_expect_success '3a-check-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
git checkout B^0 &&
GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err &&
GIT_MERGE_VERBOSITY=3 git -c merge.directoryRenames=true merge -s recursive A^0 >out 2>err &&
test_i18ngrep ! "Skipped bar/bq" out &&
test_must_be_empty err &&
@ -560,7 +560,7 @@ test_expect_success '3b-check-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
git checkout A^0 &&
GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
GIT_MERGE_VERBOSITY=3 git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err &&
test_i18ngrep ! "Skipped bar/bq" out &&
test_must_be_empty err &&
@ -589,7 +589,7 @@ test_expect_success '3b-check-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
git checkout B^0 &&
GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err &&
GIT_MERGE_VERBOSITY=3 git -c merge.directoryRenames=true merge -s recursive A^0 >out 2>err &&
test_i18ngrep ! "Skipped bar/bq" out &&
test_must_be_empty err &&

View File

@ -181,7 +181,7 @@ static struct combine_diff_path *emit_path(struct combine_diff_path *p,
struct tree_desc *t, struct tree_desc *tp,
int imin)
{
unsigned mode;
unsigned short mode;
const char *path;
const struct object_id *oid;
int pathlen;

View File

@ -500,7 +500,7 @@ struct dir_state {
struct object_id oid;
};
static int find_tree_entry(struct tree_desc *t, const char *name, struct object_id *result, unsigned *mode)
static int find_tree_entry(struct tree_desc *t, const char *name, struct object_id *result, unsigned short *mode)
{
int namelen = strlen(name);
while (t->size) {
@ -535,7 +535,7 @@ static int find_tree_entry(struct tree_desc *t, const char *name, struct object_
return -1;
}
int get_tree_entry(const struct object_id *tree_oid, const char *name, struct object_id *oid, unsigned *mode)
int get_tree_entry(const struct object_id *tree_oid, const char *name, struct object_id *oid, unsigned short *mode)
{
int retval;
void *tree;
@ -585,7 +585,7 @@ int get_tree_entry(const struct object_id *tree_oid, const char *name, struct ob
* See the code for enum get_oid_result for a description of
* the return values.
*/
enum get_oid_result get_tree_entry_follow_symlinks(struct object_id *tree_oid, const char *name, struct object_id *result, struct strbuf *result_path, unsigned *mode)
enum get_oid_result get_tree_entry_follow_symlinks(struct object_id *tree_oid, const char *name, struct object_id *result, struct strbuf *result_path, unsigned short *mode)
{
int retval = MISSING_OBJECT;
struct dir_state *parents = NULL;

View File

@ -16,7 +16,7 @@ struct tree_desc {
unsigned int size;
};
static inline const struct object_id *tree_entry_extract(struct tree_desc *desc, const char **pathp, unsigned int *modep)
static inline const struct object_id *tree_entry_extract(struct tree_desc *desc, const char **pathp, unsigned short *modep)
{
*pathp = desc->entry.path;
*modep = desc->entry.mode;
@ -51,7 +51,7 @@ struct traverse_info;
typedef int (*traverse_callback_t)(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *);
int traverse_trees(struct index_state *istate, int n, struct tree_desc *t, struct traverse_info *info);
enum get_oid_result get_tree_entry_follow_symlinks(struct object_id *tree_oid, const char *name, struct object_id *result, struct strbuf *result_path, unsigned *mode);
enum get_oid_result get_tree_entry_follow_symlinks(struct object_id *tree_oid, const char *name, struct object_id *result, struct strbuf *result_path, unsigned short *mode);
struct traverse_info {
const char *traverse_path;
@ -66,7 +66,7 @@ struct traverse_info {
int show_all_errors;
};
int get_tree_entry(const struct object_id *, const char *, struct object_id *, unsigned *);
int get_tree_entry(const struct object_id *, const char *, struct object_id *, unsigned short *);
extern char *make_traverse_path(char *path, const struct traverse_info *info, const struct name_entry *n);
extern void setup_traverse_info(struct traverse_info *info, const char *base);