{"thread":{"id":"54140","subject":"[PATCH 0/5] add \"git worktree repair\" command","startedAt":"2020-08-27T08:23:07Z","lastAt":"2020-08-31T19:07:12Z","messageCount":22,"participants":["Eric Sunshine","Junio C Hamano","Johannes Schindelin"],"isPatch":true,"patchVersion":1,"patchTotal":5},"messages":[{"id":"404588","messageId":"20200827082129.56149-1-sunshine@sunshineco.com","threadId":"54140","inReplyTo":null,"subject":"[PATCH 0/5] add \"git worktree repair\" command","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2020-08-27T08:21:24Z","receivedAt":"2020-08-27T08:23:07Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"The purpose of this patch series is twofold.\n\nFirst, it adds a \"git worktree repair\" command to help users recover\nfrom situations in which worktree administrative files can become\noutdated or corrupted due to external factors. Simple examples include\n(1) the user moving a worktree manually rather than via \"git worktree\nmove\" which causes the main worktree (or bare repository) to lose track\nof the worktree, and (2) moving the main worktree (or bare repository)\nwhich results in linked worktrees being unable to find the repository.\n\nSecond, it fixes two bugs with \"git init --separate-git-dir\" when linked\nworktrees are involved (reported by [1]), both of which cause worktree\nadministrative files to become outdated or corrupted.\n\nAn intentional side-effect of the --separate-git-dir fix in patch [5/5]\nis that it closes an additional loophole not covered by [2] which made\nit illegal to use --separate-git-dir in conjunction with bare\nrepositories. Peff is Cc:'d because he commented on that thread.\n\n[1]: https://lore.kernel.org/git/CAHbriek39i9NSHRw6DZm0dftk-GkeAYR74c0xyss0vbeDHu1Hw@mail.gmail.com/T/\n[2]: https://lore.kernel.org/git/20200809225316.19503-1-sunshine@sunshineco.com/T/\n\nEric Sunshine (5):\n  worktree: add skeleton \"repair\" command\n  worktree: teach \"repair\" to fix worktree back-links to main worktree\n  worktree: teach \"repair\" to fix outgoing links to worktrees\n  init: teach --separate-git-dir to repair linked worktrees\n  init: make --separate-git-dir work from within linked worktree\n\n Documentation/git-worktree.txt |  26 ++++-\n builtin/init-db.c              |  26 +++++\n builtin/worktree.c             |  29 ++++++\n t/t0001-init.sh                |  28 ++++++\n t/t2406-worktree-repair.sh     | 169 +++++++++++++++++++++++++++++++++\n worktree.c                     | 127 +++++++++++++++++++++++++\n worktree.h                     |  22 +++++\n 7 files changed, 425 insertions(+), 2 deletions(-)\n create mode 100755 t/t2406-worktree-repair.sh\n\n-- \n2.28.0.461.g40977abb40\n\n"},{"id":"404589","messageId":"20200827082129.56149-2-sunshine@sunshineco.com","threadId":"54140","inReplyTo":"20200827082129.56149-1-sunshine@sunshineco.com","subject":"[PATCH 1/5] worktree: add skeleton \"repair\" command","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2020-08-27T08:21:25Z","receivedAt":"2020-08-27T08:23:13Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"Worktree administrative files can become corrupted or outdated due to\nexternal factors. Although, it is often possible to recover from such\nsituations by hand-tweaking these files, doing so requires intimate\nknowledge of worktree internals. While information necessary to make\nsuch repairs manually can be obtained from git-worktree.txt and\ngitrepository-layout.txt, we can assist users more directly by teaching\ngit-worktree how to repair its administrative files itself (at least to\nsome extent). Therefore, add a \"git worktree repair\" command which\nattempts to correct common problems which may arise due to factors\nbeyond Git's control.\n\nAt this stage, the \"repair\" command is a mere skeleton; subsequent\ncommits will flesh out the functionality.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n Documentation/git-worktree.txt |  6 ++++++\n builtin/worktree.c             | 15 +++++++++++++++\n t/t2406-worktree-repair.sh     | 11 +++++++++++\n 3 files changed, 32 insertions(+)\n create mode 100755 t/t2406-worktree-repair.sh\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex 6ee6ec7982..ae432d39a8 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -15,6 +15,7 @@ SYNOPSIS\n 'git worktree move' <worktree> <new-path>\n 'git worktree prune' [-n] [-v] [--expire <expire>]\n 'git worktree remove' [-f] <worktree>\n+'git worktree repair'\n 'git worktree unlock' <worktree>\n \n DESCRIPTION\n@@ -110,6 +111,11 @@ and no modification in tracked files) can be removed. Unclean working\n trees or ones with submodules can be removed with `--force`. The main\n working tree cannot be removed.\n \n+repair::\n+\n+Repair working tree administrative files, if possible, if they have\n+become corrupted or outdated due to external factors.\n+\n unlock::\n \n Unlock a working tree, allowing it to be pruned, moved or deleted.\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 378f332b5d..88af412d4f 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -1030,6 +1030,19 @@ static int remove_worktree(int ac, const char **av, const char *prefix)\n \treturn ret;\n }\n \n+static int repair(int ac, const char **av, const char *prefix)\n+{\n+\tstruct option options[] = {\n+\t\tOPT_END()\n+\t};\n+\tint rc = 0;\n+\n+\tac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n+\tif (ac)\n+\t\tusage_with_options(worktree_usage, options);\n+\treturn rc;\n+}\n+\n int cmd_worktree(int ac, const char **av, const char *prefix)\n {\n \tstruct option options[] = {\n@@ -1056,5 +1069,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)\n \t\treturn move_worktree(ac - 1, av + 1, prefix);\n \tif (!strcmp(av[1], \"remove\"))\n \t\treturn remove_worktree(ac - 1, av + 1, prefix);\n+\tif (!strcmp(av[1], \"repair\"))\n+\t\treturn repair(ac - 1, av + 1, prefix);\n \tusage_with_options(worktree_usage, options);\n }\ndiff --git a/t/t2406-worktree-repair.sh b/t/t2406-worktree-repair.sh\nnew file mode 100755\nindex 0000000000..cc679e1a21\n--- /dev/null\n+++ b/t/t2406-worktree-repair.sh\n@@ -0,0 +1,11 @@\n+#!/bin/sh\n+\n+test_description='test git worktree repair'\n+\n+. ./test-lib.sh\n+\n+test_expect_success setup '\n+\ttest_commit init\n+'\n+\n+test_done\n-- \n2.28.0.461.g40977abb40\n\n"},{"id":"404590","messageId":"20200827082129.56149-6-sunshine@sunshineco.com","threadId":"54140","inReplyTo":"20200827082129.56149-1-sunshine@sunshineco.com","subject":"[PATCH 5/5] init: make --separate-git-dir work from within linked worktree","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2020-08-27T08:21:29Z","receivedAt":"2020-08-27T08:23:14Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"The intention of `git init --separate-work-dir=<path>` is to move the\n.git/ directory to a location outside of the main worktree. When used\nwithin a linked worktree, however, rather than moving the .git/\ndirectory as intended, it instead incorrectly moves the worktree's\n.git/worktrees/<id> directory to <path>, thus disconnecting the linked\nworktree from its parent repository and breaking the worktree in the\nprocess since its local .git file no longer points at a location at\nwhich it can find the object database. Fix this broken behavior.\n\nAn intentional side-effect of this change is that it also closes a\nloophole not caught by ccf236a23a (init: disallow --separate-git-dir\nwith bare repository, 2020-08-09) in which the check to prevent\n--separate-git-dir being used in conjunction with a bare repository was\nunable to detect the invalid combination when invoked from within a\nlinked worktree. Therefore, add a test to verify that this loophole is\nclosed, as well.\n\nReported-by: Henré Botha <henrebotha@gmail.com>\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n builtin/init-db.c | 24 ++++++++++++++++++++++++\n t/t0001-init.sh   | 21 +++++++++++++++++++--\n 2 files changed, 43 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/init-db.c b/builtin/init-db.c\nindex 7b915d88ab..6a94d20a2e 100644\n--- a/builtin/init-db.c\n+++ b/builtin/init-db.c\n@@ -642,6 +642,30 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)\n \tif (!git_dir)\n \t\tgit_dir = DEFAULT_GIT_DIR_ENVIRONMENT;\n \n+\t/*\n+\t * When --separate-git-dir is used inside a linked worktree, take\n+\t * care to ensure that the common .git/ directory is relocated, not\n+\t * the worktree-specific .git/worktrees/<id>/ directory.\n+\t */\n+\tif (real_git_dir) {\n+\t\tint err;\n+\t\tconst char *p;\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\n+\t\tp = read_gitfile_gently(git_dir, &err);\n+\t\tif (p && get_common_dir(&sb, p)) {\n+\t\t\tstruct strbuf mainwt = STRBUF_INIT;\n+\n+\t\t\tstrbuf_addbuf(&mainwt, &sb);\n+\t\t\tstrbuf_strip_suffix(&mainwt, \"/.git\");\n+\t\t\tif (chdir(mainwt.buf) < 0)\n+\t\t\t\tdie_errno(_(\"cannot chdir to %s\"), mainwt.buf);\n+\t\t\tstrbuf_release(&mainwt);\n+\t\t\tgit_dir = strbuf_detach(&sb, 0);\n+\t\t}\n+\t\tstrbuf_release(&sb);\n+\t}\n+\n \tif (is_bare_repository_cfg < 0)\n \t\tis_bare_repository_cfg = guess_repository_type(git_dir);\n \ndiff --git a/t/t0001-init.sh b/t/t0001-init.sh\nindex e489eb4ddb..2f7c3dcd0f 100755\n--- a/t/t0001-init.sh\n+++ b/t/t0001-init.sh\n@@ -329,6 +329,15 @@ test_expect_success 'implicit bare & --separate-git-dir incompatible' '\n \ttest_i18ngrep \"incompatible\" err\n '\n \n+test_expect_success 'bare & --separate-git-dir incompatible within worktree' '\n+\ttest_when_finished \"rm -rf bare.git linkwt seprepo\" &&\n+\ttest_commit gumby &&\n+\tgit clone --bare . bare.git &&\n+\tgit -C bare.git worktree add --detach ../linkwt &&\n+\ttest_must_fail git -C linkwt init --separate-git-dir seprepo 2>err &&\n+\ttest_i18ngrep \"incompatible\" err\n+'\n+\n test_lazy_prereq GETCWD_IGNORES_PERMS '\n \tbase=GETCWD_TEST_BASE_DIR &&\n \tmkdir -p $base/dir &&\n@@ -405,15 +414,23 @@ test_expect_success SYMLINKS 're-init to move gitdir symlink' '\n \ttest_path_is_dir realgitdir/refs\n '\n \n-test_expect_success 're-init to move gitdir with linked worktrees' '\n+sep_git_dir_worktree ()  {\n \ttest_when_finished \"rm -rf mainwt linkwt seprepo\" &&\n \tgit init mainwt &&\n \ttest_commit -C mainwt gumby &&\n \tgit -C mainwt worktree add --detach ../linkwt &&\n-\tgit -C mainwt init --separate-git-dir ../seprepo &&\n+\tgit -C \"$1\" init --separate-git-dir ../seprepo &&\n \tgit -C mainwt rev-parse --git-common-dir >expect &&\n \tgit -C linkwt rev-parse --git-common-dir >actual &&\n \ttest_cmp expect actual\n+}\n+\n+test_expect_success 're-init to move gitdir with linked worktrees' '\n+\tsep_git_dir_worktree mainwt\n+'\n+\n+test_expect_success 're-init to move gitdir within linked worktree' '\n+\tsep_git_dir_worktree linkwt\n '\n \n test_expect_success MINGW '.git hidden' '\n-- \n2.28.0.461.g40977abb40\n\n"},{"id":"404591","messageId":"20200827082129.56149-5-sunshine@sunshineco.com","threadId":"54140","inReplyTo":"20200827082129.56149-1-sunshine@sunshineco.com","subject":"[PATCH 4/5] init: teach --separate-git-dir to repair linked worktrees","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2020-08-27T08:21:28Z","receivedAt":"2020-08-27T08:23:17Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"A linked worktree's .git file is a \"gitlink\" pointing at the\n.git/worktrees/<id> directory within the repository. When `git init\n--separate-git-dir=<path>` is used on an existing repository to relocate\nthe repository's .git/ directory to a different location, it neglects to\nupdate the .git files of linked worktrees, thus breaking the worktrees\nby making it impossible for them to locate the repository. Fix this by\nteaching --separate-git-dir to repair the .git file of each linked\nworktree to point at the new repository location.\n\nReported-by: Henré Botha <henrebotha@gmail.com>\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n builtin/init-db.c |  2 ++\n t/t0001-init.sh   | 11 +++++++++++\n 2 files changed, 13 insertions(+)\n\ndiff --git a/builtin/init-db.c b/builtin/init-db.c\nindex bbc9bc78f9..7b915d88ab 100644\n--- a/builtin/init-db.c\n+++ b/builtin/init-db.c\n@@ -9,6 +9,7 @@\n #include \"builtin.h\"\n #include \"exec-cmd.h\"\n #include \"parse-options.h\"\n+#include \"worktree.h\"\n \n #ifndef DEFAULT_GIT_TEMPLATE_DIR\n #define DEFAULT_GIT_TEMPLATE_DIR \"/usr/share/git-core/templates\"\n@@ -364,6 +365,7 @@ static void separate_git_dir(const char *git_dir, const char *git_link)\n \n \t\tif (rename(src, git_dir))\n \t\t\tdie_errno(_(\"unable to move %s to %s\"), src, git_dir);\n+\t\trepair_worktrees(NULL, NULL);\n \t}\n \n \twrite_file(git_link, \"gitdir: %s\", git_dir);\ndiff --git a/t/t0001-init.sh b/t/t0001-init.sh\nindex 50222a10c5..e489eb4ddb 100755\n--- a/t/t0001-init.sh\n+++ b/t/t0001-init.sh\n@@ -405,6 +405,17 @@ test_expect_success SYMLINKS 're-init to move gitdir symlink' '\n \ttest_path_is_dir realgitdir/refs\n '\n \n+test_expect_success 're-init to move gitdir with linked worktrees' '\n+\ttest_when_finished \"rm -rf mainwt linkwt seprepo\" &&\n+\tgit init mainwt &&\n+\ttest_commit -C mainwt gumby &&\n+\tgit -C mainwt worktree add --detach ../linkwt &&\n+\tgit -C mainwt init --separate-git-dir ../seprepo &&\n+\tgit -C mainwt rev-parse --git-common-dir >expect &&\n+\tgit -C linkwt rev-parse --git-common-dir >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success MINGW '.git hidden' '\n \trm -rf newdir &&\n \t(\n-- \n2.28.0.461.g40977abb40\n\n"},{"id":"404592","messageId":"20200827082129.56149-3-sunshine@sunshineco.com","threadId":"54140","inReplyTo":"20200827082129.56149-1-sunshine@sunshineco.com","subject":"[PATCH 2/5] worktree: teach \"repair\" to fix worktree back-links to main worktree","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2020-08-27T08:21:26Z","receivedAt":"2020-08-27T08:23:18Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"The .git file in a linked worktree is a \"gitlink\" which points back to\nthe .git/worktrees/<id> entry in the main worktree or bare repository.\nIf a worktree's .git file is deleted or becomes corrupted or outdated,\nthen the linked worktree won't know how to find the repository or any of\nits own administrative files (such as 'index', 'HEAD', etc.). An easy\nway for the .git file to become outdated is for the user to move the\nmain worktree or bare repository. Although it is possible to manually\nupdate each linked worktree's .git file to reflect the new repository\nlocation, doing so requires a level of knowledge about worktree\ninternals beyond what a user should be expected to know offhand.\n\nTherefore, teach \"git worktree repair\" how to repair broken or outdated\nworktree .git files automatically. (For this to work, the command must\nbe invoked from within the main worktree or bare repository, or from\nwithin a worktree which has not become disconnected from the repository\n-- such as one which was created after the repository was moved.)\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n Documentation/git-worktree.txt | 10 ++++-\n builtin/worktree.c             | 11 ++++++\n t/t2406-worktree-repair.sh     | 72 ++++++++++++++++++++++++++++++++++\n worktree.c                     | 53 +++++++++++++++++++++++++\n worktree.h                     | 11 ++++++\n 5 files changed, 156 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex ae432d39a8..acb0ea1c2e 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -98,7 +98,10 @@ with `--reason`.\n move::\n \n Move a working tree to a new location. Note that the main working tree\n-or linked working trees containing submodules cannot be moved.\n+or linked working trees containing submodules cannot be moved with this\n+command. (The `git worktree repair` command, however, can reestablish\n+the connection with linked working trees if you move the main working\n+tree manually.)\n \n prune::\n \n@@ -115,6 +118,11 @@ repair::\n \n Repair working tree administrative files, if possible, if they have\n become corrupted or outdated due to external factors.\n++\n+For instance, if the main working tree (or bare repository) is moved,\n+linked working trees will be unable to locate it. Running `repair` in\n+the recently-moved main working tree will reestablish the connection\n+from linked working trees back to the main working tree.\n \n unlock::\n \ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 88af412d4f..62e33eb7f5 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -1030,6 +1030,16 @@ static int remove_worktree(int ac, const char **av, const char *prefix)\n \treturn ret;\n }\n \n+static void repair_cb(int iserr, const char *path, const char *msg, void *cb_data)\n+{\n+\tif (!iserr)\n+\t\tprintf_ln(_(\"repair: %s: %s\"), msg, path);\n+\telse {\n+\t\tfprintf_ln(stderr, _(\"error: %s: %s\"), msg, path);\n+\t\t*(int *)cb_data = 1;\n+\t}\n+}\n+\n static int repair(int ac, const char **av, const char *prefix)\n {\n \tstruct option options[] = {\n@@ -1040,6 +1050,7 @@ static int repair(int ac, const char **av, const char *prefix)\n \tac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n \tif (ac)\n \t\tusage_with_options(worktree_usage, options);\n+\trepair_worktrees(repair_cb, &rc);\n \treturn rc;\n }\n \ndiff --git a/t/t2406-worktree-repair.sh b/t/t2406-worktree-repair.sh\nindex cc679e1a21..9379a63130 100755\n--- a/t/t2406-worktree-repair.sh\n+++ b/t/t2406-worktree-repair.sh\n@@ -8,4 +8,76 @@ test_expect_success setup '\n \ttest_commit init\n '\n \n+test_expect_success 'skip missing worktree' '\n+\ttest_when_finished \"git worktree prune\" &&\n+\tgit worktree add --detach missing &&\n+\trm -rf missing &&\n+\tgit worktree repair >out 2>err &&\n+\ttest_must_be_empty out &&\n+\ttest_must_be_empty err\n+'\n+\n+test_expect_success \"don't clobber .git repo\" '\n+\ttest_when_finished \"rm -rf repo && git worktree prune\" &&\n+\tgit worktree add --detach repo &&\n+\trm -rf repo &&\n+\ttest_create_repo repo &&\n+\ttest_must_fail git worktree repair >out 2>err &&\n+\ttest_must_be_empty out &&\n+\ttest_i18ngrep \".git is not a file\" err\n+'\n+\n+test_corrupt_gitlink () {\n+\tbutcher=$1 &&\n+\tproblem=$2 &&\n+\trepairdir=${3:-.} &&\n+\ttest_when_finished 'rm -rf corrupt && git worktree prune' &&\n+\tgit worktree add --detach corrupt &&\n+\tgit -C corrupt rev-parse --absolute-git-dir >expect &&\n+\teval \"$butcher\" &&\n+\tgit -C \"$repairdir\" worktree repair >out 2>err &&\n+\ttest_i18ngrep \"$problem\" out &&\n+\ttest_must_be_empty err &&\n+\tgit -C corrupt rev-parse --absolute-git-dir >actual &&\n+\ttest_cmp expect actual\n+}\n+\n+test_expect_success 'repair missing .git file' '\n+\ttest_corrupt_gitlink \"rm -f corrupt/.git\" \".git link broken\"\n+'\n+\n+test_expect_success 'repair bogus .git file' '\n+\ttest_corrupt_gitlink \"echo \\\"gitdir: /nowhere\\\" >corrupt/.git\" \\\n+\t\t\".git link broken\"\n+'\n+\n+test_expect_success 'repair incorrect .git file' '\n+\ttest_when_finished \"rm -rf other && git worktree prune\" &&\n+\ttest_create_repo other &&\n+\tother=$(git -C other rev-parse --absolute-git-dir) &&\n+\ttest_corrupt_gitlink \"echo \\\"gitdir: $other\\\" >corrupt/.git\" \\\n+\t\t\".git link incorrect\"\n+'\n+\n+test_expect_success 'repair .git file from main/.git' '\n+\ttest_corrupt_gitlink \"rm -f corrupt/.git\" \".git link broken\" .git\n+'\n+\n+test_expect_success 'repair .git file from linked worktree' '\n+\ttest_when_finished \"rm -rf other && git worktree prune\" &&\n+\tgit worktree add --detach other &&\n+\ttest_corrupt_gitlink \"rm -f corrupt/.git\" \".git link broken\" other\n+'\n+\n+test_expect_success 'repair .git file from bare.git' '\n+\ttest_when_finished \"rm -rf bare.git corrupt && git worktree prune\" &&\n+\tgit clone --bare . bare.git &&\n+\tgit -C bare.git worktree add --detach ../corrupt &&\n+\tgit -C corrupt rev-parse --absolute-git-dir >expect &&\n+\trm -f corrupt/.git &&\n+\tgit -C bare.git worktree repair &&\n+\tgit -C corrupt rev-parse --absolute-git-dir >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\ndiff --git a/worktree.c b/worktree.c\nindex 62217b4a6b..029ce91fdf 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -571,3 +571,56 @@ int other_head_refs(each_ref_fn fn, void *cb_data)\n \tfree_worktrees(worktrees);\n \treturn ret;\n }\n+\n+/*\n+ * Repair worktree's /path/to/worktree/.git link if missing, corrupt, or not\n+ * pointing at <repo>/worktrees/<id>.\n+ */\n+static void repair_dotgit(struct worktree *wt,\n+\t\t\t  worktree_repair_cb *cb, void *cb_data)\n+{\n+\tstruct strbuf dotgit = STRBUF_INIT;\n+\tstruct strbuf repo = STRBUF_INIT;\n+\tchar *backlink;\n+\tconst char *repair = NULL;\n+\tint err;\n+\n+\t/* missing worktree can't be repaired */\n+\tif (!file_exists(wt->path))\n+\t\treturn;\n+\n+\tstrbuf_realpath(&repo, git_common_path(\"worktrees/%s\", wt->id), 1);\n+\tstrbuf_addf(&dotgit, \"%s/.git\", wt->path);\n+\tbacklink = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err));\n+\n+\tif (err == READ_GITFILE_ERR_NOT_A_FILE)\n+\t\tcb(1, wt->path, _(\".git is not a file\"), cb_data);\n+\telse if (err)\n+\t\trepair = _(\".git link broken\");\n+\telse if (fspathcmp(backlink, repo.buf))\n+\t\trepair = _(\".git link incorrect\");\n+\n+\tif (repair) {\n+\t\tcb(0, wt->path, repair, cb_data);\n+\t\twrite_file(dotgit.buf, \"gitdir: %s\", repo.buf);\n+\t}\n+\n+\tfree(backlink);\n+\tstrbuf_release(&repo);\n+\tstrbuf_release(&dotgit);\n+}\n+\n+static void repair_noop_cb(int iserr, const char *path, const char *msg,\n+\t\t\t   void *cb_data) {}\n+\n+void repair_worktrees(worktree_repair_cb *cb, void *cb_data)\n+{\n+\tstruct worktree **worktrees = get_worktrees();\n+\tstruct worktree **wt = worktrees + 1; /* +1 skips main worktree */\n+\n+\tif (!cb)\n+\t\tcb = repair_noop_cb;\n+\tfor (; *wt; wt++)\n+\t\trepair_dotgit(*wt, cb, cb_data);\n+\tfree_worktrees(worktrees);\n+}\ndiff --git a/worktree.h b/worktree.h\nindex 516744c433..7d085c7b91 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -89,6 +89,17 @@ int validate_worktree(const struct worktree *wt,\n void update_worktree_location(struct worktree *wt,\n \t\t\t      const char *path_);\n \n+typedef void worktree_repair_cb(int iserr, const char *path, const char *msg,\n+\t\t\t\tvoid *cb_data);\n+\n+/*\n+ * Visit each registered linked worktree and repair corruptions. For each\n+ * repair made or error encountered while attempting a repair, the callback, if\n+ * non-NULL, is called with the path of the worktree and a description of the\n+ * repair or error, along with the callback user-data.\n+ */\n+void repair_worktrees(worktree_repair_cb *, void *cb_data);\n+\n /*\n  * Free up the memory for worktree(s)\n  */\n-- \n2.28.0.461.g40977abb40\n\n"},{"id":"404593","messageId":"20200827082129.56149-4-sunshine@sunshineco.com","threadId":"54140","inReplyTo":"20200827082129.56149-1-sunshine@sunshineco.com","subject":"[PATCH 3/5] worktree: teach \"repair\" to fix outgoing links to worktrees","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2020-08-27T08:21:27Z","receivedAt":"2020-08-27T08:23:19Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"The .git/worktrees/<id>/gitdir file points at the location of a linked\nworktree's .git file. Its content must be of the form\n/path/to/worktree/.git (from which the location of the worktree itself\ncan be derived by stripping the \"/.git\" suffix). If the gitdir file is\ndeleted or becomes corrupted or outdated, then Git will be unable to\nfind the linked worktree. An easy way for the gitdir file to become\noutdated is for the user to move the worktree manually (without using\n\"git worktree move\"). Although it is possible to manually update the\ngitdir file to reflect the new linked worktree location, doing so\nrequires a level of knowledge about worktree internals beyond what a\nuser should be expected to know offhand.\n\nTherefore, teach \"git worktree repair\" how to repair broken or outdated\n.git/worktrees/<id>/gitdir files automatically. (For this to work, the\ncommand must either be invoked from within the worktree whose gitdir\nfile requires repair, or from within the main or any linked worktree by\nproviding the path of the broken worktree as an argument to \"git\nworktree repair\".)\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n Documentation/git-worktree.txt | 14 ++++--\n builtin/worktree.c             |  7 ++-\n t/t2406-worktree-repair.sh     | 86 ++++++++++++++++++++++++++++++++++\n worktree.c                     | 74 +++++++++++++++++++++++++++++\n worktree.h                     | 11 +++++\n 5 files changed, 187 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex acb0ea1c2e..a43a0af0af 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -15,7 +15,7 @@ SYNOPSIS\n 'git worktree move' <worktree> <new-path>\n 'git worktree prune' [-n] [-v] [--expire <expire>]\n 'git worktree remove' [-f] <worktree>\n-'git worktree repair'\n+'git worktree repair' [<path>...]\n 'git worktree unlock' <worktree>\n \n DESCRIPTION\n@@ -114,7 +114,7 @@ and no modification in tracked files) can be removed. Unclean working\n trees or ones with submodules can be removed with `--force`. The main\n working tree cannot be removed.\n \n-repair::\n+repair [<path>...]::\n \n Repair working tree administrative files, if possible, if they have\n become corrupted or outdated due to external factors.\n@@ -123,6 +123,13 @@ For instance, if the main working tree (or bare repository) is moved,\n linked working trees will be unable to locate it. Running `repair` in\n the recently-moved main working tree will reestablish the connection\n from linked working trees back to the main working tree.\n++\n+Similarly, if a linked working tree is moved without using `git worktree\n+move`, the main working tree (or bare repository) will be unable to\n+locate it. Running `repair` within the recently-moved working tree will\n+reestablish the connection. If multiple linked working trees are moved,\n+running `repair` from any working tree with each tree's new `<path>` as\n+an argument, will reestablish the connection to all the specified paths.\n \n unlock::\n \n@@ -317,7 +324,8 @@ in the entry's directory. For example, if a linked working tree is moved\n to `/newpath/test-next` and its `.git` file points to\n `/path/main/.git/worktrees/test-next`, then update\n `/path/main/.git/worktrees/test-next/gitdir` to reference `/newpath/test-next`\n-instead.\n+instead. Better yet, run `git worktree repair` to reestablish the connection\n+automatically.\n \n To prevent a `$GIT_DIR/worktrees` entry from being pruned (which\n can be useful in some situations, such as when the\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 62e33eb7f5..19bbc246ad 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -1042,15 +1042,18 @@ static void repair_cb(int iserr, const char *path, const char *msg, void *cb_dat\n \n static int repair(int ac, const char **av, const char *prefix)\n {\n+\tconst char **p;\n+\tconst char *self[] = { \".\", NULL };\n \tstruct option options[] = {\n \t\tOPT_END()\n \t};\n \tint rc = 0;\n \n \tac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n-\tif (ac)\n-\t\tusage_with_options(worktree_usage, options);\n \trepair_worktrees(repair_cb, &rc);\n+\tp = ac > 0 ? av : self;\n+\tfor (; *p; p++)\n+\t\trepair_worktree_at_path(*p, repair_cb, &rc);\n \treturn rc;\n }\n \ndiff --git a/t/t2406-worktree-repair.sh b/t/t2406-worktree-repair.sh\nindex 9379a63130..87bd8fc526 100755\n--- a/t/t2406-worktree-repair.sh\n+++ b/t/t2406-worktree-repair.sh\n@@ -80,4 +80,90 @@ test_expect_success 'repair .git file from bare.git' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'invalid worktree path' '\n+\ttest_must_fail git worktree repair /notvalid >out 2>err &&\n+\ttest_must_be_empty out &&\n+\ttest_i18ngrep \"not a valid path\" err\n+'\n+\n+test_expect_success 'repo not found; .git not file' '\n+\ttest_when_finished \"rm -rf not-a-worktree\" &&\n+\ttest_create_repo not-a-worktree &&\n+\ttest_must_fail git worktree repair not-a-worktree >out 2>err &&\n+\ttest_must_be_empty out &&\n+\ttest_i18ngrep \".git is not a file\" err\n+'\n+\n+test_expect_success 'repo not found; .git link broken' '\n+\ttest_when_finished \"rm -rf orig moved && git worktree prune\" &&\n+\tgit worktree add --detach orig &&\n+\techo /invalid >orig/.git &&\n+\tmv orig moved &&\n+\ttest_must_fail git worktree repair moved >out 2>err &&\n+\ttest_must_be_empty out &&\n+\ttest_i18ngrep \".git link broken\" err\n+'\n+\n+test_expect_success 'repair broken gitdir' '\n+\ttest_when_finished \"rm -rf orig moved && git worktree prune\" &&\n+\tgit worktree add --detach orig &&\n+\tsed s,orig/\\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&\n+\trm .git/worktrees/orig/gitdir &&\n+\tmv orig moved &&\n+\tgit worktree repair moved >out 2>err &&\n+\ttest_cmp expect .git/worktrees/orig/gitdir &&\n+\ttest_i18ngrep \"gitdir unreadable\" out &&\n+\ttest_must_be_empty err\n+'\n+\n+test_expect_success 'repair incorrect gitdir' '\n+\ttest_when_finished \"rm -rf orig moved && git worktree prune\" &&\n+\tgit worktree add --detach orig &&\n+\tsed s,orig/\\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&\n+\tmv orig moved &&\n+\tgit worktree repair moved >out 2>err &&\n+\ttest_cmp expect .git/worktrees/orig/gitdir &&\n+\ttest_i18ngrep \"gitdir incorrect\" out &&\n+\ttest_must_be_empty err\n+'\n+\n+test_expect_success 'repair gitdir (implicit) from linked worktree' '\n+\ttest_when_finished \"rm -rf orig moved && git worktree prune\" &&\n+\tgit worktree add --detach orig &&\n+\tsed s,orig/\\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&\n+\tmv orig moved &&\n+\tgit -C moved worktree repair >out 2>err &&\n+\ttest_cmp expect .git/worktrees/orig/gitdir &&\n+\ttest_i18ngrep \"gitdir incorrect\" out &&\n+\ttest_must_be_empty err\n+'\n+\n+test_expect_success 'unable to repair gitdir (implicit) from main worktree' '\n+\ttest_when_finished \"rm -rf orig moved && git worktree prune\" &&\n+\tgit worktree add --detach orig &&\n+\tcat .git/worktrees/orig/gitdir >expect &&\n+\tmv orig moved &&\n+\tgit worktree repair >out 2>err &&\n+\ttest_cmp expect .git/worktrees/orig/gitdir &&\n+\ttest_must_be_empty out &&\n+\ttest_must_be_empty err\n+'\n+\n+test_expect_success 'repair multiple gitdir files' '\n+\ttest_when_finished \"rm -rf orig1 orig2 moved1 moved2 &&\n+\t\tgit worktree prune\" &&\n+\tgit worktree add --detach orig1 &&\n+\tgit worktree add --detach orig2 &&\n+\tsed s,orig1/\\.git$,moved1/.git, .git/worktrees/orig1/gitdir >expect1 &&\n+\tsed s,orig2/\\.git$,moved2/.git, .git/worktrees/orig2/gitdir >expect2 &&\n+\tmv orig1 moved1 &&\n+\tmv orig2 moved2 &&\n+\tgit worktree repair moved1 moved2 >out 2>err &&\n+\ttest_cmp expect1 .git/worktrees/orig1/gitdir &&\n+\ttest_cmp expect2 .git/worktrees/orig2/gitdir &&\n+\ttest_i18ngrep \"gitdir incorrect:.*orig1/gitdir$\" out &&\n+\ttest_i18ngrep \"gitdir incorrect:.*orig2/gitdir$\" out &&\n+\ttest_must_be_empty err\n+'\n+\n test_done\ndiff --git a/worktree.c b/worktree.c\nindex 029ce91fdf..6ade4f0d8b 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -624,3 +624,77 @@ void repair_worktrees(worktree_repair_cb *cb, void *cb_data)\n \t\trepair_dotgit(*wt, cb, cb_data);\n \tfree_worktrees(worktrees);\n }\n+\n+static int is_main_worktree_path(const char *path)\n+{\n+\tstruct strbuf target = STRBUF_INIT;\n+\tstruct strbuf main = STRBUF_INIT;\n+\tint cmp;\n+\n+\tstrbuf_add_real_path(&target, path);\n+\tstrbuf_strip_suffix(&target, \"/.git\");\n+\tstrbuf_add_real_path(&main, get_git_common_dir());\n+\tstrbuf_strip_suffix(&main, \"/.git\");\n+\tcmp = fspathcmp(main.buf, target.buf);\n+\n+\tstrbuf_release(&main);\n+\tstrbuf_release(&target);\n+\treturn !cmp;\n+}\n+\n+/*\n+ * Repair <repo>/worktrees/<id>/gitdir if missing, corrupt, or not pointing at\n+ * the worktree's path.\n+ */\n+void repair_worktree_at_path(const char *path,\n+\t\t\t     worktree_repair_cb *cb, void *cb_data)\n+{\n+\tstruct strbuf dotgit = STRBUF_INIT;\n+\tstruct strbuf realdotgit = STRBUF_INIT;\n+\tstruct strbuf gitdir = STRBUF_INIT;\n+\tstruct strbuf olddotgit = STRBUF_INIT;\n+\tchar *backlink = NULL;\n+\tconst char *repair = NULL;\n+\tint err;\n+\n+\tif (!cb)\n+\t\tcb = repair_noop_cb;\n+\n+\tif (is_main_worktree_path(path))\n+\t\tgoto done;\n+\n+\tstrbuf_addf(&dotgit, \"%s/.git\", path);\n+\tif (!strbuf_realpath(&realdotgit, dotgit.buf, 0)) {\n+\t\tcb(1, path, _(\"not a valid path\"), cb_data);\n+\t\tgoto done;\n+\t}\n+\n+\tbacklink = xstrdup_or_null(read_gitfile_gently(realdotgit.buf, &err));\n+\tif (err == READ_GITFILE_ERR_NOT_A_FILE) {\n+\t\tcb(1, realdotgit.buf, _(\"unable to locate repository; .git is not a file\"), cb_data);\n+\t\tgoto done;\n+\t} else if (err) {\n+\t\tcb(1, realdotgit.buf, _(\"unable to locate repository; .git link broken\"), cb_data);\n+\t\tgoto done;\n+\t}\n+\n+\tstrbuf_addf(&gitdir, \"%s/gitdir\", backlink);\n+\tif (strbuf_read_file(&olddotgit, gitdir.buf, 0) < 0)\n+\t\trepair = _(\"gitdir unreadable\");\n+\telse {\n+\t\tstrbuf_rtrim(&olddotgit);\n+\t\tif (fspathcmp(olddotgit.buf, realdotgit.buf))\n+\t\t\trepair = _(\"gitdir incorrect\");\n+\t}\n+\n+\tif (repair) {\n+\t\tcb(0, gitdir.buf, repair, cb_data);\n+\t\twrite_file(gitdir.buf, \"%s\", realdotgit.buf);\n+\t}\n+done:\n+\tfree(backlink);\n+\tstrbuf_release(&olddotgit);\n+\tstrbuf_release(&gitdir);\n+\tstrbuf_release(&realdotgit);\n+\tstrbuf_release(&dotgit);\n+}\ndiff --git a/worktree.h b/worktree.h\nindex 7d085c7b91..9cb7f05741 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -100,6 +100,17 @@ typedef void worktree_repair_cb(int iserr, const char *path, const char *msg,\n  */\n void repair_worktrees(worktree_repair_cb *, void *cb_data);\n \n+/*\n+ * Repair administrative files corresponding to the worktree at the given path.\n+ * The worktree's .git link pointing at the repository must be intact for the\n+ * repair to succeed. Useful for re-associating an orphaned worktree with the\n+ * repository if the worktree has been moved manually (without using \"git\n+ * worktree move\"). For each repair made or error encountered while attempting\n+ * a repair, the callback, if non-NULL, is called with the path of the worktree\n+ * and a description of the repair or error, along with the callback user-data.\n+ */\n+void repair_worktree_at_path(const char *, worktree_repair_cb *, void *cb_data);\n+\n /*\n  * Free up the memory for worktree(s)\n  */\n-- \n2.28.0.461.g40977abb40\n\n"},{"id":"404624","messageId":"xmqqr1rsqdga.fsf@gitster.c.googlers.com","threadId":"54140","inReplyTo":"20200827082129.56149-2-sunshine@sunshineco.com","subject":"Re: [PATCH 1/5] worktree: add skeleton \"repair\" command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-08-27T16:08:05Z","receivedAt":"2020-08-27T16:08:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> Worktree administrative files can become corrupted or outdated due to\n> external factors. Although, it is often possible to recover from such\n> situations by hand-tweaking these files, doing so requires intimate\n> knowledge of worktree internals. While information necessary to make\n> such repairs manually can be obtained from git-worktree.txt and\n> gitrepository-layout.txt, we can assist users more directly by teaching\n> git-worktree how to repair its administrative files itself (at least to\n> some extent). Therefore, add a \"git worktree repair\" command which\n> attempts to correct common problems which may arise due to factors\n> beyond Git's control.\n>\n> At this stage, the \"repair\" command is a mere skeleton; subsequent\n> commits will flesh out the functionality.\n\nSounds good.  It looked a bit odd to have skeleton test script only\nto reserve its test number, but it is just odd and not wrong.\n\nLet's read on.\n\n> Signed-off-by: Eric Sunshine <sunshine@sunshineco.com>\n> ---\n>  Documentation/git-worktree.txt |  6 ++++++\n>  builtin/worktree.c             | 15 +++++++++++++++\n>  t/t2406-worktree-repair.sh     | 11 +++++++++++\n>  3 files changed, 32 insertions(+)\n>  create mode 100755 t/t2406-worktree-repair.sh\n>\n> diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\n> index 6ee6ec7982..ae432d39a8 100644\n> --- a/Documentation/git-worktree.txt\n> +++ b/Documentation/git-worktree.txt\n> @@ -15,6 +15,7 @@ SYNOPSIS\n>  'git worktree move' <worktree> <new-path>\n>  'git worktree prune' [-n] [-v] [--expire <expire>]\n>  'git worktree remove' [-f] <worktree>\n> +'git worktree repair'\n>  'git worktree unlock' <worktree>\n>  \n>  DESCRIPTION\n> @@ -110,6 +111,11 @@ and no modification in tracked files) can be removed. Unclean working\n>  trees or ones with submodules can be removed with `--force`. The main\n>  working tree cannot be removed.\n>  \n> +repair::\n> +\n> +Repair working tree administrative files, if possible, if they have\n> +become corrupted or outdated due to external factors.\n> +\n>  unlock::\n>  \n>  Unlock a working tree, allowing it to be pruned, moved or deleted.\n> diff --git a/builtin/worktree.c b/builtin/worktree.c\n> index 378f332b5d..88af412d4f 100644\n> --- a/builtin/worktree.c\n> +++ b/builtin/worktree.c\n> @@ -1030,6 +1030,19 @@ static int remove_worktree(int ac, const char **av, const char *prefix)\n>  \treturn ret;\n>  }\n>  \n> +static int repair(int ac, const char **av, const char *prefix)\n> +{\n> +\tstruct option options[] = {\n> +\t\tOPT_END()\n> +\t};\n> +\tint rc = 0;\n> +\n> +\tac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n> +\tif (ac)\n> +\t\tusage_with_options(worktree_usage, options);\n> +\treturn rc;\n> +}\n> +\n>  int cmd_worktree(int ac, const char **av, const char *prefix)\n>  {\n>  \tstruct option options[] = {\n> @@ -1056,5 +1069,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)\n>  \t\treturn move_worktree(ac - 1, av + 1, prefix);\n>  \tif (!strcmp(av[1], \"remove\"))\n>  \t\treturn remove_worktree(ac - 1, av + 1, prefix);\n> +\tif (!strcmp(av[1], \"repair\"))\n> +\t\treturn repair(ac - 1, av + 1, prefix);\n>  \tusage_with_options(worktree_usage, options);\n>  }\n> diff --git a/t/t2406-worktree-repair.sh b/t/t2406-worktree-repair.sh\n> new file mode 100755\n> index 0000000000..cc679e1a21\n> --- /dev/null\n> +++ b/t/t2406-worktree-repair.sh\n> @@ -0,0 +1,11 @@\n> +#!/bin/sh\n> +\n> +test_description='test git worktree repair'\n> +\n> +. ./test-lib.sh\n> +\n> +test_expect_success setup '\n> +\ttest_commit init\n> +'\n> +\n> +test_done\n"},{"id":"404628","messageId":"xmqqlfi0qaru.fsf@gitster.c.googlers.com","threadId":"54140","inReplyTo":"20200827082129.56149-3-sunshine@sunshineco.com","subject":"Re: [PATCH 2/5] worktree: teach \"repair\" to fix worktree back-links to main worktree","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-08-27T17:05:57Z","receivedAt":"2020-08-27T17:06:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> The .git file in a linked worktree is a \"gitlink\" which points back to\n\nPlease never call it a \"gitlink\" (which is a word reserved for a\ncache entry with 160000 mode that typically point at a commit\nobject, an implementation detail of the mechanism to bind a\nsubmodule to its superproject) to avoid confusing future readers of\nour code and documentation.  The setup.c code calls it as \"gitfile\",\nsince it was introduced at b44ebb19 (Add platform-independent .git\n\"symlink\", 2008-02-20), I think, and that is how it has been described\nin the glossary.\n\n> the .git/worktrees/<id> entry in the main worktree or bare repository.\n> If a worktree's .git file is deleted or becomes corrupted or outdated,\n> then the linked worktree won't know how to find the repository or any of\n> its own administrative files (such as 'index', 'HEAD', etc.). An easy\n> way for the .git file to become outdated is for the user to move the\n> main worktree or bare repository.\n\nI am not sure if a directory that used to be a secondary worktree\nwould still appear to be a Git controlled worktree after its \".git\"\nremoved, but I guess a \".git\" pointing at a wrong place won't,\neither.  I am just curious how much more involved in dealing with\nthe \"is deleted\" situation than \"becomes corrupted\" situation.  \n\nAn obvious way is to ask the users to say \"Here in directory D\nshould be a gitfile that points at the primary copy X---D/.git may\nbe missing or corrupt so please go create or fix it\" without letting\nany \"auto repository discovery\" logic to kick in.  For that, I suspect\nyou may have to disable RUN_SETUP in git.c for \"worktree\" built-in,\nand run the setup sequence manually for all the existing subcommands\nexcept for this new \"repair\" subcommand.  We'll see.\n\n> Although it is possible to manually\n> update each linked worktree's .git file to reflect the new repository\n> location, doing so requires a level of knowledge about worktree\n> internals beyond what a user should be expected to know offhand.\n>\n> Therefore, teach \"git worktree repair\" how to repair broken or outdated\n> worktree .git files automatically. (For this to work, the command must\n> be invoked from within the main worktree or bare repository, or from\n> within a worktree which has not become disconnected from the repository\n> -- such as one which was created after the repository was moved.)\n\nHmph, ok, that is not quite satisfactory, but it would work.  So\ninstead of\n\n    user goes to a directory D, thinking it is a valid secondary\n    worktree and wanting to work in it, and finds that Git does not\n    think it is.\n\n    user tells \"worktree repair\" that X is the location of the\n    primary copy, and cwd is supposed to be the top of the working\n    tree of a secondary worktree of it.\n\n    \"worktree repair\" creates/updates ./.git gitfile to point at X.\n\n    user starts working.\n\nsequence, the user does\n\n    user goes to a directory D, thinking it is a valid secondary\n    worktree and wanting to work in it, and finds that Git does not\n    think it is.\n\n    user goes to X, which is the location of the primary copy, and\n    tells \"worktree repair\" that the directory D ought to be the top\n    of the working tree for a secondary worktree.\n\n    \"worktree repair\" creates/updates D/.git gitfile to point at X.\n\n    user comes back to D and starts working.\n\nbecause \"git worktree repair\" wants to do the usual setup sequence\nanyway.  \n\nOK.\n\n>  Move a working tree to a new location. Note that the main working tree\n> -or linked working trees containing submodules cannot be moved.\n> +or linked working trees containing submodules cannot be moved with this\n> +command. (The `git worktree repair` command, however, can reestablish\n> +the connection with linked working trees if you move the main working\n> +tree manually.)\n\n... meaning after moving the main working tree, repair can be used\nto touch-up the submodule directories?\n\nAh, no.  You are saying \"git worktree move\" still cannot be used to\nmove the main working tree (the ones with submodules in it we do not\neven care), but as a workaround, the user can \"mv\" it manually and\nrun \"repair\" to fix all the secondary worktrees.\n\nHopefully somewhere in the rest of this series it would become\nautomatic?  After all, instead of letting users \"mv\" things without\nus knowing what is going on, if we let them say \"worktree move\" the\nprimary working tree, we know both the source and the destination\ndirectories of such a move, and because we know all the secondary\nworktrees, we can run \"worktree repair\" on them as part of this\n\"worktree move\" of the primary working tree.\n\nIt is perfectly fine that it is not happening here in this step to\nkeep each step digestible, of course.\n\nLet's read on.\n\n> @@ -115,6 +118,11 @@ repair::\n>  \n>  Repair working tree administrative files, if possible, if they have\n>  become corrupted or outdated due to external factors.\n> ++\n> +For instance, if the main working tree (or bare repository) is moved,\n> +linked working trees will be unable to locate it. Running `repair` in\n> +the recently-moved main working tree will reestablish the connection\n> +from linked working trees back to the main working tree.\n\nDoes \"recently-\" have a positive value in this paragraph?  It makes\nreaders wonder how long the zombie would stay resurrectable, which\nwould encourage to run this command while s/he still remembers that\nthe primary copy got moved, but I am not sure if that is a plus.\n\n> diff --git a/builtin/worktree.c b/builtin/worktree.c\n> index 88af412d4f..62e33eb7f5 100644\n> --- a/builtin/worktree.c\n> +++ b/builtin/worktree.c\n> @@ -1030,6 +1030,16 @@ static int remove_worktree(int ac, const char **av, const char *prefix)\n>  \treturn ret;\n>  }\n>  \n> +static void repair_cb(int iserr, const char *path, const char *msg, void *cb_data)\n\nrepair_cb as the name for a parameter to repair_worktrees() API\nfunction is an excellent name, but as the name for a concrete\ninstance of such a callback function, it is a horrible one.  Name it\nafter what it does or what it is for in the context of this\nparticular use of that API function, e.g. \"report repair status\".\n\n> +{\n> +\tif (!iserr)\n> +\t\tprintf_ln(_(\"repair: %s: %s\"), msg, path);\n> +\telse {\n> +\t\tfprintf_ln(stderr, _(\"error: %s: %s\"), msg, path);\n> +\t\t*(int *)cb_data = 1;\n> +\t}\n\nSpend a local variable would pay for readability, e.g.\n\n\t{\n\t\tint *exit_status = (int *)cb_data;\n\t\tif (!iserr) {\n\t\t\t... report success ...\n\t\t} else {\n\t\t\t... report failure ...\n\t\t\t*exit_status = 1;\n\t        }\n\t}\n\nwould make it clearer that the number '1' is assigned to update the\nvalue used as the exit status.  With the same callback API, the\ncaller could be counting the number of secondaries failed to be\nresurrected, in which case assignment of 1 is a potential bug\nthat needs to be updated to \n\n\t\t*(int *)cb_data += 1;\n\nbut the reader cannot tell from the generic name cb_data.\n\n> +}\n> +\n>  static int repair(int ac, const char **av, const char *prefix)\n>  {\n>  \tstruct option options[] = {\n> @@ -1040,6 +1050,7 @@ static int repair(int ac, const char **av, const char *prefix)\n>  \tac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n>  \tif (ac)\n>  \t\tusage_with_options(worktree_usage, options);\n> +\trepair_worktrees(repair_cb, &rc);\n>  \treturn rc;\n>  }\n>  \n> diff --git a/t/t2406-worktree-repair.sh b/t/t2406-worktree-repair.sh\n> index cc679e1a21..9379a63130 100755\n> --- a/t/t2406-worktree-repair.sh\n> +++ b/t/t2406-worktree-repair.sh\n> @@ -8,4 +8,76 @@ test_expect_success setup '\n>  \ttest_commit init\n>  '\n>  \n> +test_expect_success 'skip missing worktree' '\n> +\ttest_when_finished \"git worktree prune\" &&\n> +\tgit worktree add --detach missing &&\n> +\trm -rf missing &&\n> +\tgit worktree repair >out 2>err &&\n> +\ttest_must_be_empty out &&\n> +\ttest_must_be_empty err\n> +'\n> +\n> +test_expect_success \"don't clobber .git repo\" '\n> +\ttest_when_finished \"rm -rf repo && git worktree prune\" &&\n> +\tgit worktree add --detach repo &&\n> +\trm -rf repo &&\n> +\ttest_create_repo repo &&\n> +\ttest_must_fail git worktree repair >out 2>err &&\n> +\ttest_must_be_empty out &&\n> +\ttest_i18ngrep \".git is not a file\" err\n> +'\n> +\n> +test_corrupt_gitlink () {\n\nSee gitglossary::gitfile.  We'd need to find all references to\n\"gitlink\" and \"git link\" in this series and decide which ones need\nto be fixed (there might be genuine references to gitlink---I\nhaven't read the series through).\n\n> diff --git a/worktree.c b/worktree.c\n> index 62217b4a6b..029ce91fdf 100644\n> --- a/worktree.c\n> +++ b/worktree.c\n> @@ -571,3 +571,56 @@ int other_head_refs(each_ref_fn fn, void *cb_data)\n>  \tfree_worktrees(worktrees);\n>  \treturn ret;\n>  }\n> +\n> +/*\n> + * Repair worktree's /path/to/worktree/.git link if missing, corrupt, or not\n> + * pointing at <repo>/worktrees/<id>.\n> + */\n> +static void repair_dotgit(struct worktree *wt,\n> +\t\t\t  worktree_repair_cb *cb, void *cb_data)\n\n\"dotgit\" is an OK name in this context, I would think.  repair_gitfile\nis also fine, but \"dotgit\" may be more explicit.\n\nIt is customary to call the callback function \"fn\", not \"cb\", which\nsometimes used as a short-hand for \"cb_data\".\n\n> +{\n> +\tstruct strbuf dotgit = STRBUF_INIT;\n> +\tstruct strbuf repo = STRBUF_INIT;\n> +\tchar *backlink;\n> +\tconst char *repair = NULL;\n> +\tint err;\n> +\n> +\t/* missing worktree can't be repaired */\n> +\tif (!file_exists(wt->path))\n> +\t\treturn;\n\nShouldn't it be a bit stronger?  if wt->path is not a directory, it\ncannot be the top of the working tree of a secondary worktree.\n\n> +\tstrbuf_realpath(&repo, git_common_path(\"worktrees/%s\", wt->id), 1);\n\nOK, the need to use git_common_path() is a good justification why it\nis easier to implement if we initialize ourselves using the primary\ncopy's repository data (i.e. require the user to start \"worktree\nrepair\" in the primary copy or any working secondary worktrees, and\nlet the normal setup.c sequence to prime us in the repository),\ninstead of allowing the user to start at the secondary worktree\nwhose gitfile got broken.\n\n> +\tstrbuf_addf(&dotgit, \"%s/.git\", wt->path);\n> +\tbacklink = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err));\n> +\n> +\tif (err == READ_GITFILE_ERR_NOT_A_FILE)\n> +\t\tcb(1, wt->path, _(\".git is not a file\"), cb_data);\n> +\telse if (err)\n> +\t\trepair = _(\".git link broken\");\n> +\telse if (fspathcmp(backlink, repo.buf))\n> +\t\trepair = _(\".git link incorrect\");\n> +\tif (repair) {\n> +\t\tcb(0, wt->path, repair, cb_data);\n> +\t\twrite_file(dotgit.buf, \"gitdir: %s\", repo.buf);\n\nShouldn't write_file() be monitored for its failure, and the failure\nreported back to the callback function?\n\n> +\t}\n> +\n> +\tfree(backlink);\n> +\tstrbuf_release(&repo);\n> +\tstrbuf_release(&dotgit);\n> +}\n> +\n> +static void repair_noop_cb(int iserr, const char *path, const char *msg,\n> +\t\t\t   void *cb_data) {}\n\nEven the body is empty, just follow the coding guidelines, i.e.\n\n\tstatic void function_name(parameter list)\n\t{\n\t\t/* nothing */\n\t}\n\n> +void repair_worktrees(worktree_repair_cb *cb, void *cb_data)\n> +{\n> +\tstruct worktree **worktrees = get_worktrees();\n> +\tstruct worktree **wt = worktrees + 1; /* +1 skips main worktree */\n> +\n> +\tif (!cb)\n> +\t\tcb = repair_noop_cb;\n> +\tfor (; *wt; wt++)\n> +\t\trepair_dotgit(*wt, cb, cb_data);\n> +\tfree_worktrees(worktrees);\n> +}\n\nThis \"repair\"s only in one direction.  Manual movement of secondary\nworktrees, if we want to support repairing, needs the user to tell\nwhere the new location of the secondary is, and we need a code to\nupdate the record of the location of the secondary kept at the main\nworktree, which is not needed in the repair implemented by this\nstep.\n\n> diff --git a/worktree.h b/worktree.h\n> index 516744c433..7d085c7b91 100644\n> --- a/worktree.h\n> +++ b/worktree.h\n> @@ -89,6 +89,17 @@ int validate_worktree(const struct worktree *wt,\n>  void update_worktree_location(struct worktree *wt,\n>  \t\t\t      const char *path_);\n>  \n> +typedef void worktree_repair_cb(int iserr, const char *path, const char *msg,\n> +\t\t\t\tvoid *cb_data);\n> +\n> +/*\n> + * Visit each registered linked worktree and repair corruptions. For each\n> + * repair made or error encountered while attempting a repair, the callback, if\n> + * non-NULL, is called with the path of the worktree and a description of the\n> + * repair or error, along with the callback user-data.\n> + */\n> +void repair_worktrees(worktree_repair_cb *, void *cb_data);\n> +\n>  /*\n>   * Free up the memory for worktree(s)\n>   */\n\nLooking good.\n\nThanks.\n"},{"id":"404629","messageId":"xmqqh7soqady.fsf@gitster.c.googlers.com","threadId":"54140","inReplyTo":"20200827082129.56149-4-sunshine@sunshineco.com","subject":"Re: [PATCH 3/5] worktree: teach \"repair\" to fix outgoing links to worktrees","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-08-27T17:14:17Z","receivedAt":"2020-08-27T17:14:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> The .git/worktrees/<id>/gitdir file points at the location of a linked\n> worktree's .git file. Its content must be of the form\n> /path/to/worktree/.git (from which the location of the worktree itself\n> can be derived by stripping the \"/.git\" suffix). If the gitdir file is\n> deleted or becomes corrupted or outdated, then Git will be unable to\n> find the linked worktree. An easy way for the gitdir file to become\n> outdated is for the user to move the worktree manually (without using\n> \"git worktree move\"). Although it is possible to manually update the\n> gitdir file to reflect the new linked worktree location, doing so\n> requires a level of knowledge about worktree internals beyond what a\n> user should be expected to know offhand.\n>\n> Therefore, teach \"git worktree repair\" how to repair broken or outdated\n> .git/worktrees/<id>/gitdir files automatically. (For this to work, the\n> command must either be invoked from within the worktree whose gitdir\n> file requires repair, or from within the main or any linked worktree by\n> providing the path of the broken worktree as an argument to \"git\n> worktree repair\".)\n\nWould git \"work\" in a corrupt worktree whose gitfile is broken, in\nthe sense that it notices that the cwd is the top of the working\ntree of a secondary worktree?  I can imagine how it would work,\nstarting in one of the functioning worktrees so that git can locate\nwhere the primary copy is, with end-user supplied path to a\ndirectory that is supposed to be the top of the working tree of a\nsecondary worktree.\n\nHmph, if the secondary is _moved_, how would \"worktree repair $path\"\nwould know which <id> the $path corresponds to?  Would we just cull\nall the <id> that do not point at working secondary worktrees and\nadd the $path as if it were a new one by allocating a new <id>, or\nreusing a randomly chosen <id> that points at a non-existing\nlocation?\n\n"},{"id":"404648","messageId":"CAPig+cTeGA7i2a8A9N1z00f_D06cwCSqWvJZidjGBRVAxBJaUw@mail.gmail.com","threadId":"54140","inReplyTo":"xmqqr1rsqdga.fsf@gitster.c.googlers.com","subject":"Re: [PATCH 1/5] worktree: add skeleton \"repair\" command","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2020-08-27T19:30:53Z","receivedAt":"2020-08-27T19:31:07Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Thu, Aug 27, 2020 at 12:08 PM Junio C Hamano <gitster@pobox.com> wrote:\n> Eric Sunshine <sunshine@sunshineco.com> writes:\n> > At this stage, the \"repair\" command is a mere skeleton; subsequent\n> > commits will flesh out the functionality.\n>\n> Sounds good.  It looked a bit odd to have skeleton test script only\n> to reserve its test number, but it is just odd and not wrong.\n\nThe intent wasn't to reserve a test number. Rather, I was worried\nabout reviewer fatigue with patches [2/5] and [3/5] which are lengthy,\nso I moved whatever boilerplate I could to [1/5] to make the later\npatches a tiny bit shorter.\n"},{"id":"404698","messageId":"nycvar.QRO.7.76.6.2008280413450.56@tvgsbejvaqbjf.bet","threadId":"54140","inReplyTo":"20200827082129.56149-4-sunshine@sunshineco.com","subject":"Re: [PATCH 3/5] worktree: teach \"repair\" to fix outgoing links to worktrees","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2020-08-28T02:15:12Z","receivedAt":"2020-08-28T12:55:17Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Eric,\n\nOn Thu, 27 Aug 2020, Eric Sunshine wrote:\n\n> diff --git a/worktree.c b/worktree.c\n> index 029ce91fdf..6ade4f0d8b 100644\n> --- a/worktree.c\n> +++ b/worktree.c\n> @@ -624,3 +624,77 @@ void repair_worktrees(worktree_repair_cb *cb, void *cb_data)\n>  \t\trepair_dotgit(*wt, cb, cb_data);\n>  \tfree_worktrees(worktrees);\n>  }\n> +\n> +static int is_main_worktree_path(const char *path)\n> +{\n> +\tstruct strbuf target = STRBUF_INIT;\n> +\tstruct strbuf main = STRBUF_INIT;\n> +\tint cmp;\n> +\n> +\tstrbuf_add_real_path(&target, path);\n> +\tstrbuf_strip_suffix(&target, \"/.git\");\n> +\tstrbuf_add_real_path(&main, get_git_common_dir());\n> +\tstrbuf_strip_suffix(&main, \"/.git\");\n> +\tcmp = fspathcmp(main.buf, target.buf);\n> +\n> +\tstrbuf_release(&main);\n> +\tstrbuf_release(&target);\n> +\treturn !cmp;\n> +}\n\nThis breaks our `linux-gcc` job, and I need this on top, to make it even\nbuild:\n\n-- snipsnap --\nFrom: Johannes Schindelin <johannes.schindelin@gmx.de>\nSubject: [PATCH] fixup??? worktree: teach \"repair\" to fix outgoing links to worktrees\n\nThis is needed to shut up GCC's \"‘main’ is usually a function\n[-Werror=main]\" error.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n worktree.c | 10 +++++-----\n 1 file changed, 5 insertions(+), 5 deletions(-)\n\ndiff --git a/worktree.c b/worktree.c\nindex 6ade4f0d8b2b..5471915d4680 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -628,16 +628,16 @@ void repair_worktrees(worktree_repair_cb *cb, void *cb_data)\n static int is_main_worktree_path(const char *path)\n {\n \tstruct strbuf target = STRBUF_INIT;\n-\tstruct strbuf main = STRBUF_INIT;\n+\tstruct strbuf main_worktree = STRBUF_INIT;\n \tint cmp;\n\n \tstrbuf_add_real_path(&target, path);\n \tstrbuf_strip_suffix(&target, \"/.git\");\n-\tstrbuf_add_real_path(&main, get_git_common_dir());\n-\tstrbuf_strip_suffix(&main, \"/.git\");\n-\tcmp = fspathcmp(main.buf, target.buf);\n+\tstrbuf_add_real_path(&main_worktree, get_git_common_dir());\n+\tstrbuf_strip_suffix(&main_worktree, \"/.git\");\n+\tcmp = fspathcmp(main_worktree.buf, target.buf);\n\n-\tstrbuf_release(&main);\n+\tstrbuf_release(&main_worktree);\n \tstrbuf_release(&target);\n \treturn !cmp;\n }\n--\n2.28.0.windows.1\n"},{"id":"404712","messageId":"CAPig+cRt3-3g+GSDD9q6NkoaejwgKMPk6nuc+Lax9y8_ZFzZ6Q@mail.gmail.com","threadId":"54140","inReplyTo":"nycvar.QRO.7.76.6.2008280413450.56@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 3/5] worktree: teach \"repair\" to fix outgoing links to worktrees","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2020-08-28T16:27:30Z","receivedAt":"2020-08-28T16:27:45Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Fri, Aug 28, 2020 at 8:55 AM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n> On Thu, 27 Aug 2020, Eric Sunshine wrote:\n> > +     struct strbuf main = STRBUF_INIT;\n>\n> This is needed to shut up GCC's \"‘main’ is usually a function\n> [-Werror=main]\" error.\n\nD'oh, thanks. I got hit by this just a couple months ago[1] and (for\nsome reason) was even looking at [1] the same day I submitted this\npatch. Will fix it in the re-roll.\n\n[1]: https://lore.kernel.org/git/20200610063049.74666-1-sunshine@sunshineco.com/\n"},{"id":"404765","messageId":"CAPig+cT-ipENZQ39wpaGukRzx3d52OatKEXjWc3_mv56jMbDRg@mail.gmail.com","threadId":"54140","inReplyTo":"xmqqlfi0qaru.fsf@gitster.c.googlers.com","subject":"Re: [PATCH 2/5] worktree: teach \"repair\" to fix worktree back-links to main worktree","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2020-08-30T07:20:31Z","receivedAt":"2020-08-30T07:20:49Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Thu, Aug 27, 2020 at 1:06 PM Junio C Hamano <gitster@pobox.com> wrote:\n> Eric Sunshine <sunshine@sunshineco.com> writes:\n> > The .git file in a linked worktree is a \"gitlink\" which points back to\n>\n> Please never call it a \"gitlink\" (which is a word reserved for a\n> cache entry with 160000 mode that typically point at a commit\n> object, an implementation detail of the mechanism to bind a\n> submodule to its superproject) to avoid confusing future readers of\n> our code and documentation. The setup.c code calls it as \"gitfile\",\n> since it was introduced at b44ebb19 (Add platform-independent .git\n> \"symlink\", 2008-02-20), I think, and that is how it has been described\n> in the glossary.\n\nThanks. I don't know why my brain latched onto \"gitlink\" even though I\nnoted the difference in the name read_gitfile_gently().\n\n> > the .git/worktrees/<id> entry in the main worktree or bare repository.\n> > If a worktree's .git file is deleted or becomes corrupted or outdated,\n> > then the linked worktree won't know how to find the repository or any of\n> > its own administrative files (such as 'index', 'HEAD', etc.). An easy\n> > way for the .git file to become outdated is for the user to move the\n> > main worktree or bare repository.\n>\n> I am not sure if a directory that used to be a secondary worktree\n> would still appear to be a Git controlled worktree after its \".git\"\n> removed, but I guess a \".git\" pointing at a wrong place won't,\n> either.\n\nThe directory itself will be disconnected from its object database,\nthus non-working, however, its .git/worktrees/<id> entry will still be\npresent, so the repository will still consider it a linked worktree.\n\n> I am just curious how much more involved in dealing with the \"is\n> deleted\" situation than \"becomes corrupted\" situation.\n\nThey are handled identically. \"git worktree repair\" simply visits each\n.git/worktrees/<id> entry and checks if the /path/to/worktree/.git\ngitfile points back to .git/worktrees/<id>. If it doesn't -- whether\nbecause the gitfile is missing, corrupted, or simply pointing at the\nwrong location -- it recreates the gitfile with the correct content.\n\n> sequence, the user does\n>\n>   user goes to a directory D, thinking it is a valid secondary\n>   worktree and wanting to work in it, and finds that Git does not\n>   think it is.\n>\n>   user goes to X, which is the location of the primary copy, and\n>   tells \"worktree repair\" that the directory D ought to be the top\n>   of the working tree for a secondary worktree.\n>\n>   \"worktree repair\" creates/updates D/.git gitfile to point at X.\n>\n>   user comes back to D and starts working.\n>\n> because \"git worktree repair\" wants to do the usual setup sequence\n> anyway.\n\nCorrect. Perhaps not 100% intuitive now that you mention it but that\nsort of interface could be built atop the machinery provided by this\npatch series, and I don't think this initial implementation needs to\nhave all the bells and whistles. Even with what is implemented by this\nseries, support becomes much easier -- simply ask the user to run \"git\nworktree repair\" in the main worktree (or in any functional worktree)\n-- than the situation presently which requires precise instructions\nabout how to patch administrative files by hand.\n\n> > Move a working tree to a new location. Note that the main working tree\n> > -or linked working trees containing submodules cannot be moved.\n> > +or linked working trees containing submodules cannot be moved with this\n> > +command. (The `git worktree repair` command, however, can reestablish\n> > +the connection with linked working trees if you move the main working\n> > +tree manually.)\n>\n> ... meaning after moving the main working tree, repair can be used\n> to touch-up the submodule directories?\n>\n> Ah, no. You are saying \"git worktree move\" still cannot be used to\n> move the main working tree (the ones with submodules in it we do not\n> even care), but as a workaround, the user can \"mv\" it manually and\n> run \"repair\" to fix all the secondary worktrees.\n\nSeeing as the meaning wasn't clear upon your first reading, perhaps I\nshould remove this change? Or rewrite it in some way?\n\n> Hopefully somewhere in the rest of this series it would become\n> automatic? After all, instead of letting users \"mv\" things without\n> us knowing what is going on, if we let them say \"worktree move\" the\n> primary working tree, we know both the source and the destination\n> directories of such a move, and because we know all the secondary\n> worktrees, we can run \"worktree repair\" on them as part of this\n> \"worktree move\" of the primary working tree.\n>\n> It is perfectly fine that it is not happening here in this step to\n> keep each step digestible, of course.\n\nDuy's original implementation did automatically repair worktree\nadministrative files but it ended up being buggy (I don't recall the\nprecise details) and discussion between the two of you resulted in the\nfunctionality being removed with the possibility of resurrecting it\nlater. (Unfortunately, I can't find references to any of the\ndiscussion right now.)\n\nAutomatic repair was definitely on my mind as I wrote this series, but\nas something which could possibly be added later atop the machinery\nimplemented by this series.\n\n> > +test_corrupt_gitlink () {\n>\n> See gitglossary::gitfile. We'd need to find all references to\n> \"gitlink\" and \"git link\" in this series and decide which ones need\n> to be fixed (there might be genuine references to gitlink---I\n> haven't read the series through).\n\nThey all need to be fixed. There aren't any genuine gitlink references\nin the series.\n\n> > +static void repair_dotgit(struct worktree *wt,\n> > +            worktree_repair_cb *cb, void *cb_data)\n>\n> \"dotgit\" is an OK name in this context, I would think. repair_gitfile\n> is also fine, but \"dotgit\" may be more explicit.\n\nI think I originally called the function repair_gitlink() but changed\nit to \"dotgit\" to be more explicit. But now that I know \"gitfile\" is\nthe correct term, repair_gitfile() seems preferable.\n\n> > +   /* missing worktree can't be repaired */\n> > +   if (!file_exists(wt->path))\n> > +       return;\n>\n> Shouldn't it be a bit stronger? if wt->path is not a directory, it\n> cannot be the top of the working tree of a secondary worktree.\n\nI'm not quite sure what you're asking. This code is saying that if the\npath recorded in .git/worktrees/<id>/gitdir doesn't exist, then there\nsimply isn't anything we can repair.\n\n> > +   strbuf_realpath(&repo, git_common_path(\"worktrees/%s\", wt->id), 1);\n>\n> OK, the need to use git_common_path() is a good justification why it\n> is easier to implement if we initialize ourselves using the primary\n> copy's repository data (i.e. require the user to start \"worktree\n> repair\" in the primary copy or any working secondary worktrees, and\n> let the normal setup.c sequence to prime us in the repository),\n> instead of allowing the user to start at the secondary worktree\n> whose gitfile got broken.\n\nYes, and this does highlight that there are conditions under which\n\"git worktree repair\" simply won't operate. I did consider handling a\nnumber of such conditions but ultimately decided that I'd rather keep\nthis initial implementation as simple and straightforward as\npossible. Such improvements can be built atop.\n\n> > +   if (repair) {\n> > +       cb(0, wt->path, repair, cb_data);\n> > +       write_file(dotgit.buf, \"gitdir: %s\", repo.buf);\n>\n> Shouldn't write_file() be monitored for its failure, and the failure\n> reported back to the callback function?\n\nYes and no. write_file() will die() if it can't write the file. That's\nnot necessarily ideal behavior for a command which wants to repair\n_all_ problems, and the decision to use write_file() was not one I\nmade lightly. The bigger picture is that, although \"git worktree\nrepair\" ideally should not abort when attempting repairs, there are a\nnumber of library functions called by \"git worktree repair\" which\nundesirably die(). And writing all that functionality from scratch or\nenhancing those functions to have \"gentle\" modes would have made this\nseries far longer and far more complex. So, I eventually decided to\nkeep the implementation simple, with the idea that such enhancements\ncan be made later as needed and as we gain experience with the\ncommand.\n\n> > +void repair_worktrees(worktree_repair_cb *cb, void *cb_data)\n> > +{\n> > +   struct worktree **worktrees = get_worktrees();\n> > +   struct worktree **wt = worktrees + 1; /* +1 skips main worktree */\n> > +\n> > +   if (!cb)\n> > +       cb = repair_noop_cb;\n> > +   for (; *wt; wt++)\n> > +       repair_dotgit(*wt, cb, cb_data);\n> > +   free_worktrees(worktrees);\n> > +}\n>\n> This \"repair\"s only in one direction. Manual movement of secondary\n> worktrees, if we want to support repairing, needs the user to tell\n> where the new location of the secondary is, and we need a code to\n> update the record of the location of the secondary kept at the main\n> worktree, which is not needed in the repair implemented by this\n> step.\n\nCorrect. Patch [3/5] implements repair in the other direction.\n\n> Looking good.\n\nThanks for the thoughtful review comments (including the ones to which\nI didn't respond directly -- they will be addressed by the re-roll\nitself).\n"},{"id":"404766","messageId":"CAPig+cT-w6LV490MGNyG_ihWkSzdgfnEBrjQCsafjndTRmMgFA@mail.gmail.com","threadId":"54140","inReplyTo":"xmqqh7soqady.fsf@gitster.c.googlers.com","subject":"Re: [PATCH 3/5] worktree: teach \"repair\" to fix outgoing links to worktrees","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2020-08-30T07:36:38Z","receivedAt":"2020-08-30T07:36:57Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Thu, Aug 27, 2020 at 1:14 PM Junio C Hamano <gitster@pobox.com> wrote:\n> Eric Sunshine <sunshine@sunshineco.com> writes:\n> > Therefore, teach \"git worktree repair\" how to repair broken or outdated\n> > .git/worktrees/<id>/gitdir files automatically. (For this to work, the\n> > command must either be invoked from within the worktree whose gitdir\n> > file requires repair, or from within the main or any linked worktree by\n> > providing the path of the broken worktree as an argument to \"git\n> > worktree repair\".)\n>\n> Would git \"work\" in a corrupt worktree whose gitfile is broken, in\n> the sense that it notices that the cwd is the top of the working\n> tree of a secondary worktree?\n\nNo. For this repair to work, the gitfile in the linked worktree must\nbe intact; it must be pointing back at the .git/worktrees/<id>\ndirectory so that \"git worktree repair\" can repair the corresponding\n.git/worktrees/<id>/gitdir file.\n\nMaking it \"work\" for the case when both the worktree's gitfile is\nbroken and .git/worktrees/<id>/gitdir is broken would require an\nenhancement like what you mentioned in your review of patch [2/5] in\nwhich the user would manually specify the location of the main\nworktree (or repository). That is something which can be added, but I\nwanted to keep this initial implementation simple.\n\n> Hmph, if the secondary is _moved_, how would \"worktree repair $path\"\n> would know which <id> the $path corresponds to? Would we just cull\n> all the <id> that do not point at working secondary worktrees and\n> add the $path as if it were a new one by allocating a new <id>, or\n> reusing a randomly chosen <id> that points at a non-existing\n> location?\n\nSince this can only work if the linked worktree's gitfile is intact,\nand since the content of the gitfile is the path .git/worktrees/<id>,\n\"git worktree repair\" knows the exact <id>, thus the precise\n.git/worktrees/<id>/gitdir file to repair. It is deterministic; there\nis no guessing about <id>, and there is no creating a new <id>\nmagically (though I did consider additional repair cases but opted\nagainst them for the initial implementation).\n"},{"id":"404788","messageId":"20200831065800.62502-2-sunshine@sunshineco.com","threadId":"54140","inReplyTo":"20200831065800.62502-1-sunshine@sunshineco.com","subject":"[PATCH v2 1/5] worktree: add skeleton \"repair\" command","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2020-08-31T06:57:56Z","receivedAt":"2020-08-31T06:59:12Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"Worktree administrative files can become corrupted or outdated due to\nexternal factors. Although, it is often possible to recover from such\nsituations by hand-tweaking these files, doing so requires intimate\nknowledge of worktree internals. While information necessary to make\nsuch repairs manually can be obtained from git-worktree.txt and\ngitrepository-layout.txt, we can assist users more directly by teaching\ngit-worktree how to repair its administrative files itself (at least to\nsome extent). Therefore, add a \"git worktree repair\" command which\nattempts to correct common problems which may arise due to factors\nbeyond Git's control.\n\nAt this stage, the \"repair\" command is a mere skeleton; subsequent\ncommits will flesh out the functionality.\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n Documentation/git-worktree.txt |  6 ++++++\n builtin/worktree.c             | 15 +++++++++++++++\n t/t2406-worktree-repair.sh     | 11 +++++++++++\n 3 files changed, 32 insertions(+)\n create mode 100755 t/t2406-worktree-repair.sh\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex 6ee6ec7982..ae432d39a8 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -15,6 +15,7 @@ SYNOPSIS\n 'git worktree move' <worktree> <new-path>\n 'git worktree prune' [-n] [-v] [--expire <expire>]\n 'git worktree remove' [-f] <worktree>\n+'git worktree repair'\n 'git worktree unlock' <worktree>\n \n DESCRIPTION\n@@ -110,6 +111,11 @@ and no modification in tracked files) can be removed. Unclean working\n trees or ones with submodules can be removed with `--force`. The main\n working tree cannot be removed.\n \n+repair::\n+\n+Repair working tree administrative files, if possible, if they have\n+become corrupted or outdated due to external factors.\n+\n unlock::\n \n Unlock a working tree, allowing it to be pruned, moved or deleted.\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 378f332b5d..88af412d4f 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -1030,6 +1030,19 @@ static int remove_worktree(int ac, const char **av, const char *prefix)\n \treturn ret;\n }\n \n+static int repair(int ac, const char **av, const char *prefix)\n+{\n+\tstruct option options[] = {\n+\t\tOPT_END()\n+\t};\n+\tint rc = 0;\n+\n+\tac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n+\tif (ac)\n+\t\tusage_with_options(worktree_usage, options);\n+\treturn rc;\n+}\n+\n int cmd_worktree(int ac, const char **av, const char *prefix)\n {\n \tstruct option options[] = {\n@@ -1056,5 +1069,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)\n \t\treturn move_worktree(ac - 1, av + 1, prefix);\n \tif (!strcmp(av[1], \"remove\"))\n \t\treturn remove_worktree(ac - 1, av + 1, prefix);\n+\tif (!strcmp(av[1], \"repair\"))\n+\t\treturn repair(ac - 1, av + 1, prefix);\n \tusage_with_options(worktree_usage, options);\n }\ndiff --git a/t/t2406-worktree-repair.sh b/t/t2406-worktree-repair.sh\nnew file mode 100755\nindex 0000000000..cc679e1a21\n--- /dev/null\n+++ b/t/t2406-worktree-repair.sh\n@@ -0,0 +1,11 @@\n+#!/bin/sh\n+\n+test_description='test git worktree repair'\n+\n+. ./test-lib.sh\n+\n+test_expect_success setup '\n+\ttest_commit init\n+'\n+\n+test_done\n-- \n2.28.0.531.g41c3d8a546\n\n"},{"id":"404789","messageId":"20200831065800.62502-1-sunshine@sunshineco.com","threadId":"54140","inReplyTo":"20200827082129.56149-1-sunshine@sunshineco.com","subject":"[PATCH v2 0/5] add \"git worktree repair\" command","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2020-08-31T06:57:55Z","receivedAt":"2020-08-31T06:59:14Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"This is a re-roll of [1] which adds a \"git worktree repair\" command\nand which fixes bugs with \"git init --separate-git-dir\" related to\nworktrees.\n\nChanges since v1:\n\n* employ proper terminology s/gitlink/gitfile/g (junio[2])\n* tighten documentation slightly (junio[2])\n* rename callback function and typedef s/cb/fn/g (junio[2])\n* give concrete callback function more meaningful name (junio[2])\n* employ explicit name for callback data (junio[2])\n* explicitly check that worktree path is directory, not file, and bail\n  with meaningful error message; add associated test (junio[2])\n* fix empty-function style violation (junio[2])\n* refine worktree repair callback typedef (sunshine)\n* pacify GCC -Werror=main (dscho[3])\n* pacify Sparse s/0/NULL/ (ramsay[4])\n\n[1]: https://lore.kernel.org/git/20200827082129.56149-1-sunshine@sunshineco.com/\n[2]: https://lore.kernel.org/git/xmqqlfi0qaru.fsf@gitster.c.googlers.com/\n[3]: https://lore.kernel.org/git/nycvar.QRO.7.76.6.2008280413450.56@tvgsbejvaqbjf.bet/\n[4]: https://lore.kernel.org/git/8d4b4011-b8a2-c0e0-a3f2-28c7bbec040b@ramsayjones.plus.com/\n\nEric Sunshine (5):\n  worktree: add skeleton \"repair\" command\n  worktree: teach \"repair\" to fix worktree back-links to main worktree\n  worktree: teach \"repair\" to fix outgoing links to worktrees\n  init: teach --separate-git-dir to repair linked worktrees\n  init: make --separate-git-dir work from within linked worktree\n\n Documentation/git-worktree.txt |  26 ++++-\n builtin/init-db.c              |  26 +++++\n builtin/worktree.c             |  30 ++++++\n t/t0001-init.sh                |  28 ++++++\n t/t2406-worktree-repair.sh     | 179 +++++++++++++++++++++++++++++++++\n worktree.c                     | 135 +++++++++++++++++++++++++\n worktree.h                     |  23 +++++\n 7 files changed, 445 insertions(+), 2 deletions(-)\n create mode 100755 t/t2406-worktree-repair.sh\n\nInterdiff against v1:\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex a43a0af0af..f70cda4b36 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -121,8 +121,8 @@ become corrupted or outdated due to external factors.\n +\n For instance, if the main working tree (or bare repository) is moved,\n linked working trees will be unable to locate it. Running `repair` in\n-the recently-moved main working tree will reestablish the connection\n-from linked working trees back to the main working tree.\n+the main working tree will reestablish the connection from linked\n+working trees back to the main working tree.\n +\n Similarly, if a linked working tree is moved without using `git worktree\n move`, the main working tree (or bare repository) will be unable to\ndiff --git a/builtin/init-db.c b/builtin/init-db.c\nindex 6a94d20a2e..cd3e760541 100644\n--- a/builtin/init-db.c\n+++ b/builtin/init-db.c\n@@ -661,7 +661,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)\n \t\t\tif (chdir(mainwt.buf) < 0)\n \t\t\t\tdie_errno(_(\"cannot chdir to %s\"), mainwt.buf);\n \t\t\tstrbuf_release(&mainwt);\n-\t\t\tgit_dir = strbuf_detach(&sb, 0);\n+\t\t\tgit_dir = strbuf_detach(&sb, NULL);\n \t\t}\n \t\tstrbuf_release(&sb);\n \t}\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 19bbc246ad..8165343145 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -1030,13 +1030,14 @@ static int remove_worktree(int ac, const char **av, const char *prefix)\n \treturn ret;\n }\n \n-static void repair_cb(int iserr, const char *path, const char *msg, void *cb_data)\n+static void report_repair(int iserr, const char *path, const char *msg, void *cb_data)\n {\n-\tif (!iserr)\n+\tif (!iserr) {\n \t\tprintf_ln(_(\"repair: %s: %s\"), msg, path);\n-\telse {\n+\t} else {\n+\t\tint *exit_status = (int *)cb_data;\n \t\tfprintf_ln(stderr, _(\"error: %s: %s\"), msg, path);\n-\t\t*(int *)cb_data = 1;\n+\t\t*exit_status = 1;\n \t}\n }\n \n@@ -1050,10 +1051,10 @@ static int repair(int ac, const char **av, const char *prefix)\n \tint rc = 0;\n \n \tac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n-\trepair_worktrees(repair_cb, &rc);\n+\trepair_worktrees(report_repair, &rc);\n \tp = ac > 0 ? av : self;\n \tfor (; *p; p++)\n-\t\trepair_worktree_at_path(*p, repair_cb, &rc);\n+\t\trepair_worktree_at_path(*p, report_repair, &rc);\n \treturn rc;\n }\n \ndiff --git a/t/t2406-worktree-repair.sh b/t/t2406-worktree-repair.sh\nindex 87bd8fc526..1fe468bfe8 100755\n--- a/t/t2406-worktree-repair.sh\n+++ b/t/t2406-worktree-repair.sh\n@@ -17,6 +17,16 @@ test_expect_success 'skip missing worktree' '\n \ttest_must_be_empty err\n '\n \n+test_expect_success 'worktree path not directory' '\n+\ttest_when_finished \"git worktree prune\" &&\n+\tgit worktree add --detach notdir &&\n+\trm -rf notdir &&\n+\t>notdir &&\n+\ttest_must_fail git worktree repair >out 2>err &&\n+\ttest_must_be_empty out &&\n+\ttest_i18ngrep \"not a directory\" err\n+'\n+\n test_expect_success \"don't clobber .git repo\" '\n \ttest_when_finished \"rm -rf repo && git worktree prune\" &&\n \tgit worktree add --detach repo &&\n@@ -27,7 +37,7 @@ test_expect_success \"don't clobber .git repo\" '\n \ttest_i18ngrep \".git is not a file\" err\n '\n \n-test_corrupt_gitlink () {\n+test_corrupt_gitfile () {\n \tbutcher=$1 &&\n \tproblem=$2 &&\n \trepairdir=${3:-.} &&\n@@ -43,30 +53,30 @@ test_corrupt_gitlink () {\n }\n \n test_expect_success 'repair missing .git file' '\n-\ttest_corrupt_gitlink \"rm -f corrupt/.git\" \".git link broken\"\n+\ttest_corrupt_gitfile \"rm -f corrupt/.git\" \".git file broken\"\n '\n \n test_expect_success 'repair bogus .git file' '\n-\ttest_corrupt_gitlink \"echo \\\"gitdir: /nowhere\\\" >corrupt/.git\" \\\n-\t\t\".git link broken\"\n+\ttest_corrupt_gitfile \"echo \\\"gitdir: /nowhere\\\" >corrupt/.git\" \\\n+\t\t\".git file broken\"\n '\n \n test_expect_success 'repair incorrect .git file' '\n \ttest_when_finished \"rm -rf other && git worktree prune\" &&\n \ttest_create_repo other &&\n \tother=$(git -C other rev-parse --absolute-git-dir) &&\n-\ttest_corrupt_gitlink \"echo \\\"gitdir: $other\\\" >corrupt/.git\" \\\n-\t\t\".git link incorrect\"\n+\ttest_corrupt_gitfile \"echo \\\"gitdir: $other\\\" >corrupt/.git\" \\\n+\t\t\".git file incorrect\"\n '\n \n test_expect_success 'repair .git file from main/.git' '\n-\ttest_corrupt_gitlink \"rm -f corrupt/.git\" \".git link broken\" .git\n+\ttest_corrupt_gitfile \"rm -f corrupt/.git\" \".git file broken\" .git\n '\n \n test_expect_success 'repair .git file from linked worktree' '\n \ttest_when_finished \"rm -rf other && git worktree prune\" &&\n \tgit worktree add --detach other &&\n-\ttest_corrupt_gitlink \"rm -f corrupt/.git\" \".git link broken\" other\n+\ttest_corrupt_gitfile \"rm -f corrupt/.git\" \".git file broken\" other\n '\n \n test_expect_success 'repair .git file from bare.git' '\n@@ -94,14 +104,14 @@ test_expect_success 'repo not found; .git not file' '\n \ttest_i18ngrep \".git is not a file\" err\n '\n \n-test_expect_success 'repo not found; .git link broken' '\n+test_expect_success 'repo not found; .git file broken' '\n \ttest_when_finished \"rm -rf orig moved && git worktree prune\" &&\n \tgit worktree add --detach orig &&\n \techo /invalid >orig/.git &&\n \tmv orig moved &&\n \ttest_must_fail git worktree repair moved >out 2>err &&\n \ttest_must_be_empty out &&\n-\ttest_i18ngrep \".git link broken\" err\n+\ttest_i18ngrep \".git file broken\" err\n '\n \n test_expect_success 'repair broken gitdir' '\ndiff --git a/worktree.c b/worktree.c\nindex 6ade4f0d8b..46a5fb8447 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -573,11 +573,11 @@ int other_head_refs(each_ref_fn fn, void *cb_data)\n }\n \n /*\n- * Repair worktree's /path/to/worktree/.git link if missing, corrupt, or not\n+ * Repair worktree's /path/to/worktree/.git file if missing, corrupt, or not\n  * pointing at <repo>/worktrees/<id>.\n  */\n-static void repair_dotgit(struct worktree *wt,\n-\t\t\t  worktree_repair_cb *cb, void *cb_data)\n+static void repair_gitfile(struct worktree *wt,\n+\t\t\t   worktree_repair_fn fn, void *cb_data)\n {\n \tstruct strbuf dotgit = STRBUF_INIT;\n \tstruct strbuf repo = STRBUF_INIT;\n@@ -589,19 +589,24 @@ static void repair_dotgit(struct worktree *wt,\n \tif (!file_exists(wt->path))\n \t\treturn;\n \n+\tif (!is_directory(wt->path)) {\n+\t\tfn(1, wt->path, _(\"not a directory\"), cb_data);\n+\t\treturn;\n+\t}\n+\n \tstrbuf_realpath(&repo, git_common_path(\"worktrees/%s\", wt->id), 1);\n \tstrbuf_addf(&dotgit, \"%s/.git\", wt->path);\n \tbacklink = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err));\n \n \tif (err == READ_GITFILE_ERR_NOT_A_FILE)\n-\t\tcb(1, wt->path, _(\".git is not a file\"), cb_data);\n+\t\tfn(1, wt->path, _(\".git is not a file\"), cb_data);\n \telse if (err)\n-\t\trepair = _(\".git link broken\");\n+\t\trepair = _(\".git file broken\");\n \telse if (fspathcmp(backlink, repo.buf))\n-\t\trepair = _(\".git link incorrect\");\n+\t\trepair = _(\".git file incorrect\");\n \n \tif (repair) {\n-\t\tcb(0, wt->path, repair, cb_data);\n+\t\tfn(0, wt->path, repair, cb_data);\n \t\twrite_file(dotgit.buf, \"gitdir: %s\", repo.buf);\n \t}\n \n@@ -610,34 +615,37 @@ static void repair_dotgit(struct worktree *wt,\n \tstrbuf_release(&dotgit);\n }\n \n-static void repair_noop_cb(int iserr, const char *path, const char *msg,\n-\t\t\t   void *cb_data) {}\n+static void repair_noop(int iserr, const char *path, const char *msg,\n+\t\t\tvoid *cb_data)\n+{\n+\t/* nothing */\n+}\n \n-void repair_worktrees(worktree_repair_cb *cb, void *cb_data)\n+void repair_worktrees(worktree_repair_fn fn, void *cb_data)\n {\n \tstruct worktree **worktrees = get_worktrees();\n \tstruct worktree **wt = worktrees + 1; /* +1 skips main worktree */\n \n-\tif (!cb)\n-\t\tcb = repair_noop_cb;\n+\tif (!fn)\n+\t\tfn = repair_noop;\n \tfor (; *wt; wt++)\n-\t\trepair_dotgit(*wt, cb, cb_data);\n+\t\trepair_gitfile(*wt, fn, cb_data);\n \tfree_worktrees(worktrees);\n }\n \n static int is_main_worktree_path(const char *path)\n {\n \tstruct strbuf target = STRBUF_INIT;\n-\tstruct strbuf main = STRBUF_INIT;\n+\tstruct strbuf maindir = STRBUF_INIT;\n \tint cmp;\n \n \tstrbuf_add_real_path(&target, path);\n \tstrbuf_strip_suffix(&target, \"/.git\");\n-\tstrbuf_add_real_path(&main, get_git_common_dir());\n-\tstrbuf_strip_suffix(&main, \"/.git\");\n-\tcmp = fspathcmp(main.buf, target.buf);\n+\tstrbuf_add_real_path(&maindir, get_git_common_dir());\n+\tstrbuf_strip_suffix(&maindir, \"/.git\");\n+\tcmp = fspathcmp(maindir.buf, target.buf);\n \n-\tstrbuf_release(&main);\n+\tstrbuf_release(&maindir);\n \tstrbuf_release(&target);\n \treturn !cmp;\n }\n@@ -647,7 +655,7 @@ static int is_main_worktree_path(const char *path)\n  * the worktree's path.\n  */\n void repair_worktree_at_path(const char *path,\n-\t\t\t     worktree_repair_cb *cb, void *cb_data)\n+\t\t\t     worktree_repair_fn fn, void *cb_data)\n {\n \tstruct strbuf dotgit = STRBUF_INIT;\n \tstruct strbuf realdotgit = STRBUF_INIT;\n@@ -657,24 +665,24 @@ void repair_worktree_at_path(const char *path,\n \tconst char *repair = NULL;\n \tint err;\n \n-\tif (!cb)\n-\t\tcb = repair_noop_cb;\n+\tif (!fn)\n+\t\tfn = repair_noop;\n \n \tif (is_main_worktree_path(path))\n \t\tgoto done;\n \n \tstrbuf_addf(&dotgit, \"%s/.git\", path);\n \tif (!strbuf_realpath(&realdotgit, dotgit.buf, 0)) {\n-\t\tcb(1, path, _(\"not a valid path\"), cb_data);\n+\t\tfn(1, path, _(\"not a valid path\"), cb_data);\n \t\tgoto done;\n \t}\n \n \tbacklink = xstrdup_or_null(read_gitfile_gently(realdotgit.buf, &err));\n \tif (err == READ_GITFILE_ERR_NOT_A_FILE) {\n-\t\tcb(1, realdotgit.buf, _(\"unable to locate repository; .git is not a file\"), cb_data);\n+\t\tfn(1, realdotgit.buf, _(\"unable to locate repository; .git is not a file\"), cb_data);\n \t\tgoto done;\n \t} else if (err) {\n-\t\tcb(1, realdotgit.buf, _(\"unable to locate repository; .git link broken\"), cb_data);\n+\t\tfn(1, realdotgit.buf, _(\"unable to locate repository; .git file broken\"), cb_data);\n \t\tgoto done;\n \t}\n \n@@ -688,7 +696,7 @@ void repair_worktree_at_path(const char *path,\n \t}\n \n \tif (repair) {\n-\t\tcb(0, gitdir.buf, repair, cb_data);\n+\t\tfn(0, gitdir.buf, repair, cb_data);\n \t\twrite_file(gitdir.buf, \"%s\", realdotgit.buf);\n \t}\n done:\ndiff --git a/worktree.h b/worktree.h\nindex 9cb7f05741..ff7b62e434 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -89,27 +89,28 @@ int validate_worktree(const struct worktree *wt,\n void update_worktree_location(struct worktree *wt,\n \t\t\t      const char *path_);\n \n-typedef void worktree_repair_cb(int iserr, const char *path, const char *msg,\n-\t\t\t\tvoid *cb_data);\n+typedef void (* worktree_repair_fn)(int iserr, const char *path,\n+\t\t\t\t    const char *msg, void *cb_data);\n \n /*\n  * Visit each registered linked worktree and repair corruptions. For each\n- * repair made or error encountered while attempting a repair, the callback, if\n- * non-NULL, is called with the path of the worktree and a description of the\n- * repair or error, along with the callback user-data.\n+ * repair made or error encountered while attempting a repair, the callback\n+ * function, if non-NULL, is called with the path of the worktree and a\n+ * description of the repair or error, along with the callback user-data.\n  */\n-void repair_worktrees(worktree_repair_cb *, void *cb_data);\n+void repair_worktrees(worktree_repair_fn, void *cb_data);\n \n /*\n  * Repair administrative files corresponding to the worktree at the given path.\n- * The worktree's .git link pointing at the repository must be intact for the\n+ * The worktree's .git file pointing at the repository must be intact for the\n  * repair to succeed. Useful for re-associating an orphaned worktree with the\n  * repository if the worktree has been moved manually (without using \"git\n  * worktree move\"). For each repair made or error encountered while attempting\n- * a repair, the callback, if non-NULL, is called with the path of the worktree\n- * and a description of the repair or error, along with the callback user-data.\n+ * a repair, the callback function, if non-NULL, is called with the path of the\n+ * worktree and a description of the repair or error, along with the callback\n+ * user-data.\n  */\n-void repair_worktree_at_path(const char *, worktree_repair_cb *, void *cb_data);\n+void repair_worktree_at_path(const char *, worktree_repair_fn, void *cb_data);\n \n /*\n  * Free up the memory for worktree(s)\n-- \n2.28.0.531.g41c3d8a546\n\n"},{"id":"404790","messageId":"20200831065800.62502-4-sunshine@sunshineco.com","threadId":"54140","inReplyTo":"20200831065800.62502-1-sunshine@sunshineco.com","subject":"[PATCH v2 3/5] worktree: teach \"repair\" to fix outgoing links to worktrees","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2020-08-31T06:57:58Z","receivedAt":"2020-08-31T06:59:21Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"The .git/worktrees/<id>/gitdir file points at the location of a linked\nworktree's .git file. Its content must be of the form\n/path/to/worktree/.git (from which the location of the worktree itself\ncan be derived by stripping the \"/.git\" suffix). If the gitdir file is\ndeleted or becomes corrupted or outdated, then Git will be unable to\nfind the linked worktree. An easy way for the gitdir file to become\noutdated is for the user to move the worktree manually (without using\n\"git worktree move\"). Although it is possible to manually update the\ngitdir file to reflect the new linked worktree location, doing so\nrequires a level of knowledge about worktree internals beyond what a\nuser should be expected to know offhand.\n\nTherefore, teach \"git worktree repair\" how to repair broken or outdated\n.git/worktrees/<id>/gitdir files automatically. (For this to work, the\ncommand must either be invoked from within the worktree whose gitdir\nfile requires repair, or from within the main or any linked worktree by\nproviding the path of the broken worktree as an argument to \"git\nworktree repair\".)\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n Documentation/git-worktree.txt | 14 ++++--\n builtin/worktree.c             |  7 ++-\n t/t2406-worktree-repair.sh     | 86 ++++++++++++++++++++++++++++++++++\n worktree.c                     | 74 +++++++++++++++++++++++++++++\n worktree.h                     | 12 +++++\n 5 files changed, 188 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex 34fe47cecd..f70cda4b36 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -15,7 +15,7 @@ SYNOPSIS\n 'git worktree move' <worktree> <new-path>\n 'git worktree prune' [-n] [-v] [--expire <expire>]\n 'git worktree remove' [-f] <worktree>\n-'git worktree repair'\n+'git worktree repair' [<path>...]\n 'git worktree unlock' <worktree>\n \n DESCRIPTION\n@@ -114,7 +114,7 @@ and no modification in tracked files) can be removed. Unclean working\n trees or ones with submodules can be removed with `--force`. The main\n working tree cannot be removed.\n \n-repair::\n+repair [<path>...]::\n \n Repair working tree administrative files, if possible, if they have\n become corrupted or outdated due to external factors.\n@@ -123,6 +123,13 @@ For instance, if the main working tree (or bare repository) is moved,\n linked working trees will be unable to locate it. Running `repair` in\n the main working tree will reestablish the connection from linked\n working trees back to the main working tree.\n++\n+Similarly, if a linked working tree is moved without using `git worktree\n+move`, the main working tree (or bare repository) will be unable to\n+locate it. Running `repair` within the recently-moved working tree will\n+reestablish the connection. If multiple linked working trees are moved,\n+running `repair` from any working tree with each tree's new `<path>` as\n+an argument, will reestablish the connection to all the specified paths.\n \n unlock::\n \n@@ -317,7 +324,8 @@ in the entry's directory. For example, if a linked working tree is moved\n to `/newpath/test-next` and its `.git` file points to\n `/path/main/.git/worktrees/test-next`, then update\n `/path/main/.git/worktrees/test-next/gitdir` to reference `/newpath/test-next`\n-instead.\n+instead. Better yet, run `git worktree repair` to reestablish the connection\n+automatically.\n \n To prevent a `$GIT_DIR/worktrees` entry from being pruned (which\n can be useful in some situations, such as when the\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 68b0032428..8165343145 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -1043,15 +1043,18 @@ static void report_repair(int iserr, const char *path, const char *msg, void *cb\n \n static int repair(int ac, const char **av, const char *prefix)\n {\n+\tconst char **p;\n+\tconst char *self[] = { \".\", NULL };\n \tstruct option options[] = {\n \t\tOPT_END()\n \t};\n \tint rc = 0;\n \n \tac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n-\tif (ac)\n-\t\tusage_with_options(worktree_usage, options);\n \trepair_worktrees(report_repair, &rc);\n+\tp = ac > 0 ? av : self;\n+\tfor (; *p; p++)\n+\t\trepair_worktree_at_path(*p, report_repair, &rc);\n \treturn rc;\n }\n \ndiff --git a/t/t2406-worktree-repair.sh b/t/t2406-worktree-repair.sh\nindex ef59cdce95..1fe468bfe8 100755\n--- a/t/t2406-worktree-repair.sh\n+++ b/t/t2406-worktree-repair.sh\n@@ -90,4 +90,90 @@ test_expect_success 'repair .git file from bare.git' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'invalid worktree path' '\n+\ttest_must_fail git worktree repair /notvalid >out 2>err &&\n+\ttest_must_be_empty out &&\n+\ttest_i18ngrep \"not a valid path\" err\n+'\n+\n+test_expect_success 'repo not found; .git not file' '\n+\ttest_when_finished \"rm -rf not-a-worktree\" &&\n+\ttest_create_repo not-a-worktree &&\n+\ttest_must_fail git worktree repair not-a-worktree >out 2>err &&\n+\ttest_must_be_empty out &&\n+\ttest_i18ngrep \".git is not a file\" err\n+'\n+\n+test_expect_success 'repo not found; .git file broken' '\n+\ttest_when_finished \"rm -rf orig moved && git worktree prune\" &&\n+\tgit worktree add --detach orig &&\n+\techo /invalid >orig/.git &&\n+\tmv orig moved &&\n+\ttest_must_fail git worktree repair moved >out 2>err &&\n+\ttest_must_be_empty out &&\n+\ttest_i18ngrep \".git file broken\" err\n+'\n+\n+test_expect_success 'repair broken gitdir' '\n+\ttest_when_finished \"rm -rf orig moved && git worktree prune\" &&\n+\tgit worktree add --detach orig &&\n+\tsed s,orig/\\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&\n+\trm .git/worktrees/orig/gitdir &&\n+\tmv orig moved &&\n+\tgit worktree repair moved >out 2>err &&\n+\ttest_cmp expect .git/worktrees/orig/gitdir &&\n+\ttest_i18ngrep \"gitdir unreadable\" out &&\n+\ttest_must_be_empty err\n+'\n+\n+test_expect_success 'repair incorrect gitdir' '\n+\ttest_when_finished \"rm -rf orig moved && git worktree prune\" &&\n+\tgit worktree add --detach orig &&\n+\tsed s,orig/\\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&\n+\tmv orig moved &&\n+\tgit worktree repair moved >out 2>err &&\n+\ttest_cmp expect .git/worktrees/orig/gitdir &&\n+\ttest_i18ngrep \"gitdir incorrect\" out &&\n+\ttest_must_be_empty err\n+'\n+\n+test_expect_success 'repair gitdir (implicit) from linked worktree' '\n+\ttest_when_finished \"rm -rf orig moved && git worktree prune\" &&\n+\tgit worktree add --detach orig &&\n+\tsed s,orig/\\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&\n+\tmv orig moved &&\n+\tgit -C moved worktree repair >out 2>err &&\n+\ttest_cmp expect .git/worktrees/orig/gitdir &&\n+\ttest_i18ngrep \"gitdir incorrect\" out &&\n+\ttest_must_be_empty err\n+'\n+\n+test_expect_success 'unable to repair gitdir (implicit) from main worktree' '\n+\ttest_when_finished \"rm -rf orig moved && git worktree prune\" &&\n+\tgit worktree add --detach orig &&\n+\tcat .git/worktrees/orig/gitdir >expect &&\n+\tmv orig moved &&\n+\tgit worktree repair >out 2>err &&\n+\ttest_cmp expect .git/worktrees/orig/gitdir &&\n+\ttest_must_be_empty out &&\n+\ttest_must_be_empty err\n+'\n+\n+test_expect_success 'repair multiple gitdir files' '\n+\ttest_when_finished \"rm -rf orig1 orig2 moved1 moved2 &&\n+\t\tgit worktree prune\" &&\n+\tgit worktree add --detach orig1 &&\n+\tgit worktree add --detach orig2 &&\n+\tsed s,orig1/\\.git$,moved1/.git, .git/worktrees/orig1/gitdir >expect1 &&\n+\tsed s,orig2/\\.git$,moved2/.git, .git/worktrees/orig2/gitdir >expect2 &&\n+\tmv orig1 moved1 &&\n+\tmv orig2 moved2 &&\n+\tgit worktree repair moved1 moved2 >out 2>err &&\n+\ttest_cmp expect1 .git/worktrees/orig1/gitdir &&\n+\ttest_cmp expect2 .git/worktrees/orig2/gitdir &&\n+\ttest_i18ngrep \"gitdir incorrect:.*orig1/gitdir$\" out &&\n+\ttest_i18ngrep \"gitdir incorrect:.*orig2/gitdir$\" out &&\n+\ttest_must_be_empty err\n+'\n+\n test_done\ndiff --git a/worktree.c b/worktree.c\nindex 3ad93cc4aa..46a5fb8447 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -632,3 +632,77 @@ void repair_worktrees(worktree_repair_fn fn, void *cb_data)\n \t\trepair_gitfile(*wt, fn, cb_data);\n \tfree_worktrees(worktrees);\n }\n+\n+static int is_main_worktree_path(const char *path)\n+{\n+\tstruct strbuf target = STRBUF_INIT;\n+\tstruct strbuf maindir = STRBUF_INIT;\n+\tint cmp;\n+\n+\tstrbuf_add_real_path(&target, path);\n+\tstrbuf_strip_suffix(&target, \"/.git\");\n+\tstrbuf_add_real_path(&maindir, get_git_common_dir());\n+\tstrbuf_strip_suffix(&maindir, \"/.git\");\n+\tcmp = fspathcmp(maindir.buf, target.buf);\n+\n+\tstrbuf_release(&maindir);\n+\tstrbuf_release(&target);\n+\treturn !cmp;\n+}\n+\n+/*\n+ * Repair <repo>/worktrees/<id>/gitdir if missing, corrupt, or not pointing at\n+ * the worktree's path.\n+ */\n+void repair_worktree_at_path(const char *path,\n+\t\t\t     worktree_repair_fn fn, void *cb_data)\n+{\n+\tstruct strbuf dotgit = STRBUF_INIT;\n+\tstruct strbuf realdotgit = STRBUF_INIT;\n+\tstruct strbuf gitdir = STRBUF_INIT;\n+\tstruct strbuf olddotgit = STRBUF_INIT;\n+\tchar *backlink = NULL;\n+\tconst char *repair = NULL;\n+\tint err;\n+\n+\tif (!fn)\n+\t\tfn = repair_noop;\n+\n+\tif (is_main_worktree_path(path))\n+\t\tgoto done;\n+\n+\tstrbuf_addf(&dotgit, \"%s/.git\", path);\n+\tif (!strbuf_realpath(&realdotgit, dotgit.buf, 0)) {\n+\t\tfn(1, path, _(\"not a valid path\"), cb_data);\n+\t\tgoto done;\n+\t}\n+\n+\tbacklink = xstrdup_or_null(read_gitfile_gently(realdotgit.buf, &err));\n+\tif (err == READ_GITFILE_ERR_NOT_A_FILE) {\n+\t\tfn(1, realdotgit.buf, _(\"unable to locate repository; .git is not a file\"), cb_data);\n+\t\tgoto done;\n+\t} else if (err) {\n+\t\tfn(1, realdotgit.buf, _(\"unable to locate repository; .git file broken\"), cb_data);\n+\t\tgoto done;\n+\t}\n+\n+\tstrbuf_addf(&gitdir, \"%s/gitdir\", backlink);\n+\tif (strbuf_read_file(&olddotgit, gitdir.buf, 0) < 0)\n+\t\trepair = _(\"gitdir unreadable\");\n+\telse {\n+\t\tstrbuf_rtrim(&olddotgit);\n+\t\tif (fspathcmp(olddotgit.buf, realdotgit.buf))\n+\t\t\trepair = _(\"gitdir incorrect\");\n+\t}\n+\n+\tif (repair) {\n+\t\tfn(0, gitdir.buf, repair, cb_data);\n+\t\twrite_file(gitdir.buf, \"%s\", realdotgit.buf);\n+\t}\n+done:\n+\tfree(backlink);\n+\tstrbuf_release(&olddotgit);\n+\tstrbuf_release(&gitdir);\n+\tstrbuf_release(&realdotgit);\n+\tstrbuf_release(&dotgit);\n+}\ndiff --git a/worktree.h b/worktree.h\nindex 4fcb01348c..ff7b62e434 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -100,6 +100,18 @@ typedef void (* worktree_repair_fn)(int iserr, const char *path,\n  */\n void repair_worktrees(worktree_repair_fn, void *cb_data);\n \n+/*\n+ * Repair administrative files corresponding to the worktree at the given path.\n+ * The worktree's .git file pointing at the repository must be intact for the\n+ * repair to succeed. Useful for re-associating an orphaned worktree with the\n+ * repository if the worktree has been moved manually (without using \"git\n+ * worktree move\"). For each repair made or error encountered while attempting\n+ * a repair, the callback function, if non-NULL, is called with the path of the\n+ * worktree and a description of the repair or error, along with the callback\n+ * user-data.\n+ */\n+void repair_worktree_at_path(const char *, worktree_repair_fn, void *cb_data);\n+\n /*\n  * Free up the memory for worktree(s)\n  */\n-- \n2.28.0.531.g41c3d8a546\n\n"},{"id":"404791","messageId":"20200831065800.62502-6-sunshine@sunshineco.com","threadId":"54140","inReplyTo":"20200831065800.62502-1-sunshine@sunshineco.com","subject":"[PATCH v2 5/5] init: make --separate-git-dir work from within linked worktree","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2020-08-31T06:58:00Z","receivedAt":"2020-08-31T06:59:25Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"The intention of `git init --separate-work-dir=<path>` is to move the\n.git/ directory to a location outside of the main worktree. When used\nwithin a linked worktree, however, rather than moving the .git/\ndirectory as intended, it instead incorrectly moves the worktree's\n.git/worktrees/<id> directory to <path>, thus disconnecting the linked\nworktree from its parent repository and breaking the worktree in the\nprocess since its local .git file no longer points at a location at\nwhich it can find the object database. Fix this broken behavior.\n\nAn intentional side-effect of this change is that it also closes a\nloophole not caught by ccf236a23a (init: disallow --separate-git-dir\nwith bare repository, 2020-08-09) in which the check to prevent\n--separate-git-dir being used in conjunction with a bare repository was\nunable to detect the invalid combination when invoked from within a\nlinked worktree. Therefore, add a test to verify that this loophole is\nclosed, as well.\n\nReported-by: Henré Botha <henrebotha@gmail.com>\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n builtin/init-db.c | 24 ++++++++++++++++++++++++\n t/t0001-init.sh   | 21 +++++++++++++++++++--\n 2 files changed, 43 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/init-db.c b/builtin/init-db.c\nindex 7b915d88ab..cd3e760541 100644\n--- a/builtin/init-db.c\n+++ b/builtin/init-db.c\n@@ -642,6 +642,30 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)\n \tif (!git_dir)\n \t\tgit_dir = DEFAULT_GIT_DIR_ENVIRONMENT;\n \n+\t/*\n+\t * When --separate-git-dir is used inside a linked worktree, take\n+\t * care to ensure that the common .git/ directory is relocated, not\n+\t * the worktree-specific .git/worktrees/<id>/ directory.\n+\t */\n+\tif (real_git_dir) {\n+\t\tint err;\n+\t\tconst char *p;\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\n+\t\tp = read_gitfile_gently(git_dir, &err);\n+\t\tif (p && get_common_dir(&sb, p)) {\n+\t\t\tstruct strbuf mainwt = STRBUF_INIT;\n+\n+\t\t\tstrbuf_addbuf(&mainwt, &sb);\n+\t\t\tstrbuf_strip_suffix(&mainwt, \"/.git\");\n+\t\t\tif (chdir(mainwt.buf) < 0)\n+\t\t\t\tdie_errno(_(\"cannot chdir to %s\"), mainwt.buf);\n+\t\t\tstrbuf_release(&mainwt);\n+\t\t\tgit_dir = strbuf_detach(&sb, NULL);\n+\t\t}\n+\t\tstrbuf_release(&sb);\n+\t}\n+\n \tif (is_bare_repository_cfg < 0)\n \t\tis_bare_repository_cfg = guess_repository_type(git_dir);\n \ndiff --git a/t/t0001-init.sh b/t/t0001-init.sh\nindex e489eb4ddb..2f7c3dcd0f 100755\n--- a/t/t0001-init.sh\n+++ b/t/t0001-init.sh\n@@ -329,6 +329,15 @@ test_expect_success 'implicit bare & --separate-git-dir incompatible' '\n \ttest_i18ngrep \"incompatible\" err\n '\n \n+test_expect_success 'bare & --separate-git-dir incompatible within worktree' '\n+\ttest_when_finished \"rm -rf bare.git linkwt seprepo\" &&\n+\ttest_commit gumby &&\n+\tgit clone --bare . bare.git &&\n+\tgit -C bare.git worktree add --detach ../linkwt &&\n+\ttest_must_fail git -C linkwt init --separate-git-dir seprepo 2>err &&\n+\ttest_i18ngrep \"incompatible\" err\n+'\n+\n test_lazy_prereq GETCWD_IGNORES_PERMS '\n \tbase=GETCWD_TEST_BASE_DIR &&\n \tmkdir -p $base/dir &&\n@@ -405,15 +414,23 @@ test_expect_success SYMLINKS 're-init to move gitdir symlink' '\n \ttest_path_is_dir realgitdir/refs\n '\n \n-test_expect_success 're-init to move gitdir with linked worktrees' '\n+sep_git_dir_worktree ()  {\n \ttest_when_finished \"rm -rf mainwt linkwt seprepo\" &&\n \tgit init mainwt &&\n \ttest_commit -C mainwt gumby &&\n \tgit -C mainwt worktree add --detach ../linkwt &&\n-\tgit -C mainwt init --separate-git-dir ../seprepo &&\n+\tgit -C \"$1\" init --separate-git-dir ../seprepo &&\n \tgit -C mainwt rev-parse --git-common-dir >expect &&\n \tgit -C linkwt rev-parse --git-common-dir >actual &&\n \ttest_cmp expect actual\n+}\n+\n+test_expect_success 're-init to move gitdir with linked worktrees' '\n+\tsep_git_dir_worktree mainwt\n+'\n+\n+test_expect_success 're-init to move gitdir within linked worktree' '\n+\tsep_git_dir_worktree linkwt\n '\n \n test_expect_success MINGW '.git hidden' '\n-- \n2.28.0.531.g41c3d8a546\n\n"},{"id":"404792","messageId":"20200831065800.62502-3-sunshine@sunshineco.com","threadId":"54140","inReplyTo":"20200831065800.62502-1-sunshine@sunshineco.com","subject":"[PATCH v2 2/5] worktree: teach \"repair\" to fix worktree back-links to main worktree","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2020-08-31T06:57:57Z","receivedAt":"2020-08-31T06:59:26Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"The .git file in a linked worktree is a \"gitfile\" which points back to\nthe .git/worktrees/<id> entry in the main worktree or bare repository.\nIf a worktree's .git file is deleted or becomes corrupted or outdated,\nthen the linked worktree won't know how to find the repository or any of\nits own administrative files (such as 'index', 'HEAD', etc.). An easy\nway for the .git file to become outdated is for the user to move the\nmain worktree or bare repository. Although it is possible to manually\nupdate each linked worktree's .git file to reflect the new repository\nlocation, doing so requires a level of knowledge about worktree\ninternals beyond what a user should be expected to know offhand.\n\nTherefore, teach \"git worktree repair\" how to repair broken or outdated\nworktree .git files automatically. (For this to work, the command must\nbe invoked from within the main worktree or bare repository, or from\nwithin a worktree which has not become disconnected from the repository\n-- such as one which was created after the repository was moved.)\n\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n Documentation/git-worktree.txt | 10 ++++-\n builtin/worktree.c             | 12 +++++\n t/t2406-worktree-repair.sh     | 82 ++++++++++++++++++++++++++++++++++\n worktree.c                     | 61 +++++++++++++++++++++++++\n worktree.h                     | 11 +++++\n 5 files changed, 175 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex ae432d39a8..34fe47cecd 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -98,7 +98,10 @@ with `--reason`.\n move::\n \n Move a working tree to a new location. Note that the main working tree\n-or linked working trees containing submodules cannot be moved.\n+or linked working trees containing submodules cannot be moved with this\n+command. (The `git worktree repair` command, however, can reestablish\n+the connection with linked working trees if you move the main working\n+tree manually.)\n \n prune::\n \n@@ -115,6 +118,11 @@ repair::\n \n Repair working tree administrative files, if possible, if they have\n become corrupted or outdated due to external factors.\n++\n+For instance, if the main working tree (or bare repository) is moved,\n+linked working trees will be unable to locate it. Running `repair` in\n+the main working tree will reestablish the connection from linked\n+working trees back to the main working tree.\n \n unlock::\n \ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 88af412d4f..68b0032428 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -1030,6 +1030,17 @@ static int remove_worktree(int ac, const char **av, const char *prefix)\n \treturn ret;\n }\n \n+static void report_repair(int iserr, const char *path, const char *msg, void *cb_data)\n+{\n+\tif (!iserr) {\n+\t\tprintf_ln(_(\"repair: %s: %s\"), msg, path);\n+\t} else {\n+\t\tint *exit_status = (int *)cb_data;\n+\t\tfprintf_ln(stderr, _(\"error: %s: %s\"), msg, path);\n+\t\t*exit_status = 1;\n+\t}\n+}\n+\n static int repair(int ac, const char **av, const char *prefix)\n {\n \tstruct option options[] = {\n@@ -1040,6 +1051,7 @@ static int repair(int ac, const char **av, const char *prefix)\n \tac = parse_options(ac, av, prefix, options, worktree_usage, 0);\n \tif (ac)\n \t\tusage_with_options(worktree_usage, options);\n+\trepair_worktrees(report_repair, &rc);\n \treturn rc;\n }\n \ndiff --git a/t/t2406-worktree-repair.sh b/t/t2406-worktree-repair.sh\nindex cc679e1a21..ef59cdce95 100755\n--- a/t/t2406-worktree-repair.sh\n+++ b/t/t2406-worktree-repair.sh\n@@ -8,4 +8,86 @@ test_expect_success setup '\n \ttest_commit init\n '\n \n+test_expect_success 'skip missing worktree' '\n+\ttest_when_finished \"git worktree prune\" &&\n+\tgit worktree add --detach missing &&\n+\trm -rf missing &&\n+\tgit worktree repair >out 2>err &&\n+\ttest_must_be_empty out &&\n+\ttest_must_be_empty err\n+'\n+\n+test_expect_success 'worktree path not directory' '\n+\ttest_when_finished \"git worktree prune\" &&\n+\tgit worktree add --detach notdir &&\n+\trm -rf notdir &&\n+\t>notdir &&\n+\ttest_must_fail git worktree repair >out 2>err &&\n+\ttest_must_be_empty out &&\n+\ttest_i18ngrep \"not a directory\" err\n+'\n+\n+test_expect_success \"don't clobber .git repo\" '\n+\ttest_when_finished \"rm -rf repo && git worktree prune\" &&\n+\tgit worktree add --detach repo &&\n+\trm -rf repo &&\n+\ttest_create_repo repo &&\n+\ttest_must_fail git worktree repair >out 2>err &&\n+\ttest_must_be_empty out &&\n+\ttest_i18ngrep \".git is not a file\" err\n+'\n+\n+test_corrupt_gitfile () {\n+\tbutcher=$1 &&\n+\tproblem=$2 &&\n+\trepairdir=${3:-.} &&\n+\ttest_when_finished 'rm -rf corrupt && git worktree prune' &&\n+\tgit worktree add --detach corrupt &&\n+\tgit -C corrupt rev-parse --absolute-git-dir >expect &&\n+\teval \"$butcher\" &&\n+\tgit -C \"$repairdir\" worktree repair >out 2>err &&\n+\ttest_i18ngrep \"$problem\" out &&\n+\ttest_must_be_empty err &&\n+\tgit -C corrupt rev-parse --absolute-git-dir >actual &&\n+\ttest_cmp expect actual\n+}\n+\n+test_expect_success 'repair missing .git file' '\n+\ttest_corrupt_gitfile \"rm -f corrupt/.git\" \".git file broken\"\n+'\n+\n+test_expect_success 'repair bogus .git file' '\n+\ttest_corrupt_gitfile \"echo \\\"gitdir: /nowhere\\\" >corrupt/.git\" \\\n+\t\t\".git file broken\"\n+'\n+\n+test_expect_success 'repair incorrect .git file' '\n+\ttest_when_finished \"rm -rf other && git worktree prune\" &&\n+\ttest_create_repo other &&\n+\tother=$(git -C other rev-parse --absolute-git-dir) &&\n+\ttest_corrupt_gitfile \"echo \\\"gitdir: $other\\\" >corrupt/.git\" \\\n+\t\t\".git file incorrect\"\n+'\n+\n+test_expect_success 'repair .git file from main/.git' '\n+\ttest_corrupt_gitfile \"rm -f corrupt/.git\" \".git file broken\" .git\n+'\n+\n+test_expect_success 'repair .git file from linked worktree' '\n+\ttest_when_finished \"rm -rf other && git worktree prune\" &&\n+\tgit worktree add --detach other &&\n+\ttest_corrupt_gitfile \"rm -f corrupt/.git\" \".git file broken\" other\n+'\n+\n+test_expect_success 'repair .git file from bare.git' '\n+\ttest_when_finished \"rm -rf bare.git corrupt && git worktree prune\" &&\n+\tgit clone --bare . bare.git &&\n+\tgit -C bare.git worktree add --detach ../corrupt &&\n+\tgit -C corrupt rev-parse --absolute-git-dir >expect &&\n+\trm -f corrupt/.git &&\n+\tgit -C bare.git worktree repair &&\n+\tgit -C corrupt rev-parse --absolute-git-dir >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\ndiff --git a/worktree.c b/worktree.c\nindex 62217b4a6b..3ad93cc4aa 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -571,3 +571,64 @@ int other_head_refs(each_ref_fn fn, void *cb_data)\n \tfree_worktrees(worktrees);\n \treturn ret;\n }\n+\n+/*\n+ * Repair worktree's /path/to/worktree/.git file if missing, corrupt, or not\n+ * pointing at <repo>/worktrees/<id>.\n+ */\n+static void repair_gitfile(struct worktree *wt,\n+\t\t\t   worktree_repair_fn fn, void *cb_data)\n+{\n+\tstruct strbuf dotgit = STRBUF_INIT;\n+\tstruct strbuf repo = STRBUF_INIT;\n+\tchar *backlink;\n+\tconst char *repair = NULL;\n+\tint err;\n+\n+\t/* missing worktree can't be repaired */\n+\tif (!file_exists(wt->path))\n+\t\treturn;\n+\n+\tif (!is_directory(wt->path)) {\n+\t\tfn(1, wt->path, _(\"not a directory\"), cb_data);\n+\t\treturn;\n+\t}\n+\n+\tstrbuf_realpath(&repo, git_common_path(\"worktrees/%s\", wt->id), 1);\n+\tstrbuf_addf(&dotgit, \"%s/.git\", wt->path);\n+\tbacklink = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err));\n+\n+\tif (err == READ_GITFILE_ERR_NOT_A_FILE)\n+\t\tfn(1, wt->path, _(\".git is not a file\"), cb_data);\n+\telse if (err)\n+\t\trepair = _(\".git file broken\");\n+\telse if (fspathcmp(backlink, repo.buf))\n+\t\trepair = _(\".git file incorrect\");\n+\n+\tif (repair) {\n+\t\tfn(0, wt->path, repair, cb_data);\n+\t\twrite_file(dotgit.buf, \"gitdir: %s\", repo.buf);\n+\t}\n+\n+\tfree(backlink);\n+\tstrbuf_release(&repo);\n+\tstrbuf_release(&dotgit);\n+}\n+\n+static void repair_noop(int iserr, const char *path, const char *msg,\n+\t\t\tvoid *cb_data)\n+{\n+\t/* nothing */\n+}\n+\n+void repair_worktrees(worktree_repair_fn fn, void *cb_data)\n+{\n+\tstruct worktree **worktrees = get_worktrees();\n+\tstruct worktree **wt = worktrees + 1; /* +1 skips main worktree */\n+\n+\tif (!fn)\n+\t\tfn = repair_noop;\n+\tfor (; *wt; wt++)\n+\t\trepair_gitfile(*wt, fn, cb_data);\n+\tfree_worktrees(worktrees);\n+}\ndiff --git a/worktree.h b/worktree.h\nindex 516744c433..4fcb01348c 100644\n--- a/worktree.h\n+++ b/worktree.h\n@@ -89,6 +89,17 @@ int validate_worktree(const struct worktree *wt,\n void update_worktree_location(struct worktree *wt,\n \t\t\t      const char *path_);\n \n+typedef void (* worktree_repair_fn)(int iserr, const char *path,\n+\t\t\t\t    const char *msg, void *cb_data);\n+\n+/*\n+ * Visit each registered linked worktree and repair corruptions. For each\n+ * repair made or error encountered while attempting a repair, the callback\n+ * function, if non-NULL, is called with the path of the worktree and a\n+ * description of the repair or error, along with the callback user-data.\n+ */\n+void repair_worktrees(worktree_repair_fn, void *cb_data);\n+\n /*\n  * Free up the memory for worktree(s)\n  */\n-- \n2.28.0.531.g41c3d8a546\n\n"},{"id":"404793","messageId":"20200831065800.62502-5-sunshine@sunshineco.com","threadId":"54140","inReplyTo":"20200831065800.62502-1-sunshine@sunshineco.com","subject":"[PATCH v2 4/5] init: teach --separate-git-dir to repair linked worktrees","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2020-08-31T06:57:59Z","receivedAt":"2020-08-31T06:59:27Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"A linked worktree's .git file is a \"gitfile\" pointing at the\n.git/worktrees/<id> directory within the repository. When `git init\n--separate-git-dir=<path>` is used on an existing repository to relocate\nthe repository's .git/ directory to a different location, it neglects to\nupdate the .git files of linked worktrees, thus breaking the worktrees\nby making it impossible for them to locate the repository. Fix this by\nteaching --separate-git-dir to repair the .git file of each linked\nworktree to point at the new repository location.\n\nReported-by: Henré Botha <henrebotha@gmail.com>\nSigned-off-by: Eric Sunshine <sunshine@sunshineco.com>\n---\n builtin/init-db.c |  2 ++\n t/t0001-init.sh   | 11 +++++++++++\n 2 files changed, 13 insertions(+)\n\ndiff --git a/builtin/init-db.c b/builtin/init-db.c\nindex bbc9bc78f9..7b915d88ab 100644\n--- a/builtin/init-db.c\n+++ b/builtin/init-db.c\n@@ -9,6 +9,7 @@\n #include \"builtin.h\"\n #include \"exec-cmd.h\"\n #include \"parse-options.h\"\n+#include \"worktree.h\"\n \n #ifndef DEFAULT_GIT_TEMPLATE_DIR\n #define DEFAULT_GIT_TEMPLATE_DIR \"/usr/share/git-core/templates\"\n@@ -364,6 +365,7 @@ static void separate_git_dir(const char *git_dir, const char *git_link)\n \n \t\tif (rename(src, git_dir))\n \t\t\tdie_errno(_(\"unable to move %s to %s\"), src, git_dir);\n+\t\trepair_worktrees(NULL, NULL);\n \t}\n \n \twrite_file(git_link, \"gitdir: %s\", git_dir);\ndiff --git a/t/t0001-init.sh b/t/t0001-init.sh\nindex 50222a10c5..e489eb4ddb 100755\n--- a/t/t0001-init.sh\n+++ b/t/t0001-init.sh\n@@ -405,6 +405,17 @@ test_expect_success SYMLINKS 're-init to move gitdir symlink' '\n \ttest_path_is_dir realgitdir/refs\n '\n \n+test_expect_success 're-init to move gitdir with linked worktrees' '\n+\ttest_when_finished \"rm -rf mainwt linkwt seprepo\" &&\n+\tgit init mainwt &&\n+\ttest_commit -C mainwt gumby &&\n+\tgit -C mainwt worktree add --detach ../linkwt &&\n+\tgit -C mainwt init --separate-git-dir ../seprepo &&\n+\tgit -C mainwt rev-parse --git-common-dir >expect &&\n+\tgit -C linkwt rev-parse --git-common-dir >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success MINGW '.git hidden' '\n \trm -rf newdir &&\n \t(\n-- \n2.28.0.531.g41c3d8a546\n\n"},{"id":"404825","messageId":"xmqqbliqfxpv.fsf@gitster.c.googlers.com","threadId":"54140","inReplyTo":"20200831065800.62502-1-sunshine@sunshineco.com","subject":"Re: [PATCH v2 0/5] add \"git worktree repair\" command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-08-31T18:59:24Z","receivedAt":"2020-08-31T18:59:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> This is a re-roll of [1] which adds a \"git worktree repair\" command\n> and which fixes bugs with \"git init --separate-git-dir\" related to\n> worktrees.\n\nThese look good to me.  Thanks.\n"},{"id":"404826","messageId":"xmqq7dtefxd3.fsf@gitster.c.googlers.com","threadId":"54140","inReplyTo":"CAPig+cT-w6LV490MGNyG_ihWkSzdgfnEBrjQCsafjndTRmMgFA@mail.gmail.com","subject":"Re: [PATCH 3/5] worktree: teach \"repair\" to fix outgoing links to worktrees","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-08-31T19:07:04Z","receivedAt":"2020-08-31T19:07:12Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Sunshine <sunshine@sunshineco.com> writes:\n\n> No. For this repair to work, the gitfile in the linked worktree must\n> be intact; it must be pointing back at the .git/worktrees/<id>\n> directory so that \"git worktree repair\" can repair the corresponding\n> .git/worktrees/<id>/gitdir file.\n\nOK, I missed the fact that we can learn <id> quite easily if we have\nthe gitfile.  Thanks.\n\n"}]}