git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v5 7/8] worktree: add relative cli/config options to `repair` command

From
Caleb White <cdwhite3@pm.me>
Date
Nov 26, 2024, 01:52 UTC
Message-ID
<20241125-wt_relative_options-v5-7-356d122ff3db@pm.me>
In-Reply-To
<20241125-wt_relative_options-v5-0-356d122ff3db@pm.me>

This teaches the `worktree repair` command to respect the `--[no-]relative-paths` CLI option and `worktree.useRelativePaths` config setting. If an existing worktree with an absolute path is repaired with `--relative-paths`, the links will be replaced with relative paths, even if the original path was correct. This allows a user to covert existing worktrees between absolute/relative as desired.

To simplify things, both linking files are written when one of the files needs to be repaired. In some cases, this fixes the other file before it is checked, in other cases this results in a correct file being written with the same contents.

Signed-off-by: Caleb White <cdwhite3@pm.me>
---
 Documentation/git-worktree.txt |  3 +++
 builtin/worktree.c             |  6 +++--
 t/t2406-worktree-repair.sh     | 39 +++++++++++++++++++++++++++++
 worktree.c                     | 56 ++++++++++++++++++++----------------------
 worktree.h                     |  5 ++--
 5 files changed, 76 insertions(+), 33 deletions(-)
diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index 60a671bbc255aa7763b4f77511c09f6a02783e00..8340b7f028e6c1c3bae3de0879e9754098466d14 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -220,6 +220,9 @@ This can also be set up as the default behaviour by using the
 	Link worktrees using relative paths or absolute paths (default).
 	Overrides the `worktree.useRelativePaths` config option, see
 	linkgit:git-config[1].
++
+With `repair`, the linking files will be updated if there's an absolute/relative
+mismatch, even if the links are correct.
 
 --[no-]track::
 	When creating a new branch, if `<commit-ish>` is a branch,
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 302151506981718658db1cd338cd9064688f5c14..fde9ff4dc9a734c655e95ccd62774282950cbba6 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -1385,6 +1385,8 @@ static int repair(int ac, const char **av, const char *prefix)
 	const char **p;
 	const char *self[] = { ".", NULL };
 	struct option options[] = {
+		OPT_BOOL(0, "relative-paths", &use_relative_paths,
+			 N_("use relative paths for worktrees")),
 		OPT_END()
 	};
 	int rc = 0;
@@ -1392,8 +1394,8 @@ static int repair(int ac, const char **av, const char *prefix)
 	ac = parse_options(ac, av, prefix, options, git_worktree_repair_usage, 0);
 	p = ac > 0 ? av : self;
 	for (; *p; p++)
-		repair_worktree_at_path(*p, report_repair, &rc);
-	repair_worktrees(report_repair, &rc);
+		repair_worktree_at_path(*p, report_repair, &rc, use_relative_paths);
+	repair_worktrees(report_repair, &rc, use_relative_paths);
 	return rc;
 }
 
diff --git a/t/t2406-worktree-repair.sh b/t/t2406-worktree-repair.sh
index 7686e60f6ad186519b275f11a5e14064c905b207..49b70b999518d47e1edd72a61a847b427f4c67a1 100755
--- a/t/t2406-worktree-repair.sh
+++ b/t/t2406-worktree-repair.sh
@@ -216,4 +216,43 @@ test_expect_success 'repair copied main and linked worktrees' '
 	test_cmp dup/linked.expect dup/linked/.git
 '
 
+test_expect_success 'repair worktree with relative path with missing gitfile' '
+	test_when_finished "rm -rf main wt" &&
+	test_create_repo main &&
+	git -C main config worktree.useRelativePaths true &&
+	test_commit -C main init &&
+	git -C main worktree add --detach ../wt &&
+	rm wt/.git &&
+	test_path_is_missing wt/.git &&
+	git -C main worktree repair &&
+	echo "gitdir: ../main/.git/worktrees/wt" >expect &&
+	test_cmp expect wt/.git
+'
+
+test_expect_success 'repair absolute worktree to use relative paths' '
+	test_when_finished "rm -rf main side sidemoved" &&
+	test_create_repo main &&
+	test_commit -C main init &&
+	git -C main worktree add --detach ../side &&
+	echo "../../../../sidemoved/.git" >expect-gitdir &&
+	echo "gitdir: ../main/.git/worktrees/side" >expect-gitfile &&
+	mv side sidemoved &&
+	git -C main worktree repair --relative-paths ../sidemoved &&
+	test_cmp expect-gitdir main/.git/worktrees/side/gitdir &&
+	test_cmp expect-gitfile sidemoved/.git
+'
+
+test_expect_success 'repair relative worktree to use absolute paths' '
+	test_when_finished "rm -rf main side sidemoved" &&
+	test_create_repo main &&
+	test_commit -C main init &&
+	git -C main worktree add --relative-paths --detach ../side &&
+	echo "$(pwd)/sidemoved/.git" >expect-gitdir &&
+	echo "gitdir: $(pwd)/main/.git/worktrees/side" >expect-gitfile &&
+	mv side sidemoved &&
+	git -C main worktree repair ../sidemoved &&
+	test_cmp expect-gitdir main/.git/worktrees/side/gitdir &&
+	test_cmp expect-gitfile sidemoved/.git
+'
+
 test_done
diff --git a/worktree.c b/worktree.c
index c749cb16994cf46ccccd4c2880ac917e497671b8..2e76bbc1494afc125997f803dc39846a0b95a84f 100644
--- a/worktree.c
+++ b/worktree.c
@@ -573,12 +573,13 @@ int other_head_refs(each_ref_fn fn, void *cb_data)
  * pointing at <repo>/worktrees/<id>.
  */
 static void repair_gitfile(struct worktree *wt,
-			   worktree_repair_fn fn, void *cb_data)
+			   worktree_repair_fn fn, void *cb_data,
+			   int use_relative_paths)
 {
 	struct strbuf dotgit = STRBUF_INIT;
+	struct strbuf gitdir = STRBUF_INIT;
 	struct strbuf repo = STRBUF_INIT;
 	struct strbuf backlink = STRBUF_INIT;
-	struct strbuf tmp = STRBUF_INIT;
 	char *dotgit_contents = NULL;
 	const char *repair = NULL;
 	int err;
@@ -594,6 +595,7 @@ static void repair_gitfile(struct worktree *wt,
 
 	strbuf_realpath(&repo, git_common_path("worktrees/%s", wt->id), 1);
 	strbuf_addf(&dotgit, "%s/.git", wt->path);
+	strbuf_addf(&gitdir, "%s/gitdir", repo.buf);
 	dotgit_contents = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err));
 
 	if (dotgit_contents) {
@@ -611,18 +613,20 @@ static void repair_gitfile(struct worktree *wt,
 		repair = _(".git file broken");
 	else if (fspathcmp(backlink.buf, repo.buf))
 		repair = _(".git file incorrect");
+	else if (use_relative_paths == is_absolute_path(dotgit_contents))
+		repair = _(".git file absolute/relative path mismatch");
 
 	if (repair) {
 		fn(0, wt->path, repair, cb_data);
-		write_file(dotgit.buf, "gitdir: %s", relative_path(repo.buf, wt->path, &tmp));
+		write_worktree_linking_files(dotgit, gitdir, use_relative_paths);
 	}
 
 done:
 	free(dotgit_contents);
 	strbuf_release(&repo);
 	strbuf_release(&dotgit);
+	strbuf_release(&gitdir);
 	strbuf_release(&backlink);
-	strbuf_release(&tmp);
 }
 
 static void repair_noop(int iserr UNUSED,
@@ -633,7 +637,7 @@ static void repair_noop(int iserr UNUSED,
 	/* nothing */
 }
 
-void repair_worktrees(worktree_repair_fn fn, void *cb_data)
+void repair_worktrees(worktree_repair_fn fn, void *cb_data, int use_relative_paths)
 {
 	struct worktree **worktrees = get_worktrees_internal(1);
 	struct worktree **wt = worktrees + 1; /* +1 skips main worktree */
@@ -641,7 +645,7 @@ void repair_worktrees(worktree_repair_fn fn, void *cb_data)
 	if (!fn)
 		fn = repair_noop;
 	for (; *wt; wt++)
-		repair_gitfile(*wt, fn, cb_data);
+		repair_gitfile(*wt, fn, cb_data, use_relative_paths);
 	free_worktrees(worktrees);
 }
 
@@ -757,16 +761,14 @@ static ssize_t infer_backlink(const char *gitfile, struct strbuf *inferred)
  * the worktree's path.
  */
 void repair_worktree_at_path(const char *path,
-			     worktree_repair_fn fn, void *cb_data)
+			     worktree_repair_fn fn, void *cb_data,
+			     int use_relative_paths)
 {
 	struct strbuf dotgit = STRBUF_INIT;
-	struct strbuf realdotgit = STRBUF_INIT;
 	struct strbuf backlink = STRBUF_INIT;
 	struct strbuf inferred_backlink = STRBUF_INIT;
 	struct strbuf gitdir = STRBUF_INIT;
 	struct strbuf olddotgit = STRBUF_INIT;
-	struct strbuf realolddotgit = STRBUF_INIT;
-	struct strbuf tmp = STRBUF_INIT;
 	char *dotgit_contents = NULL;
 	const char *repair = NULL;
 	int err;
@@ -778,25 +780,25 @@ void repair_worktree_at_path(const char *path,
 		goto done;
 
 	strbuf_addf(&dotgit, "%s/.git", path);
-	if (!strbuf_realpath(&realdotgit, dotgit.buf, 0)) {
+	if (!strbuf_realpath(&dotgit, dotgit.buf, 0)) {
 		fn(1, path, _("not a valid path"), cb_data);
 		goto done;
 	}
 
-	infer_backlink(realdotgit.buf, &inferred_backlink);
+	infer_backlink(dotgit.buf, &inferred_backlink);
 	strbuf_realpath_forgiving(&inferred_backlink, inferred_backlink.buf, 0);
-	dotgit_contents = xstrdup_or_null(read_gitfile_gently(realdotgit.buf, &err));
+	dotgit_contents = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err));
 	if (dotgit_contents) {
 		if (is_absolute_path(dotgit_contents)) {
 			strbuf_addstr(&backlink, dotgit_contents);
 		} else {
-			strbuf_addbuf(&backlink, &realdotgit);
+			strbuf_addbuf(&backlink, &dotgit);
 			strbuf_strip_suffix(&backlink, ".git");
 			strbuf_addstr(&backlink, dotgit_contents);
 			strbuf_realpath_forgiving(&backlink, backlink.buf, 0);
 		}
 	} else if (err == READ_GITFILE_ERR_NOT_A_FILE) {
-		fn(1, realdotgit.buf, _("unable to locate repository; .git is not a file"), cb_data);
+		fn(1, dotgit.buf, _("unable to locate repository; .git is not a file"), cb_data);
 		goto done;
 	} else if (err == READ_GITFILE_ERR_NOT_A_REPO) {
 		if (inferred_backlink.len) {
@@ -809,11 +811,11 @@ void repair_worktree_at_path(const char *path,
 			 */
 			strbuf_swap(&backlink, &inferred_backlink);
 		} else {
-			fn(1, realdotgit.buf, _("unable to locate repository; .git file does not reference a repository"), cb_data);
+			fn(1, dotgit.buf, _("unable to locate repository; .git file does not reference a repository"), cb_data);
 			goto done;
 		}
 	} else {
-		fn(1, realdotgit.buf, _("unable to locate repository; .git file broken"), cb_data);
+		fn(1, dotgit.buf, _("unable to locate repository; .git file broken"), cb_data);
 		goto done;
 	}
 
@@ -835,39 +837,35 @@ void repair_worktree_at_path(const char *path,
 	 * in the "copy" repository. In this case, point the "copy" worktree's
 	 * .git file at the "copy" repository.
 	 */
-	if (inferred_backlink.len && fspathcmp(backlink.buf, inferred_backlink.buf)) {
+	if (inferred_backlink.len && fspathcmp(backlink.buf, inferred_backlink.buf))
 		strbuf_swap(&backlink, &inferred_backlink);
-	}
 
 	strbuf_addf(&gitdir, "%s/gitdir", backlink.buf);
 	if (strbuf_read_file(&olddotgit, gitdir.buf, 0) < 0)
 		repair = _("gitdir unreadable");
+	else if (use_relative_paths == is_absolute_path(olddotgit.buf))
+		repair = _("gitdir absolute/relative path mismatch");
 	else {
 		strbuf_rtrim(&olddotgit);
-		if (is_absolute_path(olddotgit.buf)) {
-			strbuf_addbuf(&realolddotgit, &olddotgit);
-		} else {
-			strbuf_addf(&realolddotgit, "%s/%s", backlink.buf, olddotgit.buf);
-			strbuf_realpath_forgiving(&realolddotgit, realolddotgit.buf, 0);
+		if (!is_absolute_path(olddotgit.buf)) {
+			strbuf_insertf(&olddotgit, 0, "%s/", backlink.buf);
+			strbuf_realpath_forgiving(&olddotgit, olddotgit.buf, 0);
 		}
-		if (fspathcmp(realolddotgit.buf, realdotgit.buf))
+		if (fspathcmp(olddotgit.buf, dotgit.buf))
 			repair = _("gitdir incorrect");
 	}
 
 	if (repair) {
 		fn(0, gitdir.buf, repair, cb_data);
-		write_file(gitdir.buf, "%s", relative_path(realdotgit.buf, backlink.buf, &tmp));
+		write_worktree_linking_files(dotgit, gitdir, use_relative_paths);
 	}
 done:
 	free(dotgit_contents);
 	strbuf_release(&olddotgit);
-	strbuf_release(&realolddotgit);
 	strbuf_release(&backlink);
 	strbuf_release(&inferred_backlink);
 	strbuf_release(&gitdir);
-	strbuf_release(&realdotgit);
 	strbuf_release(&dotgit);
-	strbuf_release(&tmp);
 }
 
 int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath, timestamp_t expire)
diff --git a/worktree.h b/worktree.h
index 9c699d080d8ebf37712044136679db3821ee1f63..38145df80f41079f301d3aeaaffcc35b7f6760b9 100644
--- a/worktree.h
+++ b/worktree.h
@@ -129,7 +129,7 @@ typedef void (* worktree_repair_fn)(int iserr, const char *path,
  * function, if non-NULL, is called with the path of the worktree and a
  * description of the repair or error, along with the callback user-data.
  */
-void repair_worktrees(worktree_repair_fn, void *cb_data);
+void repair_worktrees(worktree_repair_fn, void *cb_data, int use_relative_paths);
 
 /*
  * Repair the linked worktrees after the gitdir has been moved.
@@ -151,7 +151,8 @@ void repair_worktree_after_gitdir_move(struct worktree *wt, const char *old_path
  * worktree and a description of the repair or error, along with the callback
  * user-data.
  */
-void repair_worktree_at_path(const char *, worktree_repair_fn, void *cb_data);
+void repair_worktree_at_path(const char *, worktree_repair_fn,
+			     void *cb_data, int use_relative_paths);
 
 /*
  * Free up the memory for a worktree.
-- 
2.47.0
Previous: Caleb WhiteNext: Caleb White
Message 43 of 60 in “Allow relative worktree linking to be configured by the user”
  1. 0/8 Allow relative worktree linking to be configured by the userCaleb White, Nov 1, 2024
  2. 1/8 setup: correctly reinitialize repository versionCaleb White, Nov 1, 2024
  3. 2/8 worktree: add `relativeWorktrees` extensionCaleb White, Nov 1, 2024
  4. Phillip WoodNov 19, 2024
  5. Caleb WhiteNov 20, 2024
  6. Phillip WoodNov 22, 2024
  7. Caleb WhiteNov 22, 2024
  8. 3/8 worktree: refactor infer_backlink returnCaleb White, Nov 1, 2024
  9. Phillip WoodNov 19, 2024
  10. Caleb WhiteNov 20, 2024
  11. Phillip WoodNov 22, 2024
  12. Caleb WhiteNov 22, 2024
  13. 4/8 worktree: add `write_worktree_linking_files()` functionCaleb White, Nov 1, 2024
  14. 6/8 worktree: add relative cli/config options to `move` commandCaleb White, Nov 1, 2024
  15. Phillip WoodNov 22, 2024
  16. Caleb WhiteNov 23, 2024
  17. 5/8 worktree: add relative cli/config options to `add` commandCaleb White, Nov 1, 2024
  18. Phillip WoodNov 19, 2024
  19. Caleb WhiteNov 20, 2024
  20. phillip.wood123@gmail.comNov 22, 2024
  21. Caleb WhiteNov 23, 2024
  22. 7/8 worktree: add relative cli/config options to `repair` commandCaleb White, Nov 1, 2024
  23. Phillip WoodNov 22, 2024
  24. Caleb WhiteNov 23, 2024
  25. phillip.wood123@gmail.comNov 24, 2024
  26. Caleb WhiteNov 26, 2024
  27. 8/8 worktree: refactor `repair_worktree_after_gitdir_move()`Caleb White, Nov 1, 2024
  28. Phillip WoodNov 22, 2024
  29. Caleb WhiteNov 23, 2024
  30. Junio C HamanoNov 1, 2024
  31. Caleb WhiteNov 1, 2024
  32. Junio C HamanoNov 2, 2024
  33. Kristoffer HaugsbakkNov 2, 2024
  34. Phillip WoodNov 22, 2024
  35. Caleb WhiteNov 23, 2024
  36. 0/8 Allow relative worktree linking to be configured by the userCaleb White, Nov 26, 2024
  37. 1/8 setup: correctly reinitialize repository versionCaleb White, Nov 26, 2024
  38. 2/8 worktree: add `relativeWorktrees` extensionCaleb White, Nov 26, 2024
  39. 3/8 worktree: refactor infer_backlink returnCaleb White, Nov 26, 2024
  40. 4/8 worktree: add `write_worktree_linking_files()` functionCaleb White, Nov 26, 2024
  41. 5/8 worktree: add relative cli/config options to `add` commandCaleb White, Nov 26, 2024
  42. 6/8 worktree: add relative cli/config options to `move` commandCaleb White, Nov 26, 2024
  43. 7/8 worktree: add relative cli/config options to `repair` commandCaleb White, Nov 26, 2024
  44. 8/8 worktree: refactor `repair_worktree_after_gitdir_move()`Caleb White, Nov 26, 2024
  45. Junio C HamanoNov 26, 2024
  46. Caleb WhiteNov 26, 2024
  47. Phillip WoodNov 28, 2024
  48. Caleb WhiteNov 28, 2024
  49. 0/8 Allow relative worktree linking to be configured by the userCaleb White, Nov 29, 2024
  50. 1/8 setup: correctly reinitialize repository versionCaleb White, Nov 29, 2024
  51. 2/8 worktree: add `relativeWorktrees` extensionCaleb White, Nov 29, 2024
  52. 3/8 worktree: refactor infer_backlink returnCaleb White, Nov 29, 2024
  53. 4/8 worktree: add `write_worktree_linking_files()` functionCaleb White, Nov 29, 2024
  54. 5/8 worktree: add relative cli/config options to `add` commandCaleb White, Nov 29, 2024
  55. 6/8 worktree: add relative cli/config options to `move` commandCaleb White, Nov 29, 2024
  56. 7/8 worktree: add relative cli/config options to `repair` commandCaleb White, Nov 29, 2024
  57. 8/8 worktree: refactor `repair_worktree_after_gitdir_move()`Caleb White, Nov 29, 2024
  58. Phillip WoodDec 2, 2024
  59. Junio C HamanoDec 3, 2024
  60. Caleb WhiteDec 3, 2024

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.