зеркало из https://github.com/microsoft/git.git
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 ...
This commit is contained in:
Коммит
c67de747f4
1372
merge-recursive.c
1372
merge-recursive.c
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,6 +1,7 @@
|
|||
#ifndef MERGE_RECURSIVE_H
|
||||
#define MERGE_RECURSIVE_H
|
||||
|
||||
#include "unpack-trees.h"
|
||||
#include "string-list.h"
|
||||
|
||||
struct merge_options {
|
||||
|
@ -27,6 +28,33 @@ struct merge_options {
|
|||
struct strbuf obuf;
|
||||
struct hashmap current_file_dir_set;
|
||||
struct string_list df_conflict_file_set;
|
||||
struct unpack_trees_options unpack_opts;
|
||||
struct index_state orig_index;
|
||||
};
|
||||
|
||||
/*
|
||||
* For dir_rename_entry, directory names are stored as a full path from the
|
||||
* toplevel of the repository and do not include a trailing '/'. Also:
|
||||
*
|
||||
* dir: original name of directory being renamed
|
||||
* non_unique_new_dir: if true, could not determine new_dir
|
||||
* new_dir: final name of directory being renamed
|
||||
* possible_new_dirs: temporary used to help determine new_dir; see comments
|
||||
* in get_directory_renames() for details
|
||||
*/
|
||||
struct dir_rename_entry {
|
||||
struct hashmap_entry ent; /* must be the first member! */
|
||||
char *dir;
|
||||
unsigned non_unique_new_dir:1;
|
||||
struct strbuf new_dir;
|
||||
struct string_list possible_new_dirs;
|
||||
};
|
||||
|
||||
struct collision_entry {
|
||||
struct hashmap_entry ent; /* must be the first member! */
|
||||
char *target_file;
|
||||
struct string_list source_files;
|
||||
unsigned reported_already:1;
|
||||
};
|
||||
|
||||
/* merge_trees() but with recursive ancestor consolidation */
|
||||
|
|
16
strbuf.c
16
strbuf.c
|
@ -1,5 +1,6 @@
|
|||
#include "cache.h"
|
||||
#include "refs.h"
|
||||
#include "string-list.h"
|
||||
#include "utf8.h"
|
||||
|
||||
int starts_with(const char *str, const char *prefix)
|
||||
|
@ -180,6 +181,21 @@ struct strbuf **strbuf_split_buf(const char *str, size_t slen,
|
|||
return ret;
|
||||
}
|
||||
|
||||
void strbuf_add_separated_string_list(struct strbuf *str,
|
||||
const char *sep,
|
||||
struct string_list *slist)
|
||||
{
|
||||
struct string_list_item *item;
|
||||
int sep_needed = 0;
|
||||
|
||||
for_each_string_list_item(item, slist) {
|
||||
if (sep_needed)
|
||||
strbuf_addstr(str, sep);
|
||||
strbuf_addstr(str, item->string);
|
||||
sep_needed = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void strbuf_list_free(struct strbuf **sbs)
|
||||
{
|
||||
struct strbuf **s = sbs;
|
||||
|
|
16
strbuf.h
16
strbuf.h
|
@ -1,6 +1,8 @@
|
|||
#ifndef STRBUF_H
|
||||
#define STRBUF_H
|
||||
|
||||
struct string_list;
|
||||
|
||||
/**
|
||||
* strbuf's are meant to be used with all the usual C string and memory
|
||||
* 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,
|
|||
return strbuf_split_max(sb, terminator, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Adds all strings of a string list to the strbuf, separated by the given
|
||||
* separator. For example, if sep is
|
||||
* ', '
|
||||
* and slist contains
|
||||
* ['element1', 'element2', ..., 'elementN'],
|
||||
* then write:
|
||||
* 'element1, element2, ..., elementN'
|
||||
* to str. If only one element, just write "element1" to str.
|
||||
*/
|
||||
extern void strbuf_add_separated_string_list(struct strbuf *str,
|
||||
const char *sep,
|
||||
struct string_list *slist);
|
||||
|
||||
/**
|
||||
* Free a NULL-terminated list of strbufs (for example, the return
|
||||
* values of the strbuf_split*() functions).
|
||||
|
|
|
@ -141,7 +141,7 @@ test_expect_success 'cherry-pick "-" works with arguments' '
|
|||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_failure 'cherry-pick works with dirty renamed file' '
|
||||
test_expect_success 'cherry-pick works with dirty renamed file' '
|
||||
test_commit to-rename &&
|
||||
git checkout -b unrelated &&
|
||||
test_commit unrelated &&
|
||||
|
@ -150,9 +150,8 @@ test_expect_failure 'cherry-pick works with dirty renamed file' '
|
|||
test_tick &&
|
||||
git commit -m renamed &&
|
||||
echo modified >renamed &&
|
||||
test_must_fail git cherry-pick refs/heads/unrelated >out &&
|
||||
test_i18ngrep "Refusing to lose dirty file at renamed" out &&
|
||||
test $(git rev-parse :0:renamed) = $(git rev-parse HEAD^:to-rename.t) &&
|
||||
git cherry-pick refs/heads/unrelated >out &&
|
||||
test $(git rev-parse :0:renamed) = $(git rev-parse HEAD~2:to-rename.t) &&
|
||||
grep -q "^modified$" renamed
|
||||
'
|
||||
|
||||
|
|
|
@ -247,7 +247,7 @@ test_expect_success 'merge of identical changes in a renamed file' '
|
|||
git reset --hard HEAD^ &&
|
||||
git checkout change &&
|
||||
GIT_MERGE_VERBOSITY=3 git merge change+rename >out &&
|
||||
test_i18ngrep "^Skipped B" out
|
||||
test_i18ngrep ! "^Skipped B" out
|
||||
'
|
||||
|
||||
test_expect_success 'setup for rename + d/f conflicts' '
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,761 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description="merge cases"
|
||||
|
||||
# The setup for all of them, pictorially, is:
|
||||
#
|
||||
# A
|
||||
# o
|
||||
# / \
|
||||
# O o ?
|
||||
# \ /
|
||||
# o
|
||||
# B
|
||||
#
|
||||
# To help make it easier to follow the flow of tests, they have been
|
||||
# divided into sections and each test will start with a quick explanation
|
||||
# of what commits O, A, and B contain.
|
||||
#
|
||||
# Notation:
|
||||
# z/{b,c} means files z/b and z/c both exist
|
||||
# x/d_1 means file x/d exists with content d1. (Purpose of the
|
||||
# underscore notation is to differentiate different
|
||||
# files that might be renamed into each other's paths.)
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
|
||||
###########################################################################
|
||||
# SECTION 1: Cases involving no renames (one side has subset of changes of
|
||||
# the other side)
|
||||
###########################################################################
|
||||
|
||||
# Testcase 1a, Changes on A, subset of changes on B
|
||||
# Commit O: b_1
|
||||
# Commit A: b_2
|
||||
# Commit B: b_3
|
||||
# Expected: b_2
|
||||
|
||||
test_expect_success '1a-setup: Modify(A)/Modify(B), change on B subset of A' '
|
||||
test_create_repo 1a &&
|
||||
(
|
||||
cd 1a &&
|
||||
|
||||
test_write_lines 1 2 3 4 5 6 7 8 9 10 >b &&
|
||||
git add b &&
|
||||
test_tick &&
|
||||
git commit -m "O" &&
|
||||
|
||||
git branch O &&
|
||||
git branch A &&
|
||||
git branch B &&
|
||||
|
||||
git checkout A &&
|
||||
test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b &&
|
||||
git add b &&
|
||||
test_tick &&
|
||||
git commit -m "A" &&
|
||||
|
||||
git checkout B &&
|
||||
test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b &&
|
||||
git add b &&
|
||||
test_tick &&
|
||||
git commit -m "B"
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success '1a-check-L: Modify(A)/Modify(B), change on B subset of A' '
|
||||
test_when_finished "git -C 1a reset --hard" &&
|
||||
test_when_finished "git -C 1a clean -fd" &&
|
||||
(
|
||||
cd 1a &&
|
||||
|
||||
git checkout A^0 &&
|
||||
|
||||
test-tool chmtime =31337 b &&
|
||||
test-tool chmtime -v +0 b >expected-mtime &&
|
||||
|
||||
GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
|
||||
|
||||
test_i18ngrep "Skipped b" out &&
|
||||
test_must_be_empty err &&
|
||||
|
||||
test-tool chmtime -v +0 b >actual-mtime &&
|
||||
test_cmp expected-mtime actual-mtime &&
|
||||
|
||||
git ls-files -s >index_files &&
|
||||
test_line_count = 1 index_files &&
|
||||
|
||||
git rev-parse >actual HEAD:b &&
|
||||
git rev-parse >expect A:b &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
git hash-object b >actual &&
|
||||
git rev-parse A:b >expect &&
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success '1a-check-R: Modify(A)/Modify(B), change on B subset of A' '
|
||||
test_when_finished "git -C 1a reset --hard" &&
|
||||
test_when_finished "git -C 1a clean -fd" &&
|
||||
(
|
||||
cd 1a &&
|
||||
|
||||
git checkout B^0 &&
|
||||
|
||||
GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err &&
|
||||
|
||||
test_i18ngrep "Auto-merging b" out &&
|
||||
test_must_be_empty err &&
|
||||
|
||||
git ls-files -s >index_files &&
|
||||
test_line_count = 1 index_files &&
|
||||
|
||||
git rev-parse >actual HEAD:b &&
|
||||
git rev-parse >expect A:b &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
git hash-object b >actual &&
|
||||
git rev-parse A:b >expect &&
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
||||
|
||||
###########################################################################
|
||||
# SECTION 2: Cases involving basic renames
|
||||
###########################################################################
|
||||
|
||||
# Testcase 2a, Changes on A, rename on B
|
||||
# Commit O: b_1
|
||||
# Commit A: b_2
|
||||
# Commit B: c_1
|
||||
# Expected: c_2
|
||||
|
||||
test_expect_success '2a-setup: Modify(A)/rename(B)' '
|
||||
test_create_repo 2a &&
|
||||
(
|
||||
cd 2a &&
|
||||
|
||||
test_seq 1 10 >b &&
|
||||
git add b &&
|
||||
test_tick &&
|
||||
git commit -m "O" &&
|
||||
|
||||
git branch O &&
|
||||
git branch A &&
|
||||
git branch B &&
|
||||
|
||||
git checkout A &&
|
||||
test_seq 1 11 >b &&
|
||||
git add b &&
|
||||
test_tick &&
|
||||
git commit -m "A" &&
|
||||
|
||||
git checkout B &&
|
||||
git mv b c &&
|
||||
test_tick &&
|
||||
git commit -m "B"
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success '2a-check-L: Modify/rename, merge into modify side' '
|
||||
test_when_finished "git -C 2a reset --hard" &&
|
||||
test_when_finished "git -C 2a clean -fd" &&
|
||||
(
|
||||
cd 2a &&
|
||||
|
||||
git checkout A^0 &&
|
||||
|
||||
GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
|
||||
|
||||
test_i18ngrep ! "Skipped c" out &&
|
||||
test_must_be_empty err &&
|
||||
|
||||
git ls-files -s >index_files &&
|
||||
test_line_count = 1 index_files &&
|
||||
|
||||
git rev-parse >actual HEAD:c &&
|
||||
git rev-parse >expect A:b &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
git hash-object c >actual &&
|
||||
git rev-parse A:b >expect &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
test_must_fail git rev-parse HEAD:b &&
|
||||
test_path_is_missing b
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success '2a-check-R: Modify/rename, merge into rename side' '
|
||||
test_when_finished "git -C 2a reset --hard" &&
|
||||
test_when_finished "git -C 2a clean -fd" &&
|
||||
(
|
||||
cd 2a &&
|
||||
|
||||
git checkout B^0 &&
|
||||
|
||||
GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err &&
|
||||
|
||||
test_i18ngrep ! "Skipped c" out &&
|
||||
test_must_be_empty err &&
|
||||
|
||||
git ls-files -s >index_files &&
|
||||
test_line_count = 1 index_files &&
|
||||
|
||||
git rev-parse >actual HEAD:c &&
|
||||
git rev-parse >expect A:b &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
git hash-object c >actual &&
|
||||
git rev-parse A:b >expect &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
test_must_fail git rev-parse HEAD:b &&
|
||||
test_path_is_missing b
|
||||
)
|
||||
'
|
||||
|
||||
# Testcase 2b, Changed and renamed on A, subset of changes on B
|
||||
# Commit O: b_1
|
||||
# Commit A: c_2
|
||||
# Commit B: b_3
|
||||
# Expected: c_2
|
||||
|
||||
test_expect_success '2b-setup: Rename+Mod(A)/Mod(B), B mods subset of A' '
|
||||
test_create_repo 2b &&
|
||||
(
|
||||
cd 2b &&
|
||||
|
||||
test_write_lines 1 2 3 4 5 6 7 8 9 10 >b &&
|
||||
git add b &&
|
||||
test_tick &&
|
||||
git commit -m "O" &&
|
||||
|
||||
git branch O &&
|
||||
git branch A &&
|
||||
git branch B &&
|
||||
|
||||
git checkout A &&
|
||||
test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b &&
|
||||
git add b &&
|
||||
git mv b c &&
|
||||
test_tick &&
|
||||
git commit -m "A" &&
|
||||
|
||||
git checkout B &&
|
||||
test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b &&
|
||||
git add b &&
|
||||
test_tick &&
|
||||
git commit -m "B"
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success '2b-check-L: Rename+Mod(A)/Mod(B), B mods subset of A' '
|
||||
test_when_finished "git -C 2b reset --hard" &&
|
||||
test_when_finished "git -C 2b clean -fd" &&
|
||||
(
|
||||
cd 2b &&
|
||||
|
||||
git checkout A^0 &&
|
||||
|
||||
test-tool chmtime =31337 c &&
|
||||
test-tool chmtime -v +0 c >expected-mtime &&
|
||||
|
||||
GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
|
||||
|
||||
test_i18ngrep "Skipped c" out &&
|
||||
test_must_be_empty err &&
|
||||
|
||||
test-tool chmtime -v +0 c >actual-mtime &&
|
||||
test_cmp expected-mtime actual-mtime &&
|
||||
|
||||
git ls-files -s >index_files &&
|
||||
test_line_count = 1 index_files &&
|
||||
|
||||
git rev-parse >actual HEAD:c &&
|
||||
git rev-parse >expect A:c &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
git hash-object c >actual &&
|
||||
git rev-parse A:c >expect &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
test_must_fail git rev-parse HEAD:b &&
|
||||
test_path_is_missing b
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success '2b-check-R: Rename+Mod(A)/Mod(B), B mods subset of A' '
|
||||
test_when_finished "git -C 2b reset --hard" &&
|
||||
test_when_finished "git -C 2b clean -fd" &&
|
||||
(
|
||||
cd 2b &&
|
||||
|
||||
git checkout B^0 &&
|
||||
|
||||
GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err &&
|
||||
|
||||
test_i18ngrep "Auto-merging c" out &&
|
||||
test_must_be_empty err &&
|
||||
|
||||
git ls-files -s >index_files &&
|
||||
test_line_count = 1 index_files &&
|
||||
|
||||
git rev-parse >actual HEAD:c &&
|
||||
git rev-parse >expect A:c &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
git hash-object c >actual &&
|
||||
git rev-parse A:c >expect &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
test_must_fail git rev-parse HEAD:b &&
|
||||
test_path_is_missing b
|
||||
)
|
||||
'
|
||||
|
||||
# Testcase 2c, Changes on A, rename on B
|
||||
# Commit O: b_1
|
||||
# Commit A: b_2, c_3
|
||||
# Commit B: c_1
|
||||
# Expected: rename/add conflict c_2 vs c_3
|
||||
#
|
||||
# NOTE: Since A modified b_1->b_2, and B renamed b_1->c_1, the threeway
|
||||
# merge of those files should result in c_2. We then should have a
|
||||
# rename/add conflict between c_2 and c_3. However, if we note in
|
||||
# merge_content() that A had the right contents (b_2 has same
|
||||
# contents as c_2, just at a different name), and that A had the
|
||||
# right path present (c_3 existed) and thus decides that it can
|
||||
# skip the update, then we're in trouble. This test verifies we do
|
||||
# not make that particular mistake.
|
||||
|
||||
test_expect_success '2c-setup: Modify b & add c VS rename b->c' '
|
||||
test_create_repo 2c &&
|
||||
(
|
||||
cd 2c &&
|
||||
|
||||
test_seq 1 10 >b &&
|
||||
git add b &&
|
||||
test_tick &&
|
||||
git commit -m "O" &&
|
||||
|
||||
git branch O &&
|
||||
git branch A &&
|
||||
git branch B &&
|
||||
|
||||
git checkout A &&
|
||||
test_seq 1 11 >b &&
|
||||
echo whatever >c &&
|
||||
git add b c &&
|
||||
test_tick &&
|
||||
git commit -m "A" &&
|
||||
|
||||
git checkout B &&
|
||||
git mv b c &&
|
||||
test_tick &&
|
||||
git commit -m "B"
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success '2c-check: Modify b & add c VS rename b->c' '
|
||||
(
|
||||
cd 2c &&
|
||||
|
||||
git checkout A^0 &&
|
||||
|
||||
GIT_MERGE_VERBOSITY=3 test_must_fail git merge -s recursive B^0 >out 2>err &&
|
||||
|
||||
test_i18ngrep "CONFLICT (rename/add): Rename b->c" out &&
|
||||
test_i18ngrep ! "Skipped c" out &&
|
||||
test_must_be_empty err
|
||||
|
||||
# FIXME: rename/add conflicts are horribly broken right now;
|
||||
# when I get back to my patch series fixing it and
|
||||
# rename/rename(2to1) conflicts to bring them in line with
|
||||
# how add/add conflicts behave, then checks like the below
|
||||
# could be added. But that patch series is waiting until
|
||||
# the rename-directory-detection series lands, which this
|
||||
# is part of. And in the mean time, I do not want to further
|
||||
# enforce broken behavior. So for now, the main test is the
|
||||
# one above that err is an empty file.
|
||||
|
||||
#git ls-files -s >index_files &&
|
||||
#test_line_count = 2 index_files &&
|
||||
|
||||
#git rev-parse >actual :2:c :3:c &&
|
||||
#git rev-parse >expect A:b A:c &&
|
||||
#test_cmp expect actual &&
|
||||
|
||||
#git cat-file -p A:b >>merged &&
|
||||
#git cat-file -p A:c >>merge-me &&
|
||||
#>empty &&
|
||||
#test_must_fail git merge-file \
|
||||
# -L "Temporary merge branch 1" \
|
||||
# -L "" \
|
||||
# -L "Temporary merge branch 2" \
|
||||
# merged empty merge-me &&
|
||||
#sed -e "s/^\([<=>]\)/\1\1\1/" merged >merged-internal &&
|
||||
|
||||
#git hash-object c >actual &&
|
||||
#git hash-object merged-internal >expect &&
|
||||
#test_cmp expect actual &&
|
||||
|
||||
#test_path_is_missing b
|
||||
)
|
||||
'
|
||||
|
||||
|
||||
###########################################################################
|
||||
# SECTION 3: Cases involving directory renames
|
||||
#
|
||||
# NOTE:
|
||||
# Directory renames only apply when one side renames a directory, and the
|
||||
# other side adds or renames a path into that directory. Applying the
|
||||
# directory rename to that new path creates a new pathname that didn't
|
||||
# exist on either side of history. Thus, it is impossible for the
|
||||
# merge contents to already be at the right path, so all of these checks
|
||||
# exist just to make sure that updates are not skipped.
|
||||
###########################################################################
|
||||
|
||||
# Testcase 3a, Change + rename into dir foo on A, dir rename foo->bar on B
|
||||
# Commit O: bq_1, foo/whatever
|
||||
# Commit A: foo/{bq_2, whatever}
|
||||
# Commit B: bq_1, bar/whatever
|
||||
# Expected: bar/{bq_2, whatever}
|
||||
|
||||
test_expect_success '3a-setup: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
|
||||
test_create_repo 3a &&
|
||||
(
|
||||
cd 3a &&
|
||||
|
||||
mkdir foo &&
|
||||
test_seq 1 10 >bq &&
|
||||
test_write_lines a b c d e f g h i j k >foo/whatever &&
|
||||
git add bq foo/whatever &&
|
||||
test_tick &&
|
||||
git commit -m "O" &&
|
||||
|
||||
git branch O &&
|
||||
git branch A &&
|
||||
git branch B &&
|
||||
|
||||
git checkout A &&
|
||||
test_seq 1 11 >bq &&
|
||||
git add bq &&
|
||||
git mv bq foo/ &&
|
||||
test_tick &&
|
||||
git commit -m "A" &&
|
||||
|
||||
git checkout B &&
|
||||
git mv foo/ bar/ &&
|
||||
test_tick &&
|
||||
git commit -m "B"
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success '3a-check-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
|
||||
test_when_finished "git -C 3a reset --hard" &&
|
||||
test_when_finished "git -C 3a clean -fd" &&
|
||||
(
|
||||
cd 3a &&
|
||||
|
||||
git checkout A^0 &&
|
||||
|
||||
GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
|
||||
|
||||
test_i18ngrep ! "Skipped bar/bq" out &&
|
||||
test_must_be_empty err &&
|
||||
|
||||
git ls-files -s >index_files &&
|
||||
test_line_count = 2 index_files &&
|
||||
|
||||
git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever &&
|
||||
git rev-parse >expect A:foo/bq A:foo/whatever &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
git hash-object bar/bq bar/whatever >actual &&
|
||||
git rev-parse A:foo/bq A:foo/whatever >expect &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
test_must_fail git rev-parse HEAD:bq HEAD:foo/bq &&
|
||||
test_path_is_missing bq foo/bq foo/whatever
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success '3a-check-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
|
||||
test_when_finished "git -C 3a reset --hard" &&
|
||||
test_when_finished "git -C 3a clean -fd" &&
|
||||
(
|
||||
cd 3a &&
|
||||
|
||||
git checkout B^0 &&
|
||||
|
||||
GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err &&
|
||||
|
||||
test_i18ngrep ! "Skipped bar/bq" out &&
|
||||
test_must_be_empty err &&
|
||||
|
||||
git ls-files -s >index_files &&
|
||||
test_line_count = 2 index_files &&
|
||||
|
||||
git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever &&
|
||||
git rev-parse >expect A:foo/bq A:foo/whatever &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
git hash-object bar/bq bar/whatever >actual &&
|
||||
git rev-parse A:foo/bq A:foo/whatever >expect &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
test_must_fail git rev-parse HEAD:bq HEAD:foo/bq &&
|
||||
test_path_is_missing bq foo/bq foo/whatever
|
||||
)
|
||||
'
|
||||
|
||||
# Testcase 3b, rename into dir foo on A, dir rename foo->bar + change on B
|
||||
# Commit O: bq_1, foo/whatever
|
||||
# Commit A: foo/{bq_1, whatever}
|
||||
# Commit B: bq_2, bar/whatever
|
||||
# Expected: bar/{bq_2, whatever}
|
||||
|
||||
test_expect_success '3b-setup: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
|
||||
test_create_repo 3b &&
|
||||
(
|
||||
cd 3b &&
|
||||
|
||||
mkdir foo &&
|
||||
test_seq 1 10 >bq &&
|
||||
test_write_lines a b c d e f g h i j k >foo/whatever &&
|
||||
git add bq foo/whatever &&
|
||||
test_tick &&
|
||||
git commit -m "O" &&
|
||||
|
||||
git branch O &&
|
||||
git branch A &&
|
||||
git branch B &&
|
||||
|
||||
git checkout A &&
|
||||
git mv bq foo/ &&
|
||||
test_tick &&
|
||||
git commit -m "A" &&
|
||||
|
||||
git checkout B &&
|
||||
test_seq 1 11 >bq &&
|
||||
git add bq &&
|
||||
git mv foo/ bar/ &&
|
||||
test_tick &&
|
||||
git commit -m "B"
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success '3b-check-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
|
||||
test_when_finished "git -C 3b reset --hard" &&
|
||||
test_when_finished "git -C 3b clean -fd" &&
|
||||
(
|
||||
cd 3b &&
|
||||
|
||||
git checkout A^0 &&
|
||||
|
||||
GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
|
||||
|
||||
test_i18ngrep ! "Skipped bar/bq" out &&
|
||||
test_must_be_empty err &&
|
||||
|
||||
git ls-files -s >index_files &&
|
||||
test_line_count = 2 index_files &&
|
||||
|
||||
git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever &&
|
||||
git rev-parse >expect B:bq A:foo/whatever &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
git hash-object bar/bq bar/whatever >actual &&
|
||||
git rev-parse B:bq A:foo/whatever >expect &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
test_must_fail git rev-parse HEAD:bq HEAD:foo/bq &&
|
||||
test_path_is_missing bq foo/bq foo/whatever
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success '3b-check-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
|
||||
test_when_finished "git -C 3b reset --hard" &&
|
||||
test_when_finished "git -C 3b clean -fd" &&
|
||||
(
|
||||
cd 3b &&
|
||||
|
||||
git checkout B^0 &&
|
||||
|
||||
GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err &&
|
||||
|
||||
test_i18ngrep ! "Skipped bar/bq" out &&
|
||||
test_must_be_empty err &&
|
||||
|
||||
git ls-files -s >index_files &&
|
||||
test_line_count = 2 index_files &&
|
||||
|
||||
git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever &&
|
||||
git rev-parse >expect B:bq A:foo/whatever &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
git hash-object bar/bq bar/whatever >actual &&
|
||||
git rev-parse B:bq A:foo/whatever >expect &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
test_must_fail git rev-parse HEAD:bq HEAD:foo/bq &&
|
||||
test_path_is_missing bq foo/bq foo/whatever
|
||||
)
|
||||
'
|
||||
|
||||
###########################################################################
|
||||
# SECTION 4: Cases involving dirty changes
|
||||
###########################################################################
|
||||
|
||||
# Testcase 4a, Changed on A, subset of changes on B, locally modified
|
||||
# Commit O: b_1
|
||||
# Commit A: b_2
|
||||
# Commit B: b_3
|
||||
# Working copy: b_4
|
||||
# Expected: b_2 for merge, b_4 in working copy
|
||||
|
||||
test_expect_success '4a-setup: Change on A, change on B subset of A, dirty mods present' '
|
||||
test_create_repo 4a &&
|
||||
(
|
||||
cd 4a &&
|
||||
|
||||
test_write_lines 1 2 3 4 5 6 7 8 9 10 >b &&
|
||||
git add b &&
|
||||
test_tick &&
|
||||
git commit -m "O" &&
|
||||
|
||||
git branch O &&
|
||||
git branch A &&
|
||||
git branch B &&
|
||||
|
||||
git checkout A &&
|
||||
test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b &&
|
||||
git add b &&
|
||||
test_tick &&
|
||||
git commit -m "A" &&
|
||||
|
||||
git checkout B &&
|
||||
test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b &&
|
||||
git add b &&
|
||||
test_tick &&
|
||||
git commit -m "B"
|
||||
)
|
||||
'
|
||||
|
||||
# NOTE: For as long as we continue using unpack_trees() without index_only
|
||||
# set to true, it will error out on a case like this claiming the the locally
|
||||
# modified file would be overwritten by the merge. Getting this testcase
|
||||
# correct requires doing the merge in-memory first, then realizing that no
|
||||
# updates to the file are necessary, and thus that we can just leave the path
|
||||
# alone.
|
||||
test_expect_failure '4a-check: Change on A, change on B subset of A, dirty mods present' '
|
||||
test_when_finished "git -C 4a reset --hard" &&
|
||||
test_when_finished "git -C 4a clean -fd" &&
|
||||
(
|
||||
cd 4a &&
|
||||
|
||||
git checkout A^0 &&
|
||||
echo "File rewritten" >b &&
|
||||
|
||||
test-tool chmtime =31337 b &&
|
||||
test-tool chmtime -v +0 b >expected-mtime &&
|
||||
|
||||
GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
|
||||
|
||||
test_i18ngrep "Skipped b" out &&
|
||||
test_must_be_empty err &&
|
||||
|
||||
test-tool chmtime -v +0 b >actual-mtime &&
|
||||
test_cmp expected-mtime actual-mtime &&
|
||||
|
||||
git ls-files -s >index_files &&
|
||||
test_line_count = 1 index_files &&
|
||||
|
||||
git rev-parse >actual :0:b &&
|
||||
git rev-parse >expect A:b &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
git hash-object b >actual &&
|
||||
echo "File rewritten" | git hash-object --stdin >expect &&
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
||||
# Testcase 4b, Changed+renamed on A, subset of changes on B, locally modified
|
||||
# Commit O: b_1
|
||||
# Commit A: c_2
|
||||
# Commit B: b_3
|
||||
# Working copy: c_4
|
||||
# Expected: c_2
|
||||
|
||||
test_expect_success '4b-setup: Rename+Mod(A)/Mod(B), change on B subset of A, dirty mods present' '
|
||||
test_create_repo 4b &&
|
||||
(
|
||||
cd 4b &&
|
||||
|
||||
test_write_lines 1 2 3 4 5 6 7 8 9 10 >b &&
|
||||
git add b &&
|
||||
test_tick &&
|
||||
git commit -m "O" &&
|
||||
|
||||
git branch O &&
|
||||
git branch A &&
|
||||
git branch B &&
|
||||
|
||||
git checkout A &&
|
||||
test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b &&
|
||||
git add b &&
|
||||
git mv b c &&
|
||||
test_tick &&
|
||||
git commit -m "A" &&
|
||||
|
||||
git checkout B &&
|
||||
test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b &&
|
||||
git add b &&
|
||||
test_tick &&
|
||||
git commit -m "B"
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success '4b-check: Rename+Mod(A)/Mod(B), change on B subset of A, dirty mods present' '
|
||||
test_when_finished "git -C 4b reset --hard" &&
|
||||
test_when_finished "git -C 4b clean -fd" &&
|
||||
(
|
||||
cd 4b &&
|
||||
|
||||
git checkout A^0 &&
|
||||
echo "File rewritten" >c &&
|
||||
|
||||
test-tool chmtime =31337 c &&
|
||||
test-tool chmtime -v +0 c >expected-mtime &&
|
||||
|
||||
GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
|
||||
|
||||
test_i18ngrep "Skipped c" out &&
|
||||
test_must_be_empty err &&
|
||||
|
||||
test-tool chmtime -v +0 c >actual-mtime &&
|
||||
test_cmp expected-mtime actual-mtime &&
|
||||
|
||||
git ls-files -s >index_files &&
|
||||
test_line_count = 1 index_files &&
|
||||
|
||||
git rev-parse >actual :0:c &&
|
||||
git rev-parse >expect A:c &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
git hash-object c >actual &&
|
||||
echo "File rewritten" | git hash-object --stdin >expect &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
test_must_fail git rev-parse HEAD:b &&
|
||||
test_path_is_missing b
|
||||
)
|
||||
'
|
||||
|
||||
test_done
|
|
@ -92,7 +92,7 @@ test_expect_success 'will not overwrite removed file with staged changes' '
|
|||
test_cmp important c1.c
|
||||
'
|
||||
|
||||
test_expect_failure 'will not overwrite unstaged changes in renamed file' '
|
||||
test_expect_success 'will not overwrite unstaged changes in renamed file' '
|
||||
git reset --hard c1 &&
|
||||
git mv c1.c other.c &&
|
||||
git commit -m rename &&
|
||||
|
|
|
@ -1509,7 +1509,7 @@ static int verify_uptodate_1(const struct cache_entry *ce,
|
|||
add_rejected_path(o, error_type, ce->name);
|
||||
}
|
||||
|
||||
static int verify_uptodate(const struct cache_entry *ce,
|
||||
int verify_uptodate(const struct cache_entry *ce,
|
||||
struct unpack_trees_options *o)
|
||||
{
|
||||
if (!o->skip_sparse_checkout && (ce->ce_flags & CE_NEW_SKIP_WORKTREE))
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#ifndef UNPACK_TREES_H
|
||||
#define UNPACK_TREES_H
|
||||
|
||||
#include "tree-walk.h"
|
||||
#include "string-list.h"
|
||||
|
||||
#define MAX_UNPACK_TREES 8
|
||||
|
@ -78,6 +79,9 @@ struct unpack_trees_options {
|
|||
extern int unpack_trees(unsigned n, struct tree_desc *t,
|
||||
struct unpack_trees_options *options);
|
||||
|
||||
int verify_uptodate(const struct cache_entry *ce,
|
||||
struct unpack_trees_options *o);
|
||||
|
||||
int threeway_merge(const struct cache_entry * const *stages,
|
||||
struct unpack_trees_options *o);
|
||||
int twoway_merge(const struct cache_entry * const *src,
|
||||
|
|
Загрузка…
Ссылка в новой задаче