{"thread":{"id":"57271","subject":"A puzzle: reset --hard and hard links","startedAt":"2022-01-19T20:38:06Z","lastAt":"2022-01-26T18:46:23Z","messageCount":18,"participants":["Michael Herrmann","brian m. carlson","Junio C Hamano","rsbecker@nexbridge.com","Andreas Schwab","Ævar Arnfjörð Bjarmason"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"446522","messageId":"CABrKpmDseZkPCpRb8KmBJaxDp24sySJay5ffZrxqgSMGKyj5qQ@mail.gmail.com","threadId":"57271","inReplyTo":null,"subject":"A puzzle: reset --hard and hard links","fromName":"Michael Herrmann","fromEmail":"michael@herrmann.io","sentAt":"2022-01-19T20:37:48Z","receivedAt":"2022-01-19T20:38:06Z","isPatch":false,"sender":{"key":"michael@herrmann.io","avatar":null},"body":"Hi all,\n\nIt seems `git reset --hard` recreates files that have external hard\nlinks. Is this intended?\n\nThe example below highlights the behavior. I have an unchanged Git\nrepository. When I create an (outside) hard link to a file in the\nrepository, then `git reset --hard` re-creates the file with a new\nmodification time and inode. This occurs on Debian 11 with Git 2.30.2\nbut not on Debian 10 with Git 2.20.1\n\n> git init\nInitialized empty Git repository in .../git-test/.git/\n> echo \"test\" > file.txt\n> git add file.txt\n> git commit -m \"Import\"\n[main (root-commit) f73709f] Import\n 1 file changed, 1 insertion(+)\n create mode 100644 file.txt\n> stat -c '%y' file.txt\n2022-01-19 18:43:52.147781748 +0100\n> ls -i file.txt\n74458979 file.txt\n> ln file.txt ../file.txt\n> git reset --hard\nHEAD is now at f73709f Import\n> stat -c '%y' file.txt\n2022-01-19 18:44:47.013167127 +0100\n> ls -i file.txt\n74458995 file.txt\n\nCan this behavior be avoided?\n\nBest,\nMichael\n"},{"id":"446525","messageId":"YeiOoAcM7TMK2pgz@camp.crustytoothpaste.net","threadId":"57271","inReplyTo":"CABrKpmDseZkPCpRb8KmBJaxDp24sySJay5ffZrxqgSMGKyj5qQ@mail.gmail.com","subject":"Re: A puzzle: reset --hard and hard links","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-01-19T22:20:16Z","receivedAt":"2022-01-19T22:20:20Z","isPatch":false,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2022-01-19 at 20:37:48, Michael Herrmann wrote:\n> Hi all,\n> \n> It seems `git reset --hard` recreates files that have external hard\n> links. Is this intended?\n> \n> The example below highlights the behavior. I have an unchanged Git\n> repository. When I create an (outside) hard link to a file in the\n> repository, then `git reset --hard` re-creates the file with a new\n> modification time and inode. This occurs on Debian 11 with Git 2.30.2\n> but not on Debian 10 with Git 2.20.1\n> \n> > git init\n> Initialized empty Git repository in .../git-test/.git/\n> > echo \"test\" > file.txt\n> > git add file.txt\n> > git commit -m \"Import\"\n> [main (root-commit) f73709f] Import\n>  1 file changed, 1 insertion(+)\n>  create mode 100644 file.txt\n> > stat -c '%y' file.txt\n> 2022-01-19 18:43:52.147781748 +0100\n> > ls -i file.txt\n> 74458979 file.txt\n> > ln file.txt ../file.txt\n> > git reset --hard\n> HEAD is now at f73709f Import\n> > stat -c '%y' file.txt\n> 2022-01-19 18:44:47.013167127 +0100\n> > ls -i file.txt\n> 74458995 file.txt\n> \n> Can this behavior be avoided?\n\nGit generally doesn't guarantee that it will preserve hard links in any\nparticular situation.  It can and does replace files rather than writing\nover the existing ones, so this behavior is expected in at least some\ncircumstances.\n\nWhether it happens in this particular case probably depends on what data\nis in the index and whether it's considered stale, since if the file is\nout of date, I believe a git reset --hard will replace it rather than\ntry to determine whether it's up to date.\n-- \nbrian m. carlson (he/him or they/them)\nToronto, Ontario, CA\n"},{"id":"446526","messageId":"xmqqilufl5cv.fsf@gitster.g","threadId":"57271","inReplyTo":"YeiOoAcM7TMK2pgz@camp.crustytoothpaste.net","subject":"Re: A puzzle: reset --hard and hard links","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-01-19T22:37:20Z","receivedAt":"2022-01-19T22:37:32Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n\n> Whether it happens in this particular case probably depends on what data\n> is in the index and whether it's considered stale, since if the file is\n> out of date, I believe a git reset --hard will replace it rather than\n> try to determine whether it's up to date.\n\nTrue, but I think it answers a different question, which was \"to\nreplace or to overwrite\".  I do not recall any codepath in git\nproper (I do not know about third-party tools and scripts around the\nfringe, though) that overwrites working tree files instead of\nwriting into new files and replacing them.\n\nIf there is a hardlink to outside the working tree of a tracked\npath, and \"git reset --hard [<committish>]\" needs to modify the\ncontents of that tracked path because it does not match what is\nrecorded in the committish, I think it is the right thing to severe\nthe link and leave the path outside the working tree intact. If we\ninstead overwrote, we will damage \"the other file\" that shares the\nhardlink, which is outside the working tree hence outside our\ncontrol.\n\nAlso, if two paths inside a working tree are made into hardlinks to\neach other, whenever Git needs to update either of them, we would\nsevere the link, i.e. not ovewrite but replace, and it is the right\nthing to do, since Git trakcs these two paths as two separate things\n(i.e. the index has one entry for each of these paths).\n"},{"id":"446543","messageId":"CABrKpmCt3zKONLp5ZjV1PxLyfM-koc=tKopKUUpx4nF2n_eo_w@mail.gmail.com","threadId":"57271","inReplyTo":"xmqqilufl5cv.fsf@gitster.g","subject":"Re: A puzzle: reset --hard and hard links","fromName":"Michael Herrmann","fromEmail":"michael@herrmann.io","sentAt":"2022-01-20T08:59:02Z","receivedAt":"2022-01-20T08:59:23Z","isPatch":false,"sender":{"key":"michael@herrmann.io","avatar":null},"body":"Thank you for your replies. Is there a way to tell Git not to sever\nhard links as highlighted by my example?\n"},{"id":"446604","messageId":"YengSfSDzVzvrJ6f@camp.crustytoothpaste.net","threadId":"57271","inReplyTo":"CABrKpmCt3zKONLp5ZjV1PxLyfM-koc=tKopKUUpx4nF2n_eo_w@mail.gmail.com","subject":"Re: A puzzle: reset --hard and hard links","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-01-20T22:20:57Z","receivedAt":"2022-01-20T22:21:03Z","isPatch":false,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2022-01-20 at 08:59:02, Michael Herrmann wrote:\n> Thank you for your replies. Is there a way to tell Git not to sever\n> hard links as highlighted by my example?\n\nNo, there isn't.  If you need to deal with files that should be linked\nand stored in a Git repository, your best bet is symlinks.\n-- \nbrian m. carlson (he/him or they/them)\nToronto, Ontario, CA\n"},{"id":"446630","messageId":"CABrKpmASHgBwPYgKnO2ZZRVVxMti=NFaxw6cBV=pst0xpVZYGA@mail.gmail.com","threadId":"57271","inReplyTo":"YengSfSDzVzvrJ6f@camp.crustytoothpaste.net","subject":"Re: A puzzle: reset --hard and hard links","fromName":"Michael Herrmann","fromEmail":"michael@herrmann.io","sentAt":"2022-01-21T12:50:55Z","receivedAt":"2022-01-21T12:51:20Z","isPatch":false,"sender":{"key":"michael@herrmann.io","avatar":null},"body":"On Thu, 20 Jan 2022 at 19:21, brian m. carlson\n<sandals@crustytoothpaste.net> wrote:\n> No, there isn't.  If you need to deal with files that should be linked\n> and stored in a Git repository, your best bet is symlinks.\n\nOkay, thank you. I unfortunately do not have a choice over which kind\nof link (soft or hard) is used. I will find a workaround.\n"},{"id":"446748","messageId":"CABrKpmBFrrWgBh7QAOX35zQr_e+LC1E6Jn5FKb_XP-7bew9Hkg@mail.gmail.com","threadId":"57271","inReplyTo":"CABrKpmASHgBwPYgKnO2ZZRVVxMti=NFaxw6cBV=pst0xpVZYGA@mail.gmail.com","subject":"Re: A puzzle: reset --hard and hard links","fromName":"Michael Herrmann","fromEmail":"michael@herrmann.io","sentAt":"2022-01-24T13:48:27Z","receivedAt":"2022-01-24T13:48:47Z","isPatch":false,"sender":{"key":"michael@herrmann.io","avatar":null},"body":"I now believe this is a bug in Git because calling `git status` fixes\nthe problem.\n\n1) Create a hard link to a file in a Git repo.\n2) Call `git status`.\n3) Call `git reset --hard`.\n\nWithout 2), Git severs the hard link. With 2), the hard link is preserved.\n\nReproduction steps (\"Test script\") and further info:\nhttps://github.com/brave/brave-browser/issues/20316#issuecomment-1020054082\n\nThis occurs for me on Debian 10 & 11, as well as Git 2.30.2 and 2.20.1.\n"},{"id":"446765","messageId":"xmqqk0ep57ou.fsf@gitster.g","threadId":"57271","inReplyTo":"CABrKpmBFrrWgBh7QAOX35zQr_e+LC1E6Jn5FKb_XP-7bew9Hkg@mail.gmail.com","subject":"Re: A puzzle: reset --hard and hard links","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-01-24T18:07:13Z","receivedAt":"2022-01-24T18:07:25Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Michael Herrmann <michael@herrmann.io> writes:\n\n> I now believe this is a bug in Git because calling `git status` fixes\n> the problem.\n>\n> 1) Create a hard link to a file in a Git repo.\n> 2) Call `git status`.\n> 3) Call `git reset --hard`.\n\nIt is merely because you helped Git to realize that there is no need\nto change the contents of hte file with \"reset --hard\".\n\nWith another step 1.5 \"append a line to the file in question\", git\nshould severe the link, I would think, as at that point, to revert\nthe contents of the file in question to its pristine state, it needs\nto modify it.\n\n\n\n\n\n\n"},{"id":"446767","messageId":"CABrKpmB7UEGzLCiNHQtY5-Dt16jLkpcpBEx3o8y9OBGZ418keA@mail.gmail.com","threadId":"57271","inReplyTo":"xmqqk0ep57ou.fsf@gitster.g","subject":"Re: A puzzle: reset --hard and hard links","fromName":"Michael Herrmann","fromEmail":"michael@herrmann.io","sentAt":"2022-01-24T18:16:43Z","receivedAt":"2022-01-24T18:17:03Z","isPatch":false,"sender":{"key":"michael@herrmann.io","avatar":null},"body":"> It is merely because you helped Git to realize\n\nI don't want to \"help Git realize\". I'm sorry but in my opinion `git\nstatus` should not have any effects on other commands. I don't\nunderstand how you can argue that calling `git status` is a valid fix\nto \"help Git\".\n\n> With another step 1.5 \"append a line to the file in question\", git\n> should severe the link,\n\nI don't want to sever the hard link. I want to avoid that it gets severed.\n\nOn Mon, 24 Jan 2022 at 15:07, Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Michael Herrmann <michael@herrmann.io> writes:\n>\n> > I now believe this is a bug in Git because calling `git status` fixes\n> > the problem.\n> >\n> > 1) Create a hard link to a file in a Git repo.\n> > 2) Call `git status`.\n> > 3) Call `git reset --hard`.\n>\n> It is merely because you helped Git to realize that there is no need\n> to change the contents of hte file with \"reset --hard\".\n>\n> With another step 1.5 \"append a line to the file in question\", git\n> should severe the link, I would think, as at that point, to revert\n> the contents of the file in question to its pristine state, it needs\n> to modify it.\n>\n>\n>\n>\n>\n>\n"},{"id":"446808","messageId":"xmqqfspc3k8k.fsf@gitster.g","threadId":"57271","inReplyTo":"CABrKpmB7UEGzLCiNHQtY5-Dt16jLkpcpBEx3o8y9OBGZ418keA@mail.gmail.com","subject":"Re: A puzzle: reset --hard and hard links","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-01-24T21:19:07Z","receivedAt":"2022-01-24T21:47:28Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Michael Herrmann <michael@herrmann.io> writes:\n\n>> It is merely because you helped Git to realize\n>\n> I don't want to \"help Git realize\". I'm sorry but in my opinion `git\n> status` should not have any effects on other commands. I don't\n> understand how you can argue that calling `git status` is a valid fix\n> to \"help Git\".\n>\n>> With another step 1.5 \"append a line to the file in question\", git\n>> should severe the link,\n>\n> I don't want to sever the hard link. I want to avoid that it gets severed.\n\nSorry, if that is the case, what you want is not a version control,\nand it is certainly not Git.  You want something else.\n\nThink about this scenario.\n\n    $ rm -fr one && git init one && cd one\n    $ echo 0 >a; echo 0 >b; git add a b; git commit -m zero\n\nWe have two files, a and b, each of which has \"0\" in it.\n\n    $ echo 1 >b; git add b; git commit -m one\n\nNow they have \"0\" and \"1\" respectively.\n\n    $ echo 2 >a; echo 2 >b; git commit -a -m two\n    $ ln -f a b\n    $ git diff\n    $ git diff HEAD\n\nNow they have \"2\".  Since they have identical contents, \"diff\" would\nreport no difference relative to the index or HEAD, even after we\nmanually break the working tree by making one of them a hardlink to\nthe other.\n\nNow, what should this command do?\n\n    $ git reset --hard HEAD^\n\nWhat the user is asking is (1) to move the branch to point at the\nprevious commit, which had 0 and 1 in a and b respectively, and (2)\nto make sure that the index and the working tree contents match what\nis recorded in the commit.\n\nSo for Git to be a usefully correct version control system, it is\nessential to make sure what it writes out would not affect any path\nother than the one it is writing out.  When it writes \"0\" to \"a\", it\nMUST break the hardlink from elsewhere that points at \"a\" before it\ndoes so.  Otherwise, the \"0\" it writes into \"a\" will also be seen\nelsewhere, which is not what the updated HEAD (i.e. commit \"one\")\nwants to see.  The same for \"b\" when it is updated from \"2\" to \"1\"\nwhen this happens.\n\n"},{"id":"446816","messageId":"000e01d81170$5f62f600$1e28e200$@nexbridge.com","threadId":"57271","inReplyTo":"xmqqfspc3k8k.fsf@gitster.g","subject":"RE: A puzzle: reset --hard and hard links","fromName":"","fromEmail":"rsbecker@nexbridge.com","sentAt":"2022-01-24T22:18:48Z","receivedAt":"2022-01-24T22:34:06Z","isPatch":false,"sender":{"key":"randall.becker@nexbridge.ca","avatar":"https://avatars.githubusercontent.com/u/28956764?v=4"},"body":"On January 24, 2022 4:19 PM, Junio wrote:\n> Michael Herrmann <michael@herrmann.io> writes:\n> \n> >> It is merely because you helped Git to realize\n> >\n> > I don't want to \"help Git realize\". I'm sorry but in my opinion `git\n> > status` should not have any effects on other commands. I don't\n> > understand how you can argue that calling `git status` is a valid fix\n> > to \"help Git\".\n> >\n> >> With another step 1.5 \"append a line to the file in question\", git\n> >> should severe the link,\n> >\n> > I don't want to sever the hard link. I want to avoid that it gets\nsevered.\n> \n> Sorry, if that is the case, what you want is not a version control, and it\nis certainly\n> not Git.  You want something else.\n> \n> Think about this scenario.\n> \n>     $ rm -fr one && git init one && cd one\n>     $ echo 0 >a; echo 0 >b; git add a b; git commit -m zero\n> \n> We have two files, a and b, each of which has \"0\" in it.\n> \n>     $ echo 1 >b; git add b; git commit -m one\n> \n> Now they have \"0\" and \"1\" respectively.\n> \n>     $ echo 2 >a; echo 2 >b; git commit -a -m two\n>     $ ln -f a b\n>     $ git diff\n>     $ git diff HEAD\n> \n> Now they have \"2\".  Since they have identical contents, \"diff\" would\nreport no\n> difference relative to the index or HEAD, even after we manually break the\n> working tree by making one of them a hardlink to the other.\n> \n> Now, what should this command do?\n> \n>     $ git reset --hard HEAD^\n> \n> What the user is asking is (1) to move the branch to point at the previous\ncommit,\n> which had 0 and 1 in a and b respectively, and (2) to make sure that the\nindex and\n> the working tree contents match what is recorded in the commit.\n> \n> So for Git to be a usefully correct version control system, it is\nessential to make\n> sure what it writes out would not affect any path other than the one it is\nwriting\n> out.  When it writes \"0\" to \"a\", it MUST break the hardlink from elsewhere\nthat\n> points at \"a\" before it does so.  Otherwise, the \"0\" it writes into \"a\"\nwill also be\n> seen elsewhere, which is not what the updated HEAD (i.e. commit \"one\")\nwants to\n> see.  The same for \"b\" when it is updated from \"2\" to \"1\"\n> when this happens.\n\nI think there are more use cases here than are apparent but also some\nserious question about why one would do this.\n\nIn a Linux/POSIX environment, one can do a hard link to a file inside a git\nrepo, change the file using something like vim, and have git recognize that\nthere is a change in git status. However, this only works on some platforms.\nHard links do not have a 100% consistent semantic from one OS to another,\none file system to another, or even between editors and scripts. As Junio\npointed out, using a > operator on a hard link is likely going to replace\nthe file instead of modifying the existing one. >> might correctly append\nand have git recognize it in a git status... on some platforms.\n\nMaking git consistent in this situation across every possible situation is\nnot only impractical, it is likely impossible and cutting down what git is\nallowed to do so that only those common things are implemented might gut git\nbadly.\n\nIf you are looking for doing external edits while keeping git notified, I\nwould suggest wrapping the file modification in a script that is aware of\nhard links so that you get the results you want.\n\nAlternatively, a soft link made externally to a physical git file location\nmight do what you want - assuming your platform supports soft links. Your\nmodification script/program/etc. would use the file directly in git instead\nof the hard link inode, so git is happy. A git status would see the change\nbecause the file really only exists in git. Other git operations, like\nrestore, switch, etc., would cause the physical file to be modified\ncorrectly, and anything using the referencing soft link would see the\nchange. Note: I am not suggest soft-linking from inside git to outside.\n\n--Randall\n\n"},{"id":"446824","messageId":"CABrKpmDjrTPhL_55YaXEAVTEmu8iZEsKUJYab7OgK0=w9d_7MA@mail.gmail.com","threadId":"57271","inReplyTo":"xmqqfspc3k8k.fsf@gitster.g","subject":"Re: A puzzle: reset --hard and hard links","fromName":"Michael Herrmann","fromEmail":"michael@herrmann.io","sentAt":"2022-01-24T21:50:49Z","receivedAt":"2022-01-25T02:08:10Z","isPatch":false,"sender":{"key":"michael@herrmann.io","avatar":null},"body":"Thank you for your explanations Junio. This is the first part where I differ:\n\n> $ ln -f a b\n\nMy hard link is outside the repo. In your example, it makes sense that\nGit has to sever the hard link to be able to give the files different\ncontents. In my case and example, this complication is not present.\nAnd it does not address the main point:\n\nMy working tree is clean. `git reset --hard HEAD` (not HEAD^ like you\nhad) should not do anything.\n\nFinally, your (kind!) explanation does not give a reason why calling\n`git status` should change the behavior that Git unnecessarily severs\nthe hard link.\n\nMy suspicion is that Git keeps a cache of the stat(...) result of\nfiles. An additional hard link increases the .st_nlink count of this\nstruct. `git reset` compares the cached stat(...) values to the actual\nones and sees that one has changed. `git status` does the same but is\nsmart enough to realize that the additional hard link does not change\nanything. It writes this to the cache. `git reset` should also be\nsmart!\n"},{"id":"446833","messageId":"875yq842to.fsf@igel.home","threadId":"57271","inReplyTo":"CABrKpmDjrTPhL_55YaXEAVTEmu8iZEsKUJYab7OgK0=w9d_7MA@mail.gmail.com","subject":"Re: A puzzle: reset --hard and hard links","fromName":"Andreas Schwab","fromEmail":"schwab@linux-m68k.org","sentAt":"2022-01-25T08:49:55Z","receivedAt":"2022-01-25T08:55:17Z","isPatch":false,"sender":{"key":"schwab@linux-m68k.org","avatar":"https://avatars.githubusercontent.com/u/2175493?v=4"},"body":"On Jan 24 2022, Michael Herrmann wrote:\n\n> My suspicion is that Git keeps a cache of the stat(...) result of\n> files. An additional hard link increases the .st_nlink count of this\n> struct. `git reset` compares the cached stat(...) values to the actual\n> ones and sees that one has changed. `git status` does the same but is\n> smart enough to realize that the additional hard link does not change\n> anything. It writes this to the cache. `git reset` should also be\n> smart!\n\nSee the core.trustctime config.\n\n-- \nAndreas Schwab, schwab@linux-m68k.org\nGPG Key fingerprint = 7578 EB47 D4E5 4D69 2510  2552 DF73 E780 A9DA AEC1\n\"And now for something completely different.\"\n"},{"id":"446840","messageId":"220125.865yq8ghae.gmgdl@evledraar.gmail.com","threadId":"57271","inReplyTo":"CABrKpmDjrTPhL_55YaXEAVTEmu8iZEsKUJYab7OgK0=w9d_7MA@mail.gmail.com","subject":"Re: A puzzle: reset --hard and hard links","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-01-25T11:33:56Z","receivedAt":"2022-01-25T12:00:54Z","isPatch":false,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Jan 24 2022, Michael Herrmann wrote:\n\n> Thank you for your explanations Junio. This is the first part where I differ:\n>\n>> $ ln -f a b\n>\n> My hard link is outside the repo. In your example, it makes sense that\n> Git has to sever the hard link to be able to give the files different\n> contents. In my case and example, this complication is not present.\n> And it does not address the main point:\n>\n> My working tree is clean. `git reset --hard HEAD` (not HEAD^ like you\n> had) should not do anything.\n>\n> Finally, your (kind!) explanation does not give a reason why calling\n> `git status` should change the behavior that Git unnecessarily severs\n> the hard link.\n>\n> My suspicion is that Git keeps a cache of the stat(...) result of\n> files. An additional hard link increases the .st_nlink count of this\n> struct. `git reset` compares the cached stat(...) values to the actual\n> ones and sees that one has changed. `git status` does the same but is\n> smart enough to realize that the additional hard link does not change\n> anything. It writes this to the cache. `git reset` should also be\n> smart!\n\nWhat you're observing is that we tweak the index when various commands\nare run, some of that is documented, and others we consider purely\nimplementation details. Whether we sever a hard link relationship is\ndefinitely on the \"implementation detail\" side of that.\n\nI.e. that you can observe a behavior difference here doesn't mean that\nit's a bug, it means that you're poking at behavior that was never\nsupposed to work this way, or be stable.\n\nThat being said I don't see a reason for why we shouldn't ever support\nwhat you're requesting here in some way. E.g. when we spin up different\na different 'git worktree' on the same storage we could optionally\nhardlink to an existing checkout to save space.\n\nThis would be useful e.g. for spinning up a bunch of trees to run\ncompilations on, where much of the checkout tree will be duplicated.\n\nAnd this probably won't match your use-case, but I wonder how far you\ncould get with the post-checkout hook, i.e. to have it run around after\na checkout and fix up things that aren't hard links to be hardlinked\nappropriately.\n\nI don't know of a tool to take two directories and hardlink things where\npossible, but it wouldn't be hard to write. I thought rsync could, but\nit appears just to support copying things as hardlink, not \"fixing\"\nfiles with the same content to be hardlinks after the fact (but maybe\nI've just missed a way to operate it).\n"},{"id":"446846","messageId":"87pmog2bbf.fsf@igel.home","threadId":"57271","inReplyTo":"220125.865yq8ghae.gmgdl@evledraar.gmail.com","subject":"Re: A puzzle: reset --hard and hard links","fromName":"Andreas Schwab","fromEmail":"schwab@linux-m68k.org","sentAt":"2022-01-25T13:29:24Z","receivedAt":"2022-01-25T13:32:09Z","isPatch":false,"sender":{"key":"schwab@linux-m68k.org","avatar":"https://avatars.githubusercontent.com/u/2175493?v=4"},"body":"On Jan 25 2022, Ævar Arnfjörð Bjarmason wrote:\n\n> I don't know of a tool to take two directories and hardlink things where\n> possible, but it wouldn't be hard to write.\n\nhttps://github.com/adrianlopezroche/fdupes\n\n-- \nAndreas Schwab, schwab@linux-m68k.org\nGPG Key fingerprint = 7578 EB47 D4E5 4D69 2510  2552 DF73 E780 A9DA AEC1\n\"And now for something completely different.\"\n"},{"id":"446848","messageId":"CABrKpmAcNsexcmHK5kZvZr_NBm7TWWU=wVQExHwWSPpprptdQw@mail.gmail.com","threadId":"57271","inReplyTo":"87pmog2bbf.fsf@igel.home","subject":"Re: A puzzle: reset --hard and hard links","fromName":"Michael Herrmann","fromEmail":"michael@herrmann.io","sentAt":"2022-01-25T14:30:11Z","receivedAt":"2022-01-25T14:36:46Z","isPatch":false,"sender":{"key":"michael@herrmann.io","avatar":null},"body":"Andreas Schwab wrote:\n\n> See the core.trustctime config.\n\nThis sounds very promising. It also fixes the problem in my\npreliminary tests. Are there known drawbacks to changing this setting\nto false? I haven't yet noticed a performance impact.\n\nRandall wrote:\n\n> I think there are more use cases here than are apparent\n\nThe use case is Chromium's build process. It creates hard links from a\nsrc/... to a gen/... directory. Some actions do `git reset --hard` in\nsrc/. This updates the modification time because of the hard links -\neven when there are no changes. That in turn leads to unnecessary\nrebuilds. I have little control over the creation of the hard links.\n\nÆvar, everything you wrote is very interesting and helpful. Thank you!\n"},{"id":"446911","messageId":"YfCue6m4gqGEWqDj@camp.crustytoothpaste.net","threadId":"57271","inReplyTo":"CABrKpmAcNsexcmHK5kZvZr_NBm7TWWU=wVQExHwWSPpprptdQw@mail.gmail.com","subject":"Re: A puzzle: reset --hard and hard links","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-01-26T02:14:19Z","receivedAt":"2022-01-26T02:14:24Z","isPatch":false,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2022-01-25 at 14:30:11, Michael Herrmann wrote:\n> Andreas Schwab wrote:\n> \n> > See the core.trustctime config.\n> \n> This sounds very promising. It also fixes the problem in my\n> preliminary tests. Are there known drawbacks to changing this setting\n> to false? I haven't yet noticed a performance impact.\n\nThe index holds a bunch of information on files and checks that\ninformation to see whether the file has changed.  If the stat\ninformation changes, then the file is marked dirty and may be re-read.\nThe option above and core.checkStat control which information is\nincluded.\n\nWhen you do \"git reset --hard\" and there's no change to the file in the\nindex, Git happens not to rewrite it in the working tree.  This is an\nimplementation detail and isn't guaranteed, but that's why things happen\nto be working here.\n\nThe downside to restricting what's in the index is that Git can miss\nchanges.  For example, with core.trustctime turned off, a program that\nmodifies a file without changing its size but resets it to have the same\nmtime would probably result in Git missing those changes.  It shouldn't\nresult in a performance difference, but it could theoretically result in\na correctness difference.  You may decide the tradeoff is worth it,\nhowever.\n\n> Randall wrote:\n> \n> > I think there are more use cases here than are apparent\n> \n> The use case is Chromium's build process. It creates hard links from a\n> src/... to a gen/... directory. Some actions do `git reset --hard` in\n> src/. This updates the modification time because of the hard links -\n> even when there are no changes. That in turn leads to unnecessary\n> rebuilds. I have little control over the creation of the hard links.\n\nThanks, this is helpful context and it explains why you'd want this\nbehavior.  If you're involved with the project, it may be helpful to\npoint out to other project members that this occurs and suggest that the\nscripts avoid running \"git reset --hard\".  For example, it may be easy\nto avoid if \"git status --porcelain\" produces empty output.  I've heard\nstories about Chromium's build times and I'm sure such an optimization\nwould be welcome.\n-- \nbrian m. carlson (he/him or they/them)\nToronto, Ontario, CA\n"},{"id":"446984","messageId":"xmqqsftas5c8.fsf@gitster.g","threadId":"57271","inReplyTo":"YfCue6m4gqGEWqDj@camp.crustytoothpaste.net","subject":"Re: A puzzle: reset --hard and hard links","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-01-26T18:46:15Z","receivedAt":"2022-01-26T18:46:23Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"brian m. carlson\" <sandals@crustytoothpaste.net> writes:\n\n> Thanks, this is helpful context and it explains why you'd want this\n> behavior.  If you're involved with the project, it may be helpful to\n> point out to other project members that this occurs and suggest that the\n> scripts avoid running \"git reset --hard\".  For example, it may be easy\n> to avoid if \"git status --porcelain\" produces empty output.  I've heard\n> stories about Chromium's build times and I'm sure such an optimization\n> would be welcome.\n\nI am not sure about that.  If the ONLY problem is that hardlinks to\nUNMODIFIED paths are severed by \"reset --hard\" when it is not necessary\nin order to ensure that HEAD and the working tree matches in content\nwithout clobbering anything unrelated, then adding an internal call\nto refresh before \"git reset --hard\" would neatly solve it, and\nthere should not be a need for end-user workaround like that.\n\nBut it does not change the fact that we try to avoid clobbering\nanything unrelated to the path we are updating when we need to\nupdate the contents of the working tree files, and the way we do so\nis to call checkout_entry(), which does unlink() followed by\ncreat().  So even though you may be able to teach \"git reset --hard\"\nto refrain from severing extra hardlinks when it does not have to,\nit will do so when the contents of the path must be changed.\n\nTo be quite honest, I am not sure if the patch below is safe either.\nI doubt that the lack of \"update-index --refresh\" in the \"reset\n--hard\" command was a mistake; rather, I suspect that it was\ndeliberately omitted to avoid some problems, which I do not offhand\nrecall.\n\n builtin/reset.c | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git c/builtin/reset.c w/builtin/reset.c\nindex b97745ee94..8adc1be75b 100644\n--- c/builtin/reset.c\n+++ w/builtin/reset.c\n@@ -83,6 +83,7 @@ static int reset_index(const char *ref, const struct object_id *oid, int reset_t\n \t}\n \n \tread_cache_unmerged();\n+\trefresh_cache(REFRESH_QUIET);\n \n \tif (reset_type == KEEP) {\n \t\tstruct object_id head_oid;\n"}]}