mirror of
https://github.com/git/git.git
synced 2024-11-27 20:14:30 +08:00
[PATCH] read-tree --emu23.
This new flag causes two-way fast forward to internally use the three-way merge mechanism. This behaviour is intended to offer a better fast forward semantics when used in a dirty work tree. The new test t1005 is parallel to the existing t1001 "pure 2-way" tests, but some parts that are commented out would fail. These failures are due to three-way merge enforcing too strict index requirements for cases that could succeed. This problem will be addressed by later patches. Without even changing three-way mechanism, the --emu23 two-way fast forward already gives the user an easier-to-handle merge result when a file that "merged head" updates has local modifications. This is demonstrated as "case 16" test in t1005. Signed-off-by: Junio C Hamano <junkio@cox.net> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
This commit is contained in:
parent
76bc82ca0e
commit
03efa6d9a4
81
read-tree.c
81
read-tree.c
@ -209,6 +209,58 @@ static int twoway_merge(struct cache_entry **src, struct cache_entry **dst)
|
||||
return deleted_entry(oldtree, current, dst);
|
||||
}
|
||||
|
||||
/*
|
||||
* Two-way merge emulated with three-way merge.
|
||||
*
|
||||
* This treats "read-tree -m H M" by transforming it internally
|
||||
* into "read-tree -m H I+H M", where I+H is a tree that would
|
||||
* contain the contents of the current index file, overlayed on
|
||||
* top of H. Unlike the traditional two-way merge, this leaves
|
||||
* the stages in the resulting index file and lets the user resolve
|
||||
* the merge conflicts using standard tools for three-way merge.
|
||||
*
|
||||
* This function is just to set-up such an arrangement, and the
|
||||
* actual merge uses threeway_merge() function.
|
||||
*/
|
||||
static void setup_emu23(void)
|
||||
{
|
||||
/* stage0 contains I, stage1 H, stage2 M.
|
||||
* move stage2 to stage3, and create stage2 entries
|
||||
* by scanning stage0 and stage1 entries.
|
||||
*/
|
||||
int i, namelen, size;
|
||||
struct cache_entry *ce, *stage2;
|
||||
|
||||
for (i = 0; i < active_nr; i++) {
|
||||
ce = active_cache[i];
|
||||
if (ce_stage(ce) != 2)
|
||||
continue;
|
||||
/* hoist them up to stage 3 */
|
||||
namelen = ce_namelen(ce);
|
||||
ce->ce_flags = create_ce_flags(namelen, 3);
|
||||
}
|
||||
|
||||
for (i = 0; i < active_nr; i++) {
|
||||
ce = active_cache[i];
|
||||
if (ce_stage(ce) > 1)
|
||||
continue;
|
||||
namelen = ce_namelen(ce);
|
||||
size = cache_entry_size(namelen);
|
||||
stage2 = xmalloc(size);
|
||||
memcpy(stage2, ce, size);
|
||||
stage2->ce_flags = create_ce_flags(namelen, 2);
|
||||
if (add_cache_entry(stage2, ADD_CACHE_OK_TO_ADD) < 0)
|
||||
die("cannot merge index and our head tree");
|
||||
|
||||
/* We are done with this name, so skip to next name */
|
||||
while (i < active_nr &&
|
||||
ce_namelen(active_cache[i]) == namelen &&
|
||||
!memcmp(active_cache[i]->name, ce->name, namelen))
|
||||
i++;
|
||||
i--; /* compensate for the loop control */
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* One-way merge.
|
||||
*
|
||||
@ -315,7 +367,7 @@ static struct cache_file cache_file;
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int i, newfd, merge, reset;
|
||||
int i, newfd, merge, reset, emu23;
|
||||
unsigned char sha1[20];
|
||||
|
||||
newfd = hold_index_file_for_update(&cache_file, get_index_file());
|
||||
@ -324,6 +376,7 @@ int main(int argc, char **argv)
|
||||
|
||||
merge = 0;
|
||||
reset = 0;
|
||||
emu23 = 0;
|
||||
for (i = 1; i < argc; i++) {
|
||||
const char *arg = argv[i];
|
||||
|
||||
@ -335,7 +388,7 @@ int main(int argc, char **argv)
|
||||
|
||||
/* This differs from "-m" in that we'll silently ignore unmerged entries */
|
||||
if (!strcmp(arg, "--reset")) {
|
||||
if (stage || merge)
|
||||
if (stage || merge || emu23)
|
||||
usage(read_tree_usage);
|
||||
reset = 1;
|
||||
merge = 1;
|
||||
@ -345,7 +398,7 @@ int main(int argc, char **argv)
|
||||
|
||||
/* "-m" stands for "merge", meaning we start in stage 1 */
|
||||
if (!strcmp(arg, "-m")) {
|
||||
if (stage || merge)
|
||||
if (stage || merge || emu23)
|
||||
usage(read_tree_usage);
|
||||
if (read_cache_unmerged())
|
||||
die("you need to resolve your current index first");
|
||||
@ -353,6 +406,17 @@ int main(int argc, char **argv)
|
||||
merge = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* "-emu23" uses 3-way merge logic to perform fast-forward */
|
||||
if (!strcmp(arg, "--emu23")) {
|
||||
if (stage || merge || emu23)
|
||||
usage(read_tree_usage);
|
||||
if (read_cache_unmerged())
|
||||
die("you need to resolve your current index first");
|
||||
merge = emu23 = stage = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (get_sha1(arg, sha1) < 0)
|
||||
usage(read_tree_usage);
|
||||
if (stage > 3)
|
||||
@ -369,9 +433,18 @@ int main(int argc, char **argv)
|
||||
[2] = twoway_merge,
|
||||
[3] = threeway_merge,
|
||||
};
|
||||
merge_fn_t fn;
|
||||
|
||||
if (stage < 2 || stage > 4)
|
||||
die("just how do you expect me to merge %d trees?", stage-1);
|
||||
merge_cache(active_cache, active_nr, merge_function[stage-1]);
|
||||
if (emu23 && stage != 3)
|
||||
die("--emu23 takes only two trees");
|
||||
fn = merge_function[stage-1];
|
||||
if (stage == 3 && emu23) {
|
||||
setup_emu23();
|
||||
fn = merge_function[3];
|
||||
}
|
||||
merge_cache(active_cache, active_nr, fn);
|
||||
}
|
||||
if (write_cache(newfd, active_cache, active_nr) ||
|
||||
commit_index_file(&cache_file))
|
||||
|
316
t/t1005-read-tree-m-2way-emu23.sh
Normal file
316
t/t1005-read-tree-m-2way-emu23.sh
Normal file
@ -0,0 +1,316 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Copyright (c) 2005 Junio C Hamano
|
||||
#
|
||||
|
||||
test_description='Two way merge with read-tree --emu23 $H $M
|
||||
|
||||
This test tries two-way merge (aka fast forward with carry forward).
|
||||
|
||||
There is the head (called H) and another commit (called M), which is
|
||||
simply ahead of H. The index and the work tree contains a state that
|
||||
is derived from H, but may also have local changes. This test checks
|
||||
all the combinations described in the two-tree merge "carry forward"
|
||||
rules, found in <Documentation/git-rev-tree.txt>.
|
||||
|
||||
In the test, these paths are used:
|
||||
bozbar - in H, stays in M, modified from bozbar to gnusto
|
||||
frotz - not in H added in M
|
||||
nitfol - in H, stays in M unmodified
|
||||
rezrov - in H, deleted in M
|
||||
yomin - not in H nor M
|
||||
'
|
||||
. ./test-lib.sh
|
||||
|
||||
read_tree_twoway () {
|
||||
git-read-tree --emu23 "$1" "$2" &&
|
||||
git-ls-files --stage &&
|
||||
git-merge-cache git-merge-one-file-script -a &&
|
||||
git-ls-files --stage
|
||||
}
|
||||
|
||||
_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
|
||||
_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
|
||||
compare_change () {
|
||||
cat current
|
||||
sed -n >current \
|
||||
-e '/^--- /d; /^+++ /d; /^@@ /d;' \
|
||||
-e 's/^\([-+][0-7][0-7][0-7][0-7][0-7][0-7]\) '"$_x40"' /\1 X /p' \
|
||||
"$1"
|
||||
diff -u expected current
|
||||
}
|
||||
|
||||
check_cache_at () {
|
||||
clean_if_empty=`git-diff-files "$1"`
|
||||
case "$clean_if_empty" in
|
||||
'') echo "$1: clean" ;;
|
||||
?*) echo "$1: dirty" ;;
|
||||
esac
|
||||
case "$2,$clean_if_empty" in
|
||||
clean,) : ;;
|
||||
clean,?*) false ;;
|
||||
dirty,) false ;;
|
||||
dirty,?*) : ;;
|
||||
esac
|
||||
}
|
||||
|
||||
check_stages () {
|
||||
cat >expected_stages
|
||||
git-ls-files --stage | sed -e "s/ $_x40 / X /" >current_stages
|
||||
diff -u expected_stages current_stages
|
||||
}
|
||||
|
||||
test_expect_success \
|
||||
setup \
|
||||
'echo frotz >frotz &&
|
||||
echo nitfol >nitfol &&
|
||||
echo bozbar >bozbar &&
|
||||
echo rezrov >rezrov &&
|
||||
echo yomin >yomin &&
|
||||
git-update-cache --add nitfol bozbar rezrov &&
|
||||
treeH=`git-write-tree` &&
|
||||
echo treeH $treeH &&
|
||||
git-ls-tree $treeH &&
|
||||
|
||||
echo gnusto >bozbar &&
|
||||
git-update-cache --add frotz bozbar --force-remove rezrov &&
|
||||
git-ls-files --stage >M.out &&
|
||||
treeM=`git-write-tree` &&
|
||||
echo treeM $treeM &&
|
||||
git-ls-tree $treeM &&
|
||||
git-diff-tree $treeH $treeM'
|
||||
|
||||
test_expect_success \
|
||||
'1, 2, 3 - no carry forward' \
|
||||
'rm -f .git/index &&
|
||||
read_tree_twoway $treeH $treeM &&
|
||||
git-ls-files --stage >1-3.out &&
|
||||
diff -u M.out 1-3.out &&
|
||||
check_cache_at bozbar dirty &&
|
||||
check_cache_at frotz clean && # different from pure 2-way
|
||||
check_cache_at nitfol dirty'
|
||||
|
||||
echo '+100644 X 0 yomin' >expected
|
||||
|
||||
test_expect_success \
|
||||
'4 - carry forward local addition.' \
|
||||
'rm -f .git/index &&
|
||||
git-update-cache --add yomin &&
|
||||
read_tree_twoway $treeH $treeM &&
|
||||
git-ls-files --stage >4.out || exit
|
||||
diff -u M.out 4.out >4diff.out
|
||||
compare_change 4diff.out expected &&
|
||||
check_cache_at yomin clean'
|
||||
|
||||
# "read-tree -m H I+H M" where !H && !M; so (I+H) not being up-to-date
|
||||
# should not matter, but without #3ALT this does not work.
|
||||
: test_expect_success \
|
||||
'5 - carry forward local addition.' \
|
||||
'rm -f .git/index &&
|
||||
echo yomin >yomin &&
|
||||
git-update-cache --add yomin &&
|
||||
echo yomin yomin >yomin &&
|
||||
read_tree_twoway $treeH $treeM &&
|
||||
git-ls-files --stage >5.out || exit
|
||||
diff -u M.out 5.out >5diff.out
|
||||
compare_change 5diff.out expected &&
|
||||
check_cache_at yomin dirty'
|
||||
|
||||
# "read-tree -m H I+H M" where !H && M && (I+H) == M, so this should
|
||||
# succeed (even the entry is clean), but without #5ALT this does not
|
||||
# work.
|
||||
: test_expect_success \
|
||||
'6 - local addition already has the same.' \
|
||||
'rm -f .git/index &&
|
||||
git-update-cache --add frotz &&
|
||||
read_tree_twoway $treeH $treeM &&
|
||||
git-ls-files --stage >6.out &&
|
||||
diff -u M.out 6.out &&
|
||||
check_cache_at frotz clean'
|
||||
|
||||
# Exactly the same pattern as above but with dirty cache. This also
|
||||
# should succeed, but without #5ALT it does not.
|
||||
: test_expect_success \
|
||||
'7 - local addition already has the same.' \
|
||||
'rm -f .git/index &&
|
||||
echo frotz >frotz &&
|
||||
git-update-cache --add frotz &&
|
||||
echo frotz frotz >frotz &&
|
||||
read_tree_twoway $treeH $treeM &&
|
||||
git-ls-files --stage >7.out &&
|
||||
diff -u M.out 7.out &&
|
||||
check_cache_at frotz dirty'
|
||||
|
||||
test_expect_success \
|
||||
'8 - conflicting addition.' \
|
||||
'rm -f .git/index &&
|
||||
echo frotz frotz >frotz &&
|
||||
git-update-cache --add frotz &&
|
||||
if read_tree_twoway $treeH $treeM; then false; else :; fi'
|
||||
|
||||
test_expect_success \
|
||||
'9 - conflicting addition.' \
|
||||
'rm -f .git/index &&
|
||||
echo frotz frotz >frotz &&
|
||||
git-update-cache --add frotz &&
|
||||
echo frotz >frotz &&
|
||||
if read_tree_twoway $treeH $treeM; then false; else :; fi'
|
||||
|
||||
test_expect_success \
|
||||
'10 - path removed.' \
|
||||
'rm -f .git/index &&
|
||||
echo rezrov >rezrov &&
|
||||
git-update-cache --add rezrov &&
|
||||
read_tree_twoway $treeH $treeM &&
|
||||
git-ls-files --stage >10.out &&
|
||||
diff -u M.out 10.out'
|
||||
|
||||
test_expect_success \
|
||||
'11 - dirty path removed.' \
|
||||
'rm -f .git/index &&
|
||||
echo rezrov >rezrov &&
|
||||
git-update-cache --add rezrov &&
|
||||
echo rezrov rezrov >rezrov &&
|
||||
if read_tree_twoway $treeH $treeM; then false; else :; fi'
|
||||
|
||||
test_expect_success \
|
||||
'12 - unmatching local changes being removed.' \
|
||||
'rm -f .git/index &&
|
||||
echo rezrov rezrov >rezrov &&
|
||||
git-update-cache --add rezrov &&
|
||||
if read_tree_twoway $treeH $treeM; then false; else :; fi'
|
||||
|
||||
test_expect_success \
|
||||
'13 - unmatching local changes being removed.' \
|
||||
'rm -f .git/index &&
|
||||
echo rezrov rezrov >rezrov &&
|
||||
git-update-cache --add rezrov &&
|
||||
echo rezrov >rezrov &&
|
||||
if read_tree_twoway $treeH $treeM; then false; else :; fi'
|
||||
|
||||
cat >expected <<EOF
|
||||
-100644 X 0 nitfol
|
||||
+100644 X 0 nitfol
|
||||
EOF
|
||||
|
||||
test_expect_success \
|
||||
'14 - unchanged in two heads.' \
|
||||
'rm -f .git/index &&
|
||||
echo nitfol nitfol >nitfol &&
|
||||
git-update-cache --add nitfol &&
|
||||
read_tree_twoway $treeH $treeM &&
|
||||
git-ls-files --stage >14.out || exit
|
||||
diff -u M.out 14.out >14diff.out
|
||||
compare_change 14diff.out expected &&
|
||||
check_cache_at nitfol clean'
|
||||
|
||||
test_expect_success \
|
||||
'15 - unchanged in two heads.' \
|
||||
'rm -f .git/index &&
|
||||
echo nitfol nitfol >nitfol &&
|
||||
git-update-cache --add nitfol &&
|
||||
echo nitfol nitfol nitfol >nitfol &&
|
||||
read_tree_twoway $treeH $treeM &&
|
||||
git-ls-files --stage >15.out || exit
|
||||
diff -u M.out 15.out >15diff.out
|
||||
compare_change 15diff.out expected &&
|
||||
check_cache_at nitfol dirty'
|
||||
|
||||
# This is different from straight 2-way merge in that it leaves
|
||||
# three stages of bozbar in the index file without failing, so
|
||||
# the user can run git-diff-stages to examine the situation.
|
||||
test_expect_success \
|
||||
'16 - conflicting local change.' \
|
||||
'rm -f .git/index &&
|
||||
echo bozbar bozbar >bozbar &&
|
||||
git-update-cache --add bozbar &&
|
||||
git-read-tree --emu23 $treeH $treeM &&
|
||||
check_stages' <<\EOF
|
||||
100644 X 1 bozbar
|
||||
100644 X 2 bozbar
|
||||
100644 X 3 bozbar
|
||||
100644 X 3 frotz
|
||||
100644 X 0 nitfol
|
||||
100644 X 1 rezrov
|
||||
100644 X 2 rezrov
|
||||
EOF
|
||||
|
||||
test_expect_success \
|
||||
'17 - conflicting local change.' \
|
||||
'rm -f .git/index &&
|
||||
echo bozbar bozbar >bozbar &&
|
||||
git-update-cache --add bozbar &&
|
||||
echo bozbar bozbar bozbar >bozbar &&
|
||||
if read_tree_twoway $treeH $treeM; then false; else :; fi'
|
||||
|
||||
test_expect_success \
|
||||
'18 - local change already having a good result.' \
|
||||
'rm -f .git/index &&
|
||||
echo gnusto >bozbar &&
|
||||
git-update-cache --add bozbar &&
|
||||
read_tree_twoway $treeH $treeM &&
|
||||
git-ls-files --stage >18.out &&
|
||||
diff -u M.out 18.out &&
|
||||
check_cache_at bozbar clean'
|
||||
|
||||
test_expect_success \
|
||||
'19 - local change already having a good result, further modified.' \
|
||||
'rm -f .git/index &&
|
||||
echo gnusto >bozbar &&
|
||||
git-update-cache --add bozbar &&
|
||||
echo gnusto gnusto >bozbar &&
|
||||
read_tree_twoway $treeH $treeM &&
|
||||
git-ls-files --stage >19.out &&
|
||||
diff -u M.out 19.out &&
|
||||
check_cache_at bozbar dirty'
|
||||
|
||||
test_expect_success \
|
||||
'20 - no local change, use new tree.' \
|
||||
'rm -f .git/index &&
|
||||
echo bozbar >bozbar &&
|
||||
git-update-cache --add bozbar &&
|
||||
read_tree_twoway $treeH $treeM &&
|
||||
git-ls-files --stage >20.out &&
|
||||
diff -u M.out 20.out &&
|
||||
check_cache_at bozbar dirty'
|
||||
|
||||
test_expect_success \
|
||||
'21 - no local change, dirty cache.' \
|
||||
'rm -f .git/index &&
|
||||
echo bozbar >bozbar &&
|
||||
git-update-cache --add bozbar &&
|
||||
echo gnusto gnusto >bozbar &&
|
||||
if read_tree_twoway $treeH $treeM; then false; else :; fi'
|
||||
|
||||
# Also make sure we did not break DF vs DF/DF case.
|
||||
test_expect_success \
|
||||
'DF vs DF/DF case setup.' \
|
||||
'rm -f .git/index &&
|
||||
echo DF >DF &&
|
||||
git-update-cache --add DF &&
|
||||
treeDF=`git-write-tree` &&
|
||||
echo treeDF $treeDF &&
|
||||
git-ls-tree $treeDF &&
|
||||
|
||||
rm -f DF &&
|
||||
mkdir DF &&
|
||||
echo DF/DF >DF/DF &&
|
||||
git-update-cache --add --remove DF DF/DF &&
|
||||
treeDFDF=`git-write-tree` &&
|
||||
echo treeDFDF $treeDFDF &&
|
||||
git-ls-tree $treeDFDF &&
|
||||
git-ls-files --stage >DFDF.out'
|
||||
|
||||
test_expect_success \
|
||||
'DF vs DF/DF case test.' \
|
||||
'rm -f .git/index &&
|
||||
rm -fr DF &&
|
||||
echo DF >DF &&
|
||||
git-update-cache --add DF &&
|
||||
read_tree_twoway $treeDF $treeDFDF &&
|
||||
git-ls-files --stage >DFDFcheck.out &&
|
||||
diff -u DFDF.out DFDFcheck.out &&
|
||||
check_cache_at DF/DF clean && # different from pure 2-way
|
||||
:'
|
||||
|
||||
test_done
|
Loading…
Reference in New Issue
Block a user