git/list[1] front-page[2] threads[3] people[4] search[5] about
 

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

From
Junio C Hamano <junkio@cox.net>
Date
May 14, 2007, 04:37 UTC
Message-ID
<7v1whkow9z.fsf@assigned-by-dhcp.cox.net>
In-Reply-To
<7vps569904.fsf@assigned-by-dhcp.cox.net>
Junio C Hamano <junkio@cox.net> writes:
Show 11 quoted lines
> 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.

Previous: H. Peter Anvin
Message 17 of 17 in “git rebase chokes on directory -> symlink -> directory”
  1. H. Peter AnvinMay 8, 2007
  2. Alex RiesenMay 8, 2007
  3. H. Peter AnvinMay 9, 2007
  4. Alex RiesenMay 9, 2007
  5. H. Peter AnvinMay 9, 2007
  6. Alex RiesenMay 9, 2007
  7. H. Peter AnvinMay 9, 2007
  8. Junio C HamanoMay 10, 2007
  9. Johannes SixtMay 10, 2007
  10. Shawn O. PearceMay 10, 2007
  11. Junio C HamanoMay 11, 2007
  12. Linus TorvaldsMay 11, 2007
  13. Junio C HamanoMay 11, 2007
  14. apply: do not get confused by symlinks in the middleJunio C Hamano, May 11, 2007
  15. Junio C HamanoMay 12, 2007
  16. H. Peter AnvinMay 12, 2007
  17. Junio C HamanoMay 14, 2007

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.