Subject: Re: Suggestion to revert touching files when the hash matches (problem with hardlinks)

From: Jan Niklas Böhm <[email-masked]>
Date: Tue, 1 Nov 2022 10:02:47 +0100

First of all, thanks for the response.

> This is not what happens, because your proposition was that b is constructed from a via ln. So, a and b point to the same inode. In the second run, redo links a to $3. Now, a, b and $3 point to the same inode, regardless of whether redo mv's $3 to a afterwards or not. This is where the toolchain breaks, because it looks like a changed underneath, because all three point to the same inode.

I disagree with this, maybe there is a misunderstanding.  My setting is 
the following.  I have two .do files: a.do with the line

	echo aaa # or whatever

and b.do with the two lines

	redo-ifchange a
	ln a $3

Now I call "redo b".  This gives me the following files with "stat 
--format="%n: inode=%i, mtime=%Y" a b":

	a: inode=646, mtime=1667292427
	b: inode=646, mtime=1667292427

Now if I change a.do to

	echo bbb # or whatever

and I "REDO_INODE_TRUST=mtime redo a" (ctime already complains because 
`ln` changes the ctime for "a"), I get the files

	a: inode=655, mtime=1667292495
	b: inode=646, mtime=1667292427

So we can see that "b" is not changed, and "REDO_INODE_TRUST=mtime redo 
b" also works and the output is as expected

	a: inode=655, mtime=1667292495
	b: inode=655, mtime=1667292495

However, if a.do is now changed to

	echo bbb # same output as before

and we call "REDO_INODE_TRUST=mtime redo a", then the mtime for b is 
messed up:

	a: inode=655, mtime=1667292646
	b: inode=655, mtime=1667292646  # wasn't redone, but changed

If we now call "REDO_INODE_TRUST=mtime redo b", we get the warning

	warn b externally modified: not redoing

which is due to "a" being touched (as the file hash is equal to $3). 
This does not happen if $3 was renamed to "a", as then "b" would point 
to the old inode.

If the attached patch is applied to run.go (basically remove the block 
that touches the file when the hash matches), then this issue does not 
occur, as the file "a" will have a new inode number in the last example.

I hope the example makes the point a bit more clear.  Sorry that it got 
so long.

733,754d732
< 				if hsh == hshPrev {
< 					tracef(CDebug, "%s has same hash, not renaming", tgtOrig)
< 					err = os.Remove(fd.Name())
< 					if err != nil {
< 						goto Finish
< 					}
< 					err = os.Chtimes(path.Join(cwdOrig, tgt), finished, finished)
< 					if err != nil {
< 						goto Finish
< 					}
< 					if !NoSync {
< 						err = syncDir(cwdOrig)
< 						if err != nil {
< 							goto Finish
< 						}
< 					}
< 					err = depWrite(fdDep, cwdOrig, tgt, hshPrev)
< 					if err != nil {
< 						goto Finish
< 					}
< 					goto RecCommit
< 				}
778d755
< 	RecCommit: