{"thread":{"id":"33391","subject":"git diff --quiet on dirty tree sometimes erroneously exits with status 0","startedAt":"2013-04-05T11:09:00Z","lastAt":"2013-04-05T11:09:00Z","messageCount":1,"participants":["Mike Crowe"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"213260","messageId":"20130405110900.GA2446@mcrowe.com","threadId":"33391","inReplyTo":null,"subject":"git diff --quiet on dirty tree sometimes erroneously exits with status 0","fromName":"Mike Crowe","fromEmail":"mac@mcrowe.com","sentAt":"2013-04-05T11:09:00Z","receivedAt":"2013-04-05T11:09:00Z","isPatch":false,"sender":{"key":"mac@mcrowe.com","avatar":"https://avatars.githubusercontent.com/u/93615?v=4"},"body":"I'm seeing a strange problem where \"git diff --quiet\" sometimes returns an\nexit code of zero even though the tree is dirty and other invocations of\n\"git diff --quiet\" in the same directory return an exit code of 1. I'm\nusing git v1.8.2 from Debian unstable but I've also seen the problem when\nrunning v1.8.2 built from source and using older versions from Debian. The\nrepository in question is a submodule.\n\nUnfortunately this bad behaviour only happens deep inside a variable\nexpansion under OpenEmbedded's bitbake so investigation is not\nstraightforward. It's possible that when the bad behaviour occurs other\n\"git diff\" and similar informational commands are being run on the same\nrepository in parallel. I have been unable to provoke the problem by more\nconventional means. :( I can reproduce it on a separate repository on the\nsame machine though.\n\nUsing \"git diff --exit-code --no-ext-diff\" instead seems to avoid the\nproblem but unfortunately so does adding a sleep before calling \"git diff\n--quiet\" so that result isn't necessarily significant.\n\nI have been able to strace the problematic case. Diffing the straces and\nignoring unrelated differences such as addresses yields  (+ =\ndiff returns 0, - = diff returns 1):\n\n [...lots of omitted stuff...]\n open(\"/home/mac/src/oe/sources/linux/.gitmodules\", O_RDONLY) = -1 ENOENT\n (No such file or directory)\n access(\"/etc/gitconfig\", R_OK)          = -1 ENOENT (No such file or\n directory)\n access(\"/home/mac/.config/git/config\", R_OK) = -1 ENOENT (No such file or\n directory)\n access(\"/home/mac/.gitconfig\", R_OK)    = 0\n open(\"/home/mac/.gitconfig\", O_RDONLY)  = 20\n fstat(20, {st_mode=S_IFREG|0644, st_size=424, ...}) = 0\n0x7f22bfa18000\n mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =\n0x7f79414f1000\n read(20, \"[color]\\n\\tui = true\\n[core]\\n#\\tpage\"..., 4096) = 424\n read(20, \"\", 4096)                      = 0\n close(20)                               = 0\n munmap(0x7f79414f1000, 4096)            = 0\n access(\"/home/mac/src/oe/.git/modules/sources/linux/config\", R_OK) = 0\n open(\"/home/mac/src/oe/.git/modules/sources/linux/config\", O_RDONLY) = 20\n fstat(20, {st_mode=S_IFREG|0644, st_size=1467, ...}) = 0\n mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =\n0x7f79414f1000\n read(20, \"[core]\\n\\trepositoryformatversion \"..., 4096) = 1467\n read(20, \"\", 4096)                      = 0\n close(20)                               = 0\n munmap(0x7f79414f1000, 4096)            = 0\n chdir(\"/home/mac/src/oe/sources/linux\") = 0\n lstat(\".gitignore\", {st_mode=S_IFREG|0644, st_size=955, ...}) = 0\n\nAt this point the diff that erroneously returned zero went off and did:\n\n+lstat(\".gitignore\", {st_mode=S_IFREG|0644, st_size=955, ...}) = 0\n+open(\"/home/mac/src/oe/.git/modules/sources/linux/objects/pack\",\nO_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 20\n+getdents(20, /* 4 entries */, 32768)    = 192\n+access(\"/home/mac/src/oe/.git/modules/sources/linux/objects/pack/pack-2711d98bc06e8caa9c8d8e10ee40e645e6d05f18.keep\",\nF_OK) = -1 ENOENT (No such file or directory)\n+stat(\"/home/mac/src/oe/.git/modules/sources/linux/objects/pack/pack-2711d98bc06e8caa9c8d8e10ee40e645e6d05f18.pack\",\n{st_mode=S_IFREG|0444, st_size=148768254, ...}) = 0\n+getdents(20, /* 0 entries */, 32768)    = 0\n+close(20)                               = 0\n\n..and spent a while re-reading config files before continuing to recurse\nover all the files but whilst the diff that provides the correct result\nmerely lstats each file this one reads them too:\n\n lstat(\"COPYING\", {st_mode=S_IFREG|0644, st_size=18693, ...}) = 0\n+open(\"COPYING\", O_RDONLY)               = 21\n+read(21, \"\\n   NOTE! This copyright does *n\"..., 18693) = 18693\n+close(21)                               = 0\n\nWhen it comes across the repository file that has been changed the\nbehaviour of the invocation that provides the correct result is rather\ndifferent:\n\n lstat(\"drivers/net/bcmgenet/bcmgenet.c\", {st_mode=S_IFREG|0644,\n st_size=114290, ...}) = 0\n-lstat(\"drivers/net/bcmgenet/bcmgenet.c\", {st_mode=S_IFREG|0644,\nst_size=114290, ...}) = 0\n-open(\"/home/mac/src/oe/.git/modules/sources/linux/objects/pack\",\nO_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 20\n-getdents(20, /* 4 entries */, 32768)    = 192\n-access(\"/home/mac/src/oe/.git/modules/sources/linux/objects/pack/pack-2711d98bc06e8caa9c8d8e10ee40e645e6d05f18.keep\",\nF_OK) = -1 ENOENT (No such file or directory)\n-stat(\"/home/mac/src/oe/.git/modules/sources/linux/objects/pack/pack-2711d98bc06e8caa9c8d8e10ee40e645e6d05f18.pack\",\n{st_mode=S_IFREG|0444, st_size=148768254, ...}) = 0\n-getdents(20, /* 0 entries */, 32768)    = 0\n-close(20)                               = 0\n-open(\"/home/mac/src/oe/.git/modules/sources/linux/objects/info/alternates\",\nO_RDONLY|O_NOATIME) = -1 ENOENT (No such file or directory)\n-open(\"/home/mac/src/oe/.git/modules/sources/linux/objects/pack/pack-2711d98bc06e8caa9c8d8e10ee40e645e6d05f18.idx\",\nO_RDONLY|O_NOATIME) = 20\n-fstat(20, {st_mode=S_IFREG|0444, st_size=1945504, ...}) = 0\n-mmap(NULL, 1945504, PROT_READ, MAP_PRIVATE, 20, 0) = 0x7f22bea11000\n-close(20)                               = 0\n-getrlimit(RLIMIT_NOFILE, {rlim_cur=1024, rlim_max=4*1024}) = 0\n-open(\"/home/mac/src/oe/.git/modules/sources/linux/objects/pack/pack-2711d98bc06e8caa9c8d8e10ee40e645e6d05f18.pack\",\nO_RDONLY|O_NOATIME) = 20\n-fstat(20, {st_mode=S_IFREG|0444, st_size=148768254, ...}) = 0\n-fcntl(20, F_GETFD)                      = 0\n-fcntl(20, F_SETFD, FD_CLOEXEC)          = 0\n-read(20, \"PACK\\0\\0\\0\\2\\0\\1\\17D\", 12)    = 12\n-lseek(20, 148768234, SEEK_SET)          = 148768234\n-read(20, \"\\226~C\\235\\340\\255\\221\\354e't>\\325p\\340M\\307\\314j@\", 20) = 20\n-mmap(NULL, 148768254, PROT_READ, MAP_PRIVATE, 20, 0) = 0x7f22b5c30000\n\nand at that point the diff that provides the correct exit status basically\ngives up since it has found a change. The diff that provides the incorrect\nexit status _does_not_ read this file that has changed and any other files\nthat have changed even though it reads all the unchanged files.\n\nAt the end the diff that provides the incorrect exit status appears to\nrewrite the index and then:\n\n+rename(\"/home/mac/src/oe/.git/modules/sources/linux/index.lock\",\n\"/home/mac/src/oe/.git/modules/sources/linux/index\") = 0\n+fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 8), ...}) = 0\n+close(1)                                = 0\n+exit_group(0)                           = ?\n\nI'm afraid that I don't know enough about Git's internals to understand why\nit decided to look at the pack files and rebuild the index but it seems\nthat if this happens the exit status is correct. :(\n\n(Full straces available on request by private email if required.)\n\nMike.\n"}]}