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

git diff --quiet on dirty tree sometimes erroneously exits with status 0

From
Mike Crowe <mac@mcrowe.com>
Date
Apr 5, 2013, 11:09 UTC
Message-ID
<20130405110900.GA2446@mcrowe.com>

I'm seeing a strange problem where "git diff --quiet" sometimes returns an exit code of zero even though the tree is dirty and other invocations of "git diff --quiet" in the same directory return an exit code of 1. I'm using git v1.8.2 from Debian unstable but I've also seen the problem when running v1.8.2 built from source and using older versions from Debian. The repository in question is a submodule.

Unfortunately this bad behaviour only happens deep inside a variable expansion under OpenEmbedded's bitbake so investigation is not straightforward. It's possible that when the bad behaviour occurs other "git diff" and similar informational commands are being run on the same repository in parallel. I have been unable to provoke the problem by more conventional means. :( I can reproduce it on a separate repository on the same machine though.

Using "git diff --exit-code --no-ext-diff" instead seems to avoid the problem but unfortunately so does adding a sleep before calling "git diff --quiet" so that result isn't necessarily significant.

I have been able to strace the problematic case. Diffing the straces and ignoring unrelated differences such as addresses yields (+ = diff returns 0, - = diff returns 1):

 [...lots of omitted stuff...]
 open("/home/mac/src/oe/sources/linux/.gitmodules", O_RDONLY) = -1 ENOENT
 (No such file or directory)
 access("/etc/gitconfig", R_OK)          = -1 ENOENT (No such file or
 directory)
 access("/home/mac/.config/git/config", R_OK) = -1 ENOENT (No such file or
 directory)
 access("/home/mac/.gitconfig", R_OK)    = 0
 open("/home/mac/.gitconfig", O_RDONLY)  = 20
 fstat(20, {st_mode=S_IFREG|0644, st_size=424, ...}) = 0
0x7f22bfa18000
 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =
0x7f79414f1000
 read(20, "[color]\n\tui = true\n[core]\n#\tpage"..., 4096) = 424
 read(20, "", 4096)                      = 0
 close(20)                               = 0
 munmap(0x7f79414f1000, 4096)            = 0
 access("/home/mac/src/oe/.git/modules/sources/linux/config", R_OK) = 0
 open("/home/mac/src/oe/.git/modules/sources/linux/config", O_RDONLY) = 20
 fstat(20, {st_mode=S_IFREG|0644, st_size=1467, ...}) = 0
 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =
0x7f79414f1000
 read(20, "[core]\n\trepositoryformatversion "..., 4096) = 1467
 read(20, "", 4096)                      = 0
 close(20)                               = 0
 munmap(0x7f79414f1000, 4096)            = 0
 chdir("/home/mac/src/oe/sources/linux") = 0
 lstat(".gitignore", {st_mode=S_IFREG|0644, st_size=955, ...}) = 0
At this point the diff that erroneously returned zero went off and did:

+lstat(".gitignore", {st_mode=S_IFREG|0644, st_size=955, ...}) = 0 +open("/home/mac/src/oe/.git/modules/sources/linux/objects/pack", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 20 +getdents(20, /* 4 entries */, 32768) = 192 +access("/home/mac/src/oe/.git/modules/sources/linux/objects/pack/pack-2711d98bc06e8caa9c8d8e10ee40e645e6d05f18.keep", F_OK) = -1 ENOENT (No such file or directory) +stat("/home/mac/src/oe/.git/modules/sources/linux/objects/pack/pack-2711d98bc06e8caa9c8d8e10ee40e645e6d05f18.pack", {st_mode=S_IFREG|0444, st_size=148768254, ...}) = 0 +getdents(20, /* 0 entries */, 32768) = 0 +close(20) = 0

..and spent a while re-reading config files before continuing to recurse over all the files but whilst the diff that provides the correct result merely lstats each file this one reads them too:

 lstat("COPYING", {st_mode=S_IFREG|0644, st_size=18693, ...}) = 0
+open("COPYING", O_RDONLY)               = 21
+read(21, "\n   NOTE! This copyright does *n"..., 18693) = 18693
+close(21)                               = 0

When it comes across the repository file that has been changed the behaviour of the invocation that provides the correct result is rather different:

 lstat("drivers/net/bcmgenet/bcmgenet.c", {st_mode=S_IFREG|0644,
 st_size=114290, ...}) = 0
-lstat("drivers/net/bcmgenet/bcmgenet.c", {st_mode=S_IFREG|0644,
st_size=114290, ...}) = 0
-open("/home/mac/src/oe/.git/modules/sources/linux/objects/pack",
O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 20
-getdents(20, /* 4 entries */, 32768)    = 192
-access("/home/mac/src/oe/.git/modules/sources/linux/objects/pack/pack-2711d98bc06e8caa9c8d8e10ee40e645e6d05f18.keep",
F_OK) = -1 ENOENT (No such file or directory)
-stat("/home/mac/src/oe/.git/modules/sources/linux/objects/pack/pack-2711d98bc06e8caa9c8d8e10ee40e645e6d05f18.pack",
{st_mode=S_IFREG|0444, st_size=148768254, ...}) = 0
-getdents(20, /* 0 entries */, 32768)    = 0
-close(20)                               = 0
-open("/home/mac/src/oe/.git/modules/sources/linux/objects/info/alternates",
O_RDONLY|O_NOATIME) = -1 ENOENT (No such file or directory)
-open("/home/mac/src/oe/.git/modules/sources/linux/objects/pack/pack-2711d98bc06e8caa9c8d8e10ee40e645e6d05f18.idx",
O_RDONLY|O_NOATIME) = 20
-fstat(20, {st_mode=S_IFREG|0444, st_size=1945504, ...}) = 0
-mmap(NULL, 1945504, PROT_READ, MAP_PRIVATE, 20, 0) = 0x7f22bea11000
-close(20)                               = 0
-getrlimit(RLIMIT_NOFILE, {rlim_cur=1024, rlim_max=4*1024}) = 0
-open("/home/mac/src/oe/.git/modules/sources/linux/objects/pack/pack-2711d98bc06e8caa9c8d8e10ee40e645e6d05f18.pack",
O_RDONLY|O_NOATIME) = 20
-fstat(20, {st_mode=S_IFREG|0444, st_size=148768254, ...}) = 0
-fcntl(20, F_GETFD)                      = 0
-fcntl(20, F_SETFD, FD_CLOEXEC)          = 0
-read(20, "PACK\0\0\0\2\0\1\17D", 12)    = 12
-lseek(20, 148768234, SEEK_SET)          = 148768234
-read(20, "\226~C\235\340\255\221\354e't>\325p\340M\307\314j@", 20) = 20
-mmap(NULL, 148768254, PROT_READ, MAP_PRIVATE, 20, 0) = 0x7f22b5c30000

and at that point the diff that provides the correct exit status basically gives up since it has found a change. The diff that provides the incorrect exit status _does_not_ read this file that has changed and any other files that have changed even though it reads all the unchanged files.

At the end the diff that provides the incorrect exit status appears to rewrite the index and then:

+rename("/home/mac/src/oe/.git/modules/sources/linux/index.lock", "/home/mac/src/oe/.git/modules/sources/linux/index") = 0 +fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 8), ...}) = 0 +close(1) = 0 +exit_group(0) = ?

I'm afraid that I don't know enough about Git's internals to understand why it decided to look at the pack files and rebuild the index but it seems that if this happens the exit status is correct. :(

(Full straces available on request by private email if required.)
Mike.
Message 1 of 1 in “git diff --quiet on dirty tree sometimes erroneously exits with status 0”
  1. Mike CroweApr 5, 2013

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.