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 10, 2007, 06:44 UTC
Message-ID
<7vmz0dmb2u.fsf@assigned-by-dhcp.cox.net>
In-Reply-To
<46424ECC.6030907@zytor.com>
"H. Peter Anvin" <hpa@zytor.com> writes:
Show 33 quoted lines
> Applying 'Revert "x86-64: Make arch/x86-64/boot a symlink to
> arch/i386/boot"'
>
> Adds trailing whitespace.
> .dotest/patch:117:FDARGS =
> Adds trailing whitespace.
> .dotest/patch:350: * Page 0 is deliberately kept safe, since System
> Management Mode code in
> Adds trailing whitespace.
> .dotest/patch:352: * useful for future device drivers that either access
> the BIOS via VM86
> Adds trailing whitespace.
> .dotest/patch:648: *
> Adds trailing whitespace.
> .dotest/patch:649: * This is a collection of several routines from
> gzip-1.0.3
> error: arch/x86_64/boot/.gitignore: already exists in working directory
> error: arch/x86_64/boot/Makefile: already exists in working directory
> error: arch/x86_64/boot/compressed/Makefile: already exists in working
> directory
> error: arch/x86_64/boot/compressed/head.S: already exists in working
> directory
> error: arch/x86_64/boot/compressed/misc.c: already exists in working
> directory
> error: arch/x86_64/boot/compressed/vmlinux.lds: already exists in
> working directory
> error: arch/x86_64/boot/compressed/vmlinux.scr: already exists in
> working directory
> error: arch/x86_64/boot/install.sh: already exists in working directory
> error: arch/x86_64/boot/mtools.conf.in: already exists in working directory
> error: arch/x86_64/boot/tools/.gitignore: already exists in working
> directory
> error: arch/x86_64/boot/tools/build.c: already exists in working directory
Ahh.
 * The tree state before this patch is applied has arch/x86_64/boot
   as a symlink pointing at ../i386/boot/
 * The patch tries to remove arch/x86_64/boot symlink, and
   create bunch of files there: .gitignore, Makefile, etc.

This is unfortunately a bit deeper than just git-rebase. You exposed a nasty corner case problem in the stock git-apply, probably one of the most important tool the kernel project uses every day.

git-apply tries to be careful while applying patches; it never touches the working tree until it is convinced that the patch would apply cleanly. One of the check it does is that when it knows a path is going to be created by the patch (in your example, arch/x86_64/boot/.gitignore), it runs lstat() on the path to make sure it does not exist.

This leads to a false alarm. Because we do not touch the working tree before all the check passes, when we try to make sure that arch/x86_64/boot/.gitignore does not exist yet, we haven't removed the arch/x86_64/boot symlink. The lstat() check ends up seeing arch/i386/boot/.gitignore through the yet-to-be-removed symlink, and says "Hey, you already have a file there, but what you fed me is a patch to create a new file. I am not going to clobber what you have in the working tree."

We have similar checks to see a file we are going to modify does exist and match the preimage of the diff, which is done by directly opening and reading the file.

For a file we are going to delete, we make sure that it does exist and matches what is going to be removed (a removal patch records the full preimage, so we check what you have in your working tree matches it in full -- otherwise we would risk losing your local changes), which again is done by directly opening and reading the file.

These checks need to be adjusted so that they are not fooled by symlinks in the middle.

 - To make sure something does not exist, first lstat().  If it
   does not exist, it does not, so be happy.  If it _does_, we
   might be getting fooled by a symlink in the middle, so break
   leading paths and see if there are symlinks involved.  When
   we are checking for a path a/b/c/d, if any of a, a/b, a/b/c
   is a symlink, then a/b/c/d does _NOT_ exist, for the purpose
   of our test.
   This would fix this particular case you saw, and would not
   add extra overhead in the usual case.
 - To make sure something already exists, first lstat().  If it
   does not exist, barf (up to this, we already do).  Even if it
   does seem to exist, we might be getting fooled by a symlink
   in the middle, so make sure leading paths are not symlinks.
   This would make the normal codepath much more expensive for
   deep trees, which is a bit worrisome.

When the code writes out the result after the checks pass, we first delete all the paths that are going to be deleted, and also delete all the paths that are being modified. Then in the second pass, we create all the paths that are being created and also modified by writing their final contents out, while creating leading directories as needed. Because of this, a patch that removes a symlink and then makes it a directory to hold new files would first remove the symlink and then create the directory at the right location and deposit newly created files there. So I do not expect any change is needed in the writeout codepath.

By the way, I noticed a few things while diagnosing this.
 * It was very considerate of you to leave "rebase-1" branch for
   my postmortem in the repository.  You know what are needed
   for debugging very well.
 * git-rebase with -m is dog slow.  There were people who
   advocated to make it the default, but they probably are
   either working in a very small project, or working on a
   filesystem that even git-apply is slow that the speed
   difference does not matter to them.
Previous: H. Peter AnvinNext: Johannes Sixt
Message 8 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.