Two hooks carried one sentence that stated how a tool behaves as a property of the tool. Running the two hooks’ test suites on a Linux host showed it false there: what the tool did depended on how the repository was mounted into the scanner’s container.

The sentence

The pre-commit hooks in the container repository and the infrastructure repository carried the same comment: hashing the full staged patch makes the version-control tool read every new object. Both hooks run a scanner in a container. On the first machine both suites passed.

What the A/B showed

On the Linux host the two suites ran on, the infrastructure repository’s selftest passed 11 of 12 cases. The failing case, an object unreadable in the scanner’s container, was still blocked, but by a second check that caught what the first did not.

An A/B on that host explained it. Take the failing case, and run it twice on the same Linux host:

  • With the working-tree copy of the file removed, the patch step inside the scanner’s container failed and the first check blocked.
  • With the copy kept, the patch step succeeded and produced a hash identical to the host’s.

What differs between the two runs is the copy alone. The tool decides from a file’s stat data whether a working-tree copy looks unchanged, and reuses a copy that does instead of reading the object. The measurement compared seven of those stat fields between host and container: device, inode, uid, gid, size, mtime and ctime. So with the copy kept, hashing the patch did not force a read.

The comment was false on both Linux hosts, on the local file systems measured.

On the Linux host the two suites ran on, the kept-copy result came from three runs in all, across two file systems. A second Linux host gave the same result in six runs, three on each of two file systems, and there the seven fields were identical wherever both host and container could report the file’s stat data.

The A/B also ran on the first machine, with the blob moved out of the object store so that no read of it could succeed. The container’s tool failed to read the object in 6 of 6 runs with the copy kept, and in 6 of 6 with it removed. Size, mtime and ctime were equal between host and container there. Device, inode, uid and gid differed. With only modification time and size compared, the patch step succeeded in 2 of 2 runs, with no read of the object. That setting switches off all four differing fields at once, so the measurement shows that those differences are what made the container’s tool read the copy as changed and go to the object.

How far this reaches

The measurement is two suites: the infrastructure repository’s 12-case selftest and the container repository’s own suite. Each ran on the first machine and on one Linux host. On that Linux host, the container repository’s suite gave 31 of 32, on the same case as the selftest’s one failure. Other file systems, other container runtimes and other configurations of the first machine were not measured, and nothing here measured another tool or any other check.

The night this was found is told in The sentence both hooks carried.