Thread (1 message) 1 message, 1 author, 2016-06-15

Re: git rebase chokes on directory -> symlink -> directory

From: Junio C Hamano <hidden>
Date: 2016-06-15 22:43:10

Junio C Hamano [off-list ref] writes:
However.

I usually have "[apply] whitespace = strip" in my ~/.gitconfig,
but during this verification run, I disabled it to keep rebase
from falling back to 3-way merge using merge-recursive.  If I
turn it on, rebase still fails and I strongly suspect "rebase
-m" would fail the same way, although I haven't tried it (it
takes too much time).

I'll be somewhat busy this weekend, so I would welcome anybody
else beating me to fixing the problem in merge-recursive.
Yuck.  merge-recursive does seem to have problem with D/F
conflict in general; I suspect this is not limited to cases that
involve symbolic links.

First, the setup.

$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/hpa/linux-2.6-newsetup.git/
$ cd linux-2.6-newsetup.git
$ git fetch git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git/
$ git branch other b2ad90f4969226fe8cf3edc5330711ed5fc20105
$ git branch ancestor other^1
$ git reset --hard ancestor
$ echo >>Makefile ; git add Makefile ; git commit -m 'change one'
$ git branch try

To see that the underlying read-tree is working as expected (and
do not have to be debugged), we can first try the usual three-way
read tree:

$ git read-tree -m -u ancestor HEAD other
$ git ls-files -u --abbrev arch/x86-64

This would show...

    120000 ad3f146 3       arch/x86_64/boot
    100644 495f20c 1       arch/x86_64/boot/.gitignore
    100644 495f20c 2       arch/x86_64/boot/.gitignore
    100644 ee6f650 1       arch/x86_64/boot/Makefile
    100644 ee6f650 2       arch/x86_64/boot/Makefile
    ...

The current HEAD (the one that we tagged as 'try') has the
directory arch/x86_64/boot/, but all of its files are unchanged
from the "common ancestor", so they all have identical stages 1
and 2, with stage 3 missing.  The other tree has boot/ as a
symlink.  These "One-side removes other side does not touch" and
"one-side adds" cases are left unmerged by "read-tree -m -u", as
that is how merge-recursive can find renames to begin with.

Now we've seen what the read-tree (which is the same machinery
used as git_merge_trees() in mege-recursive) does with these
three trees, let's see how merge-recursive finishes this off:

    $ git reset --hard try
    $ git merge-recursive ancestor -- HEAD other
    CONFLICT (directory/file): There is a directory with name arch/x86_64/boot in HEAD. Added arch/x86_64/boot as arch/x86_64/boot~other
    Removed arch/x86_64/boot/.gitignore
    Removed arch/x86_64/boot/Makefile
    Removed arch/x86_64/boot/bootsect.S
    Removed arch/x86_64/boot/compressed/Makefile
    Removed arch/x86_64/boot/compressed/head.S
    Removed arch/x86_64/boot/compressed/misc.c
    Removed arch/x86_64/boot/compressed/vmlinux.lds
    Removed arch/x86_64/boot/compressed/vmlinux.scr
    Removed arch/x86_64/boot/install.sh
    Removed arch/x86_64/boot/mtools.conf.in
    Removed arch/x86_64/boot/setup.S
    Removed arch/x86_64/boot/tools/.gitignore
    Removed arch/x86_64/boot/tools/build.c

There is no rename, so rename detection does not interfere, but
the D/F conflict detection code in merge-recursive thinks that
arch/x86_64/boot is a directory in one tree (yes, it is true in
the original tree, but it goes away as all files under it), and
a non-directory in another.  And instead of replacing the boot/
directory that becomes empty with a new symlink boot/, it
creates the boot~funny-name symlink and fails the operation.

When it actually is checking out arch/x86_64/boot out of the
resolved index, it should notice that (1) arch/x86_64/boot
directory is unnecessary to house anything in the resulting
index anymore, and that (2) there is no locally created
untracked file that is 'precious' [*1*].  Then it can rmdir()
and create the boot/ symlink in its place.  To fix this, it may
be necessary to update its checkout code to perform the "remove
first then create" two-pass process git-apply does.

It is unfortunate that for merge-recursive it is probably too
cumbersome to always do the right thing, but I think it should
at least notice that arch/x86_64/boot whose files all disappear
does not conflict with creation of the new symlink.
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help