Browse Source
"git rebase" behaved slightly differently depending on which one of the three backends gets used; this has been documented and an effort to make them more uniform has begun. * en/rebase-consistency: git-rebase: make --allow-empty-message the default t3401: add directory rename testcases for rebase and am git-rebase.txt: document behavioral differences between modes directory-rename-detection.txt: technical docs on abilities and limitations git-rebase.txt: address confusion between --no-ff vs --force-rebase git-rebase: error out when incompatible options passed t3422: new testcases for checking when incompatible options passed git-rebase.sh: update help messages a bit git-rebase.txt: document incompatible optionsmaint
![gitster@pobox.com](/assets/img/avatar_default.png)
7 changed files with 462 additions and 42 deletions
@ -0,0 +1,115 @@
@@ -0,0 +1,115 @@
|
||||
Directory rename detection |
||||
========================== |
||||
|
||||
Rename detection logic in diffcore-rename that checks for renames of |
||||
individual files is aggregated and analyzed in merge-recursive for cases |
||||
where combinations of renames indicate that a full directory has been |
||||
renamed. |
||||
|
||||
Scope of abilities |
||||
------------------ |
||||
|
||||
It is perhaps easiest to start with an example: |
||||
|
||||
* 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'. |
||||
|
||||
More interesting possibilities exist, though, such as: |
||||
|
||||
* one side of history renames x -> z, and the other renames some file to |
||||
x/e, causing the need for the merge to do a transitive rename. |
||||
|
||||
* one side of history renames x -> z, but also renames all files within |
||||
x. For example, x/a -> z/alpha, x/b -> z/bravo, etc. |
||||
|
||||
* both 'x' and 'y' being merged into a single directory 'z', with a |
||||
directory rename being detected for both x->z and y->z. |
||||
|
||||
* not all files in a directory being renamed to the same location; |
||||
i.e. perhaps most the files in 'x' are now found under 'z', but a few |
||||
are found under 'w'. |
||||
|
||||
* a directory being renamed, which also contained a subdirectory that was |
||||
renamed to some entirely different location. (And perhaps the inner |
||||
directory itself contained inner directories that were renamed to yet |
||||
other locations). |
||||
|
||||
* combinations of the above; see t/t6043-merge-rename-directories.sh for |
||||
various interesting cases. |
||||
|
||||
Limitations -- applicability of directory renames |
||||
------------------------------------------------- |
||||
|
||||
In order to prevent edge and corner cases resulting in either conflicts |
||||
that cannot be represented in the index or which might be too complex for |
||||
users to try to understand and resolve, a couple basic rules limit when |
||||
directory rename detection applies: |
||||
|
||||
1) If a given directory still exists on both sides of a merge, we do |
||||
not consider it to have been renamed. |
||||
|
||||
2) If a subset of to-be-renamed files have a file or directory in the |
||||
way (or would be in the way of each other), "turn off" the directory |
||||
rename for those specific sub-paths and report the conflict to the |
||||
user. |
||||
|
||||
3) If the other side of history did a directory rename to a path that |
||||
your side of history renamed away, then ignore that particular |
||||
rename from the other side of history for any implicit directory |
||||
renames (but warn the user). |
||||
|
||||
Limitations -- detailed rules and testcases |
||||
------------------------------------------- |
||||
|
||||
t/t6043-merge-rename-directories.sh contains extensive tests and commentary |
||||
which generate and explore the rules listed above. It also lists a few |
||||
additional rules: |
||||
|
||||
a) If renames split a directory into two or more others, the directory |
||||
with the most renames, "wins". |
||||
|
||||
b) Avoid directory-rename-detection for a path, if that path is the |
||||
source of a rename on either side of a merge. |
||||
|
||||
c) Only apply implicit directory renames to directories if the other side |
||||
of history is the one doing the renaming. |
||||
|
||||
Limitations -- support in different commands |
||||
-------------------------------------------- |
||||
|
||||
Directory rename detection is supported by 'merge' and 'cherry-pick'. |
||||
Other git commands which users might be surprised to see limited or no |
||||
directory rename detection support in: |
||||
|
||||
* diff |
||||
|
||||
Folks have requested in the past that `git diff` detect directory |
||||
renames and somehow simplify its output. It is not clear whether this |
||||
would be desirable or how the output should be simplified, so this was |
||||
simply not implemented. Further, to implement this, directory rename |
||||
detection logic would need to move from merge-recursive to |
||||
diffcore-rename. |
||||
|
||||
* am |
||||
|
||||
git-am tries to avoid a full three way merge, instead calling |
||||
git-apply. That prevents us from detecting renames at all, which may |
||||
defeat the directory rename detection. There is a fallback, though; if |
||||
the initial git-apply fails and the user has specified the -3 option, |
||||
git-am will fall back to a three way merge. However, git-am lacks the |
||||
necessary information to do a "real" three way merge. Instead, it has |
||||
to use build_fake_ancestor() to get a merge base that is missing files |
||||
whose rename may have been important to detect for directory rename |
||||
detection to function. |
||||
|
||||
* rebase |
||||
|
||||
Since am-based rebases work by first generating a bunch of patches |
||||
(which no longer record what the original commits were and thus don't |
||||
have the necessary info from which we can find a real merge-base), and |
||||
then calling git-am, this implies that am-based rebases will not always |
||||
successfully detect directory renames either (see the 'am' section |
||||
above). merged-based rebases (rebase -m) and cherry-pick-based rebases |
||||
(rebase -i) are not affected by this shortcoming, and fully support |
||||
directory rename detection. |
@ -0,0 +1,105 @@
@@ -0,0 +1,105 @@
|
||||
#!/bin/sh |
||||
|
||||
test_description='git rebase + directory rename tests' |
||||
|
||||
. ./test-lib.sh |
||||
. "$TEST_DIRECTORY"/lib-rebase.sh |
||||
|
||||
test_expect_success 'setup testcase' ' |
||||
test_create_repo dir-rename && |
||||
( |
||||
cd dir-rename && |
||||
|
||||
mkdir x && |
||||
test_seq 1 10 >x/a && |
||||
test_seq 11 20 >x/b && |
||||
test_seq 21 30 >x/c && |
||||
test_write_lines a b c d e f g h i >l && |
||||
git add x l && |
||||
git commit -m "Initial" && |
||||
|
||||
git branch O && |
||||
git branch A && |
||||
git branch B && |
||||
|
||||
git checkout A && |
||||
git mv x y && |
||||
git mv l letters && |
||||
git commit -m "Rename x to y, l to letters" && |
||||
|
||||
git checkout B && |
||||
echo j >>l && |
||||
test_seq 31 40 >x/d && |
||||
git add l x/d && |
||||
git commit -m "Modify l, add x/d" |
||||
) |
||||
' |
||||
|
||||
test_expect_success 'rebase --interactive: directory rename detected' ' |
||||
( |
||||
cd dir-rename && |
||||
|
||||
git checkout B^0 && |
||||
|
||||
set_fake_editor && |
||||
FAKE_LINES="1" git rebase --interactive A && |
||||
|
||||
git ls-files -s >out && |
||||
test_line_count = 5 out && |
||||
|
||||
test_path_is_file y/d && |
||||
test_path_is_missing x/d |
||||
) |
||||
' |
||||
|
||||
test_expect_failure 'rebase (am): directory rename detected' ' |
||||
( |
||||
cd dir-rename && |
||||
|
||||
git checkout B^0 && |
||||
|
||||
git rebase A && |
||||
|
||||
git ls-files -s >out && |
||||
test_line_count = 5 out && |
||||
|
||||
test_path_is_file y/d && |
||||
test_path_is_missing x/d |
||||
) |
||||
' |
||||
|
||||
test_expect_success 'rebase --merge: directory rename detected' ' |
||||
( |
||||
cd dir-rename && |
||||
|
||||
git checkout B^0 && |
||||
|
||||
git rebase --merge A && |
||||
|
||||
git ls-files -s >out && |
||||
test_line_count = 5 out && |
||||
|
||||
test_path_is_file y/d && |
||||
test_path_is_missing x/d |
||||
) |
||||
' |
||||
|
||||
test_expect_failure 'am: directory rename detected' ' |
||||
( |
||||
cd dir-rename && |
||||
|
||||
git checkout A^0 && |
||||
|
||||
git format-patch -1 B && |
||||
|
||||
git am --3way 0001*.patch && |
||||
|
||||
git ls-files -s >out && |
||||
test_line_count = 5 out && |
||||
|
||||
test_path_is_file y/d && |
||||
test_path_is_missing x/d |
||||
) |
||||
' |
||||
|
||||
test_done |
@ -0,0 +1,88 @@
@@ -0,0 +1,88 @@
|
||||
#!/bin/sh |
||||
|
||||
test_description='test if rebase detects and aborts on incompatible options' |
||||
. ./test-lib.sh |
||||
|
||||
test_expect_success 'setup' ' |
||||
test_seq 2 9 >foo && |
||||
git add foo && |
||||
git commit -m orig && |
||||
|
||||
git branch A && |
||||
git branch B && |
||||
|
||||
git checkout A && |
||||
test_seq 1 9 >foo && |
||||
git add foo && |
||||
git commit -m A && |
||||
|
||||
git checkout B && |
||||
echo "q qfoo();" | q_to_tab >>foo && |
||||
git add foo && |
||||
git commit -m B |
||||
' |
||||
|
||||
# |
||||
# Rebase has lots of useful options like --whitepsace=fix, which are |
||||
# actually all built in terms of flags to git-am. Since neither |
||||
# --merge nor --interactive (nor any options that imply those two) use |
||||
# git-am, using them together will result in flags like --whitespace=fix |
||||
# being ignored. Make sure rebase warns the user and aborts instead. |
||||
# |
||||
|
||||
test_rebase_am_only () { |
||||
opt=$1 |
||||
shift |
||||
test_expect_success "$opt incompatible with --merge" " |
||||
git checkout B^0 && |
||||
test_must_fail git rebase $opt --merge A |
||||
" |
||||
|
||||
test_expect_success "$opt incompatible with --strategy=ours" " |
||||
git checkout B^0 && |
||||
test_must_fail git rebase $opt --strategy=ours A |
||||
" |
||||
|
||||
test_expect_success "$opt incompatible with --strategy-option=ours" " |
||||
git checkout B^0 && |
||||
test_must_fail git rebase $opt --strategy-option=ours A |
||||
" |
||||
|
||||
test_expect_success "$opt incompatible with --interactive" " |
||||
git checkout B^0 && |
||||
test_must_fail git rebase $opt --interactive A |
||||
" |
||||
|
||||
test_expect_success "$opt incompatible with --exec" " |
||||
git checkout B^0 && |
||||
test_must_fail git rebase $opt --exec 'true' A |
||||
" |
||||
|
||||
} |
||||
|
||||
test_rebase_am_only --whitespace=fix |
||||
test_rebase_am_only --ignore-whitespace |
||||
test_rebase_am_only --committer-date-is-author-date |
||||
test_rebase_am_only -C4 |
||||
|
||||
test_expect_success '--preserve-merges incompatible with --signoff' ' |
||||
git checkout B^0 && |
||||
test_must_fail git rebase --preserve-merges --signoff A |
||||
' |
||||
|
||||
test_expect_success '--preserve-merges incompatible with --rebase-merges' ' |
||||
git checkout B^0 && |
||||
test_must_fail git rebase --preserve-merges --rebase-merges A |
||||
' |
||||
|
||||
test_expect_success '--rebase-merges incompatible with --strategy' ' |
||||
git checkout B^0 && |
||||
test_must_fail git rebase --rebase-merges -s resolve A |
||||
' |
||||
|
||||
test_expect_success '--rebase-merges incompatible with --strategy-option' ' |
||||
git checkout B^0 && |
||||
test_must_fail git rebase --rebase-merges -Xignore-space-change A |
||||
' |
||||
|
||||
test_done |
Loading…
Reference in new issue