{"thread":{"id":"57282","subject":"Strange checkout with GIT_WORK_TREE","startedAt":"2022-01-21T16:38:11Z","lastAt":"2022-01-22T09:07:12Z","messageCount":4,"participants":["Lauri Rooden","brian m. carlson","Junio C Hamano"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"446632","messageId":"CAHqym3xs_M7BvvFDq2pHM-+DgK_nJcBakVEBL-GiNwnCRzMwWA@mail.gmail.com","threadId":"57282","inReplyTo":null,"subject":"Strange checkout with GIT_WORK_TREE","fromName":"Lauri Rooden","fromEmail":"lauri@rooden.ee","sentAt":"2022-01-21T16:37:58Z","receivedAt":"2022-01-21T16:38:11Z","isPatch":false,"sender":{"key":"lauri@rooden.ee","avatar":null},"body":"Does the GIT_WORK_TREE get lost on the middle of process\nor I am misunderstand the git checkout?\n\nWhat did you do before the bug happened? (Steps to reproduce your issue)\n- I wrote a shell script to reproduce\n\n```git-test.sh\nGIT_ROOT=$(mktemp -d)\nGIT_COPY=$(mktemp -d)\n\necho \"Create git repo with two commits: $GIT_ROOT\"\ncd $GIT_ROOT\ngit init\necho 1 > a.txt\necho 1 > b.txt\ngit add *.txt\ngit commit -m \"Initial commit\"\necho 2 > b.txt\ngit add b.txt\ngit commit -m \"Second commit\"\n\necho \"Checkout to other work-tree: $GIT_COPY\"\nGIT_WORK_TREE=$GIT_COPY git checkout HEAD~1\ngit status\n\necho \"ORIGIN $GIT_ROOT\"\nls -la $GIT_ROOT\necho \"COPY $GIT_COPY\"\nls -la $GIT_COPY\n```\n\nWhat did you expect to happen? (Expected behavior)\n- a.txt and b.txt checkouted to $GIT_COPY both with content `1`\n- current folder unchanged\n\nWhat happened instead? (Actual behavior)\n- only b.txt checkouted to $GIT_COPY\n- HEAD~1 checkouted in current folder but folder content remains HEAD\n(modified:   b.txt)\n\n\n[System Info]\ngit version:\ngit version 2.34.1\ncpu: x86_64\nno commit associated with this build\nsizeof-long: 8\nsizeof-size_t: 8\nshell-path: /bin/sh\nuname: Linux 5.16.0-arch1-1 #1 SMP PREEMPT Mon, 10 Jan 2022 20:11:47\n+0000 x86_64\ncompiler info: gnuc: 11.1\nlibc info: glibc: 2.33\n$SHELL (typically, interactive shell): /usr/bin/bash\n\n\n[Enabled Hooks]\nprepare-commit-msg\n"},{"id":"446657","messageId":"YesvAkBnxq3xrHTV@camp.crustytoothpaste.net","threadId":"57282","inReplyTo":"CAHqym3xs_M7BvvFDq2pHM-+DgK_nJcBakVEBL-GiNwnCRzMwWA@mail.gmail.com","subject":"Re: Strange checkout with GIT_WORK_TREE","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2022-01-21T22:09:06Z","receivedAt":"2022-01-21T22:09:13Z","isPatch":false,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2022-01-21 at 16:37:58, Lauri Rooden wrote:\n> Does the GIT_WORK_TREE get lost on the middle of process\n> or I am misunderstand the git checkout?\n> \n> What did you do before the bug happened? (Steps to reproduce your issue)\n> - I wrote a shell script to reproduce\n> \n> ```git-test.sh\n> GIT_ROOT=$(mktemp -d)\n> GIT_COPY=$(mktemp -d)\n> \n> echo \"Create git repo with two commits: $GIT_ROOT\"\n> cd $GIT_ROOT\n> git init\n> echo 1 > a.txt\n> echo 1 > b.txt\n> git add *.txt\n> git commit -m \"Initial commit\"\n> echo 2 > b.txt\n> git add b.txt\n> git commit -m \"Second commit\"\n> \n> echo \"Checkout to other work-tree: $GIT_COPY\"\n> GIT_WORK_TREE=$GIT_COPY git checkout HEAD~1\n> git status\n> \n> echo \"ORIGIN $GIT_ROOT\"\n> ls -la $GIT_ROOT\n> echo \"COPY $GIT_COPY\"\n> ls -la $GIT_COPY\n> ```\n> \n> What did you expect to happen? (Expected behavior)\n> - a.txt and b.txt checkouted to $GIT_COPY both with content `1`\n> - current folder unchanged\n> \n> What happened instead? (Actual behavior)\n> - only b.txt checkouted to $GIT_COPY\n> - HEAD~1 checkouted in current folder but folder content remains HEAD\n\nHere's what I believe is happening here.  When you run \"git checkout\nHEAD~1\", Git notices that the only file that's stale in the index\ncompared to what's already present is b.txt; a.txt is up to date.  As\nsuch, it only writes one file into the working tree, since only one file\nneeds to be updated.  This is an optimization, since for large working\ntrees, writing out every file every time would be extremely expensive.\n\nI don't know what our official position is on switching working trees\nlike this. I would generally recommend you pick one and stick to it, but\nif you want to do this, you'll need to update the index for the working\ntree first.  You can do that by something like this in place of your\ncheckout:\n\n  GIT_WORK_TREE=$GIT_COPY git update-index --refresh\n  GIT_WORK_TREE=$GIT_COPY git checkout -f HEAD~1\n\nThat will inform Git that your working copy is stale and then the -f\nflag to checkout will force Git to overwrite the missing files.\n\nIf your goal is to have multiple worktrees for one repository, you can\ndo that with \"git worktree\", which will keep separate indices for your\nseparate directories, provided they're on separate branches.  If you\njust want to create a detached copy of the files for a repository from a\ncommit, you can do this:\n\n  git archive HEAD~1 | tar -C \"$GIT_COPY\" -xf -\n\nHopefully that explains what's going on.  If you tell us a little bit\nmore about what you wanted to accomplish, we may be able to help you\nfind a way to do it that provides results that are at least less\nsurprising and more predictable.\n-- \nbrian m. carlson (he/him or they/them)\nToronto, Ontario, CA\n"},{"id":"446659","messageId":"xmqqv8yceldw.fsf@gitster.g","threadId":"57282","inReplyTo":"CAHqym3xs_M7BvvFDq2pHM-+DgK_nJcBakVEBL-GiNwnCRzMwWA@mail.gmail.com","subject":"Re: Strange checkout with GIT_WORK_TREE","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-01-21T23:09:47Z","receivedAt":"2022-01-21T23:09:53Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Lauri Rooden <lauri@rooden.ee> writes:\n\n> echo \"Checkout to other work-tree: $GIT_COPY\"\n> GIT_WORK_TREE=$GIT_COPY git checkout HEAD~1\n\nIn this particular case, it happens that it does not make a\ndifference, but it is discouraged to set GIT_WORK_TREE without\nsetting GIT_DIR.\n\n    GIT_DIR was invented as a way to say \"I am at the top of the\n    working tree but I do not have .git/ directory here---it is at\n    that other place, i.e. $GIT_DIR\".  And this was OK if you worked\n    only at the top of the working tree.  GIT_WORK_TREE was then\n    invented to help folks who do not always work at the top of the\n    working tree.  With that, they can still set GIT_DIR to point at\n    the location of the repository proper, use GIT_WORK_TREE to\n    point at the top of the working tree, and chdir freely to any\n    subdirectory of $GIT_WORK_TREE and expect things to work.\n\nSo, the more kosher way to do the above is\n\n\techo \"checkout to other work-tree: $GIT_COPY\"\n\tcd \"$GIT_COPY\"\n\tGIT_DIR=\"$GIT_ROOT/.git\" git checkout HEAD~\n\nbut what you wrote also happens to the same thing.  We auto-discover\nthe git directory (i.e. the current directory has .git/ directory in\nit, and that is used as the GIT_DIR).\n\nNow, what you observed is totally expected.\n\nBecause HEAD~ in \"git checkout HEAD~\" is *not* a branch name, it is\na short-hand of \"git checkout --detach HEAD~\".  It is \"I want to\nswitch to the named commit, not on any branch.  Note that I may have\nlocal changes, and please carry them when you switch to the commit\".\n\nAnd when you run the above \"git checkout\", do you have local\nchanges?  Yes, you do.  Since\n\n * Your GIT_DIR is \"$GIT_ROOT/.git\"; your HEAD has two files, a.txt\n   and b.txt, with 1 and 2 in it, respectively.  They match the\n   index in that GIT_DIR.\n\n * Your working tree is \"$GIT_COPY\".  You do not have a.txt and\n   b.txt.  I.e. your local change is to remove a.txt and b.txt\n\nNow you want to move to the state of HEAD~, where each of a.txt and\nb.txt has 1 in it.  So, if there weren't any local change, switching\nto this commit would change a.txt and b.txt to have 1 in them.\n\nNow between HEAD and HEAD~, there was no change to a.txt; so we\nhonor your local change to REMOVE a.txt.  That is why you do not\nhave \"$GIT_COPY/a.txt\" in the result.\n\nBut b.txt is different between HEAD and HEAD~.  Pedantically, this\n*should* result in \"the difference between two commits and your\nlocal change conflicts\", and make the \"checkout\" command fail\nwithout doing anything.  But there is an ancient special case to\nhelp working in a sparsely populated working tree that allows Git\nto treat \"missing\" file as the same as \"unchanged\" file, and I think\nthat is what is kicking in.  Instead of treating your \"deletion\" as\nsomething that conflicts with the change between HEAD and HEAD~ to\nturn 2 to 1, the command pretends as if you left b.txt as-is, and\nlet you switch to HEAD~, instread of failing.\n\nIn any case, if you wanted to make a copy of a different commit into\na separate directory, use of GIT_DIR/GIT_WORK_TREE is totally a\nwrong way to do so.  If you go and look at $GIT_ROOT after your\nswitching to \"HEAD~\", $GIT_ROOT/.git/HEAD and $GIT_ROOT/.git/index\nhave moved to the \"Initial\" commit, but your working tree files in\n$GIT_ROOT are left as before, which would totally be confusing.\n\nUsing \"git worktree\" to create $GIT_COPY as an additional worktree,\nor \"git clone\" to create $GIT_COPY as an additional repository, may\nbe what you are looking for.\n\n\n\n\n\n"},{"id":"446684","messageId":"CAHqym3zW1U5ovFYCzhZesorR5EcF6TS8mUuMmSwUNPiUZX=N5A@mail.gmail.com","threadId":"57282","inReplyTo":"xmqqv8yceldw.fsf@gitster.g","subject":"Re: Strange checkout with GIT_WORK_TREE","fromName":"Lauri Rooden","fromEmail":"lauri@rooden.ee","sentAt":"2022-01-22T09:06:58Z","receivedAt":"2022-01-22T09:07:12Z","isPatch":false,"sender":{"key":"lauri@rooden.ee","avatar":null},"body":"Thank you Brian and Junio for the extensive explanation!\n\nNow all makes sense and the new (for me) \"git worktree\" feature seems promising.\n\nI wanted to have $GIT_COPY without .git folder but a .git file does\nnot bother me.\n\nPreviously I have checkouted one file this way and then it seemed to work,\nconfusion raised when I tried to switsh to full repo checkout.\n"}]}