2

I use git in some automated tests as an easy way to have a "diff" command available with same syntax on both Linux and Windows. It's working ok, but since the files I'm comparing are on a shared drive I've been stumbling about differences in filemode being (unwantedly) reported as changes. I looked around and found the core.filemode setting being recommended in this case - but in my experiments, the setting does not seem to be used when outside of a working directory.

This is my diff command for the test:

git diff --ignore-space-at-eol --no-index createdFile expectedFile

Output:

diff --git a/createdFile b/expectedFile
old mode 100644
new mode 100755

(and I want to ignore differences in just the file mode).

What I've tried:

git -c core.filemode=false diff --ignore-space-at-eol --no-index createdFile expectedFile

As suggested e.g. in this answer. The output is however still the same as above (git version: 2.25.1 - maybe this filemode actually doesn't work there as somehow hinted in here?) Setting core.filemode globally also doesn't work.

My current workaround is to copy the "expected" files to a location within the linux filesystem with default permissions. Would be great if this could be avoided though...

codeling
  • 11,056
  • 4
  • 42
  • 71
  • 2
    With `core.filemode=false`, Git simply copies the mode from the index into the stat data before feeding the rest to the diff engine. So this works great with diffing against the index ... but when using `--no-index` there's nothing to copy. It just uses the result from the OS's `lstat` call. You could fix this by changing `get_mode` in `diff_no_index.c`: if `core.filemode` is false (`trust_executable_bit` is cleared), `*mode` should have bits 0111 cleared if S_IFREG. – torek Jan 11 '22 at 09:47

1 Answers1

3

There is a workaround: Show git diff, ignoring file permission changes? (Zed notes that git diff -G. does the trick). Note: the dot (period) in -G. is the key here. A mode-only diff has no diff text, so . fails to match; all other diffs do have diff text, even if it's a pure delete, so . matches something, and the file gets displayed. But this is just a workaround, not a true fix.


To expand a bit on my comment above: there is indeed no way to do exactly what you wanted (other than temporarily chmod-ing the files, or copying them and chmod-ing the copies).

When you run git diff --no-index (or any diff in which --no-index is implied), Git calls lstat on each file to find out whether it's a regular file or directory and what its mode is. This call is made from diff-no-index.c around line 43, whose last few lines include:

else
        *mode = st.st_mode;

This assumes that the OS, whatever it is, produces consistent st_mode executable bits, even if they cannot be trusted.

To make this obey the core.filemode setting, the last line might need to read, e.g.:

        *mode = !S_IFREG(st.st_mode) || trust_executable_bit ?
            st.st_mode : st.st_mode & ~0111;

for instance. This could perhaps be simplified: I don't think Git cares about the x bits on directories so just always masking away 0111 might suffice, when trust_executable_bit is false.

torek
  • 448,244
  • 59
  • 642
  • 775