threads / patch / 62258

patch, 4 partsLink worktrees with relative paths

Subject: [PATCH 0/4] Link worktrees with relative paths

## tl;dr

23 messages between Oct 6, 2024 and Oct 6, 2024. Diffs are folded; open one to read it.

replies: 22people: 3as markdown or json

Caleb White· Oct 6, 2024, 04:59 UTC · lore
Hello,

This patch series modifies Git's handling of worktree linking to use relative paths instead of absolute paths. The motivation behind this change is to enhance the robustness of worktree links when the main repository is moved, or when used in environments like containerized development where absolute paths differ across systems.

Currently, Git stores absolute paths to both the main repository and the linked worktrees. This causes issues when the repository is moved because the hardcoded paths become invalid, leading to broken worktree links. Developers are then required to manually repair the links by running `git worktree repair`, which can be cumbersome.

By switching to relative paths for worktrees, this patch improves the resilience of worktree links. For self-contained repositories (e.g., bare repositories with worktrees), the links will continue to function properly when the repository is moved or mapped inside a containerized environment. While relativ e paths do not completely eliminate the need for repairs (as links external to the repository can still break), the likelihood is reduced, and Git continues to provide mechanisms to repair broken links when needed.

I have included tests to verify that:
- worktree links are created with relative paths.
- moving the repository does not break worktree links.

Note that absolute paths are still supported, and the code handles both types of paths. There should be no breaking changes introduced with this patch. I considered adding a configuration option (e.g., `worktree.useRelativePaths`) to control path type, but decided to keep it simple. However, if there is interest, I can add this feature.

This series is based on top of 111e864d69.
Thanks!
Caleb
Caleb White (4):
  worktree: refactor infer_backlink() to use *strbuf
  worktree: link worktrees with relative paths
  worktree: sync worktree paths after gitdir move
  worktree: prevent null pointer dereference
 builtin/worktree.c           |  17 +--
 setup.c                      |   2 +-
 t/t2408-worktree-relative.sh |  39 ++++++
 worktree.c                   | 235 +++++++++++++++++++++++++++--------
 worktree.h                   |  10 ++
 5 files changed, 240 insertions(+), 63 deletions(-)
 create mode 100755 t/t2408-worktree-relative.sh
-- 
2.46.2
Caleb White· Oct 6, 2024, 04:59 UTC · re: Caleb White · lore

[PATCH 1/4] worktree: refactor infer_backlink() to use *strbuf

This refactors the `infer_backlink` function to return an integer result and use a pre-allocated `strbuf` for the inferred backlink path, replacing the previous `char*` return type.

This lays the groundwork for the next patch, which needs the resultant backlink as a `strbuf`. There was no need to go from `strbuf -> char* -> strbuf` again. This change also aligns the function's signature with other `strbuf`-based functions.

Signed-off-by: Caleb White <cdwhite3@pm.me>
---
 worktree.c | 26 ++++++++++++++------------
 1 file changed, 14 insertions(+), 12 deletions(-)
Show changes to worktree.c +14 −12
diff --git a/worktree.c b/worktree.c
index 0f032cc..c6d2ede 100644
--- a/worktree.c
+++ b/worktree.c
@@ -642,10 +642,9 @@ static int is_main_worktree_path(const char *path)
  * be able to infer the gitdir by manually reading /path/to/worktree/.git,
  * extracting the <id>, and checking if <repo>/worktrees/<id> exists.
  */
-static char *infer_backlink(const char *gitfile)
+static int infer_backlink(st
ruct strbuf *inferred, const char *gitfile)
 {
 	struct strbuf actual = STRBUF_INIT;
-	struct strbuf inferred = STRBUF_INIT;
 	const char *id;
 
 	if (strbuf_read_file(&actual, gitfile, 0) < 0)
@@ -658,17 +657,16 @@ static char *infer_backlink(const char *gitfile)
 	id++; /* advance past '/' to point at <id> */
 	if (!*id)
 		goto error;
-	strbuf_git_common_path(&inferred, the_repository, "worktrees/%s", id);
-	if (!is_directory(inferred.buf))
+	strbuf_git_common_path(inferred, the_repository, "worktrees/%s", id);
+	if (!is_directory(inferred->buf))
 		goto error;
 
 	strbuf_release(&actual);
-	return strbuf_detach(&inferred, NULL);
+	return 0;
 
 error:
 	strbuf_release(&actual);
-	strbuf_release(&inferred);
-	return NULL;
+	return 1;
 }
 
 /*
@@ -680,9 +678,10 @@ void repair_worktree_at_path(const char *path,
 {
 	struct strbuf dotgit = STRBUF_INIT;
 	struct strbuf realdotgit = STRBUF_INIT;
+	struct strbuf backlink = STRBUF_INIT;
 	struct strbuf gitd
ir = STRBUF_INIT;
 	struct strbuf olddotgit = STRBUF_INIT;
-	char *backlink = NULL;
+	char *git_contents = NULL;
 	const char *repair = NULL;
 	int err;
 
@@ -698,21 +697,23 @@ void repair_worktree_at_path(const char *path,
 		goto done;
 	}
 
-	backlink = xstrdup_or_null(read_gitfile_gently(realdotgit.buf, &err));
+	git_contents = xstrdup_or_null(read_gitfile_gently(realdotgit.buf, &err));
 	if (err == READ_GITFILE_ERR_NOT_A_FILE) {
 		fn(1, realdotgit.buf, _("unable to locate repository; .git is not a file"), cb_data);
 		goto done;
 	} else if (err == READ_GITFILE_ERR_NOT_A_REPO) {
-		if (!(backlink = infer_backlink(realdotgit.buf))) {
+		if (infer_backlink(&backlink, realdotgit.buf)) {
 			fn(1, realdotgit.buf, _("unable to locate repository; .git file does not reference a repository"), cb_data);
 			goto done;
 		}
 	} else if (err) {
 		fn(1, realdotgit.buf, _("unable to locate repository; .git file broken"), cb_data);
 		goto done;
+	} else if (git_conte
nts) {
+		strbuf_addstr(&backlink, git_contents);
 	}
 
-	strbuf_addf(&gitdir, "%s/gitdir", backlink);
+	strbuf_addf(&gitdir, "%s/gitdir", backlink.buf);
 	if (strbuf_read_file(&olddotgit, gitdir.buf, 0) < 0)
 		repair = _("gitdir unreadable");
 	else {
@@ -726,8 +727,9 @@ void repair_worktree_at_path(const char *path,
 		write_file(gitdir.buf, "%s", realdotgit.buf);
 	}
 done:
-	free(backlink);
+	free(git_contents);
 	strbuf_release(&olddotgit);
+	strbuf_release(&backlink);
 	strbuf_release(&gitdir);
 	strbuf_release(&realdotgit);
 	strbuf_release(&dotgit);
-- 
2.46.2
Caleb White· Oct 6, 2024, 04:59 UTC · re: Caleb White · lore

[PATCH 2/4] worktree: link worktrees with relative paths

This modifies Git’s handling of worktree linking to use relative paths instead of absolute paths. Previously, when creating a worktree, Git would store the absolute paths to both the main repository and the linked worktrees. These hardcoded absolute paths cause breakages such as when the repository is moved to a different directory or during containerized development where the absolute differs between systems.

By switching to relative paths, we help ensure that worktree links are more resilient when the repository is moved. While links external to the repository may still break, Git still automatically handles many common scenarios, reducing the need for manual repair. This is particularly useful in containerized or portable development environments, where the absolute path to the repository can differ between systems. Developers no longer need to reinitialize or repair worktrees after relocating the repository, improving workflow efficiency and reducing breakages.

For self-contained repositories (such as using a bare repository with worktrees), where both the repository and its worktrees are located within the same directory structure, using relative paths guarantees all links remain functional regardless of where the directory is located.

Signed-off-by: Caleb White <cdwhite3@pm.me>
---
 builtin/worktree.c           |  17 ++--
 t/t2408-worktree-relative.sh |  39 +++++++++
 worktree.c                   | 152 +++++++++++++++++++++++++----------
 3 files changed, 159 insertions(+), 49 deletions(-)
 create mode 100755 t/t2408-worktree-relative.sh
Show changes to 3 files +159 −49

builtin/worktree.c, t/t2408-worktree-relative.sh, worktree.c

diff --git a/builtin/worktree.c b/builtin/worktree.c
index fc31d07..99cee56 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -414,7 +414,8 @@ static int add_worktree(const char *path, const char *refname,
 			const struct add_opts *opts)
 {
 	struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
-	struct strbuf sb = STRBUF_INIT, realpath = STRBUF_INIT;
+	struct strbuf sb = STRBUF_INIT, sb_tmp = STRBUF_INIT;
+	struct strbuf sb_path_realpath = STRBUF_INIT, sb_repo_realpath = STRBUF_INIT;
 	const char *name;
 	struct strvec child_env = STRVEC_INIT;
 	unsigned int counter = 0;
@@ -490,11 +491,11 @@ static int add_worktree(const char *path, const char *refname,
 
 	strbuf_reset(&sb);
 	strbuf_addf(&sb, "%s/gitdir", sb_repo.buf);
-	strbuf_realpath(&realpath, sb_git.buf, 1);
-	write_file(sb.buf, "%s", realpath.buf);
-	strbuf_realpath(&realpath, repo_get_common_dir(the_repository), 1);
-	write_file(sb_git.buf, "gitdir: %s/worktrees/%s",
-		   realpath.buf, name);
+	strbuf_realpath(&sb_path_realpath, path, 1);
+	strbuf_realpath(&sb_repo_realpath, sb_repo.buf, 1);
+	write_file(sb.buf, "%s/.git", relative_path(sb_path_realpath.buf, sb_repo_realpath.buf, &sb_tmp));
+	strbuf_reset(&sb_tmp);
+	write_file(sb_git.buf, "gitdir: %s", relative_path(sb_repo_realpath.buf, sb_path_realpath.buf, &sb_tmp));
 	strbuf_reset(&sb);
 	strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
 	write_file(sb.buf, "../..");
@@ -578,11 +579,13 @@ static int add_worktree(const char *path, const char *refname,
 
 	strvec_clear(&child_env);
 	strbuf_release(&sb);
+	strbuf_release(&sb_tmp);
 	strbuf_release(&symref);
 	strbuf_release(&sb_repo);
+	strbuf_release(&sb_repo_realpath);
 	strbuf_release(&sb_git);
+	strbuf_release(&sb_path_realpath);
 	strbuf_release(&sb_name);
-	strbuf_release(&realpath);
 	free_worktree(wt);
 	return ret;
 }
diff --git a/t/t2408-worktree-relative.sh b/t/t2408-worktree-relative.sh
new file mode 100755
index 0000000..a3136db
--- /dev/null
+++ b/t/t2408-worktree-relative.sh
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+test_description='test worktrees linked with relative paths'
+
+TEST_PASSES_SANITIZE_LEAK=true
+. ./test-lib.sh
+
+test_expect_success 'links worktrees with relative paths' '
+	test_when_finished rm -rf repo &&
+	git init repo &&
+	(
+		cd repo &&
+		test_commit initial &&
+		git worktree add wt1 &&
+		echo "../../../wt1/.git" >expected_gitdir &&
+		cat .git/worktrees/wt1/gitdir >actual_gitdir &&
+		echo "gitdir: ../.git/worktrees/wt1" >expected_git &&
+		cat wt1/.git >actual_git &&
+		test_cmp expected_gitdir actual_gitdir &&
+		test_cmp expected_git actual_git
+	)
+'
+
+test_expect_success 'move repo without breaking relative internal links' '
+	test_when_finished rm -rf repo moved &&
+	git init repo &&
+	(
+		cd repo &&
+		test_commit initial &&
+		git worktree add wt1 &&
+		cd .. &&
+		mv repo moved &&
+		cd moved/wt1 &&
+		git status >out 2>err &&
+		test_must_be_empty err
+	)
+'
+
+test_done
diff --git a/worktree.c b/worktree.c
index c6d2ede..fc14e9a 100644
--- a/worktree.c
+++ b/worktree.c
@@ -110,6 +110,12 @@ struct worktree *get_linked_worktree(const char *id,
 	strbuf_rtrim(&worktree_path);
 	strbuf_strip_suffix(&worktree_path, "/.git");
 
+	if (!is_absolute_path(worktree_path.buf)) {
+	    strbuf_strip_suffix(&path, "gitdir");
+	    strbuf_addbuf(&path, &worktree_path);
+	    strbuf_realpath_forgiving(&worktree_path, path.buf, 0);
+	}
+
 	CALLOC_ARRAY(worktree, 1);
 	worktree->repo = the_repository;
 	worktree->path = strbuf_detach(&worktree_path, NULL);
@@ -373,18 +379,30 @@ int validate_worktree(const struct worktree *wt, struct strbuf *errmsg,
 void update_worktree_location(struct worktree *wt, const char *path_)
 {
 	struct strbuf path = STRBUF_INIT;
+	struct strbuf repo = STRBUF_INIT;
+	struct strbuf tmp = STRBUF_INIT;
+	char *file = NULL;
 
 	if (is_main_worktree(wt))
 		BUG("can't relocate main worktree");
 
+	strbuf_realpath(&repo, git_common_path("worktrees/%s", wt->id), 1);
 	strbuf_realpath(&path, path_, 1);
 	if (fspathcmp(wt->path, path.buf)) {
-		write_file(git_common_path("worktrees/%s/gitdir", wt->id),
-			   "%s/.git", path.buf);
+		file = xstrfmt("%s/gitdir", repo.buf);
+		write_file(file, "%s/.git", relative_path(path.buf, repo.buf, &tmp));
+		free(file);
+		strbuf_reset(&tmp);
+		file = xstrfmt("%s/.git", path.buf);
+		write_file(file, "gitdir: %s", relative_path(repo.buf, path.buf, &tmp));
+
 		free(wt->path);
 		wt->path = strbuf_detach(&path, NULL);
 	}
+	free(file);
 	strbuf_release(&path);
+	strbuf_release(&repo);
+	strbuf_release(&tmp);
 }
 
 int is_worktree_being_rebased(const struct worktree *wt,
@@ -564,38 +582,52 @@ static void repair_gitfile(struct worktree *wt,
 {
 	struct strbuf dotgit = STRBUF_INIT;
 	struct strbuf repo = STRBUF_INIT;
-	char *backlink;
+	struct strbuf backlink = STRBUF_INIT;
+	struct strbuf tmp = STRBUF_INIT;
+	char *git_contents = NULL;
 	const char *repair = NULL;
 	int err;
 
 	/* missing worktree can't be repaired */
 	if (!file_exists(wt->path))
-		return;
+		goto done;
 
 	if (!is_directory(wt->path)) {
 		fn(1, wt->path, _("not a directory"), cb_data);
-		return;
+		goto done;
 	}
 
 	strbuf_realpath(&repo, git_common_path("worktrees/%s", wt->id), 1);
 	strbuf_addf(&dotgit, "%s/.git", wt->path);
-	backlink = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err));
+	git_contents = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err));
+
+	if (git_contents) {
+		if (is_absolute_path(git_contents)) {
+			strbuf_addstr(&backlink, git_contents);
+		} else {
+			strbuf_addf(&backlink, "%s/%s", wt->path, git_contents);
+			strbuf_realpath_forgiving(&backlink, backlink.buf, 0);
+		}
+	}
 
 	if (err == READ_GITFILE_ERR_NOT_A_FILE)
 		fn(1, wt->path, _(".git is not a file"), cb_data);
 	else if (err)
 		repair = _(".git file broken");
-	else if (fspathcmp(backlink, repo.buf))
+	else if (fspathcmp(backlink.buf, repo.buf))
 		repair = _(".git file incorrect");
 
 	if (repair) {
 		fn(0, wt->path, repair, cb_data);
-		write_file(dotgit.buf, "gitdir: %s", repo.buf);
+		write_file(dotgit.buf, "gitdir: %s", relative_path(repo.buf, wt->path, &tmp));
 	}
 
-	free(backlink);
+done:
+	free(git_contents);
 	strbuf_release(&repo);
 	strbuf_release(&dotgit);
+	strbuf_release(&backlink);
+	strbuf_release(&tmp);
 }
 
 static void repair_noop(int iserr UNUSED,
@@ -681,6 +713,8 @@ void repair_worktree_at_path(const char *path,
 	struct strbuf backlink = STRBUF_INIT;
 	struct strbuf gitdir = STRBUF_INIT;
 	struct strbuf olddotgit = STRBUF_INIT;
+	struct strbuf realolddotgit = STRBUF_INIT;
+	struct strbuf tmp = STRBUF_INIT;
 	char *git_contents = NULL;
 	const char *repair = NULL;
 	int err;
@@ -710,97 +744,131 @@ void repair_worktree_at_path(const char *path,
 		fn(1, realdotgit.buf, _("unable to locate repository; .git file broken"), cb_data);
 		goto done;
 	} else if (git_contents) {
-		strbuf_addstr(&backlink, git_contents);
+		if (is_absolute_path(git_contents)) {
+			strbuf_addstr(&backlink, git_contents);
+		} else {
+			strbuf_addbuf(&backlink, &realdotgit);
+			strbuf_strip_suffix(&backlink, ".git");
+			strbuf_addstr(&backlink, git_contents);
+		}
 	}
 
+	strbuf_realpath_forgiving(&backlink, backlink.buf, 0);
 	strbuf_addf(&gitdir, "%s/gitdir", backlink.buf);
 	if (strbuf_read_file(&olddotgit, gitdir.buf, 0) < 0)
 		repair = _("gitdir unreadable");
 	else {
 		strbuf_rtrim(&olddotgit);
-		if (fspathcmp(olddotgit.buf, realdotgit.buf))
+		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 (fspathcmp(realolddotgit.buf, realdotgit.buf))
 			repair = _("gitdir incorrect");
 	}
 
 	if (repair) {
 		fn(0, gitdir.buf, repair, cb_data);
-		write_file(gitdir.buf, "%s", realdotgit.buf);
+		write_file(gitdir.buf, "%s", relative_path(realdotgit.buf, backlink.buf, &tmp));
 	}
 done:
 	free(git_contents);
 	strbuf_release(&olddotgit);
+	strbuf_release(&realolddotgit);
 	strbuf_release(&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)
 {
 	struct stat st;
-	char *path;
+	struct strbuf dotgit = STRBUF_INIT;
+	struct strbuf gitdir = STRBUF_INIT;
+	struct strbuf repo = STRBUF_INIT;
+	char *path = NULL;
+	char *file = NULL;
+	int rc = 0;
 	int fd;
 	size_t len;
 	ssize_t read_result;
 
 	*wtpath = NULL;
-	if (!is_directory(git_path("worktrees/%s", id))) {
+	strbuf_realpath(&repo, git_common_path("worktrees/%s", id), 1);
+	strbuf_addf(&gitdir, "%s/gitdir", repo.buf);
+	if (!is_directory(repo.buf)) {
 		strbuf_addstr(reason, _("not a valid directory"));
-		return 1;
+		rc = 1;
+		goto done;
 	}
-	if (file_exists(git_path("worktrees/%s/locked", id)))
-		return 0;
-	if (stat(git_path("worktrees/%s/gitdir", id), &st)) {
+	file = xstrfmt("%s/locked", repo.buf);
+	if (file_exists(file)) {
+		goto done;
+	}
+	if (stat(gitdir.buf, &st)) {
 		strbuf_addstr(reason, _("gitdir file does not exist"));
-		return 1;
+		rc = 1;
+		goto done;
 	}
-	fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY);
+	fd = open(gitdir.buf, O_RDONLY);
 	if (fd < 0) {
 		strbuf_addf(reason, _("unable to read gitdir file (%s)"),
 			    strerror(errno));
-		return 1;
+		rc = 1;
+		goto done;
 	}
 	len = xsize_t(st.st_size);
 	path = xmallocz(len);
 
 	read_result = read_in_full(fd, path, len);
+	close(fd);
 	if (read_result < 0) {
 		strbuf_addf(reason, _("unable to read gitdir file (%s)"),
 			    strerror(errno));
-		close(fd);
-		free(path);
-		return 1;
-	}
-	close(fd);
-
-	if (read_result != len) {
+		rc = 1;
+		goto done;
+	} else if (read_result != len) {
 		strbuf_addf(reason,
 			    _("short read (expected %"PRIuMAX" bytes, read %"PRIuMAX")"),
 			    (uintmax_t)len, (uintmax_t)read_result);
-		free(path);
-		return 1;
+		rc = 1;
+		goto done;
 	}
 	while (len && (path[len - 1] == '\n' || path[len - 1] == '\r'))
 		len--;
 	if (!len) {
 		strbuf_addstr(reason, _("invalid gitdir file"));
-		free(path);
-		return 1;
+		rc = 1;
+		goto done;
 	}
 	path[len] = '\0';
-	if (!file_exists(path)) {
-		if (stat(git_path("worktrees/%s/index", id), &st) ||
-		    st.st_mtime <= expire) {
+	if (is_absolute_path(path)) {
+		strbuf_addstr(&dotgit, path);
+	} else {
+		strbuf_addf(&dotgit, "%s/%s", repo.buf, path);
+		strbuf_realpath_forgiving(&dotgit, dotgit.buf, 0);
+	}
+	if (!file_exists(dotgit.buf)) {
+		free(file);
+		file = xstrfmt("%s/index", repo.buf);
+		if (stat(file, &st) || st.st_mtime <= expire) {
 			strbuf_addstr(reason, _("gitdir file points to non-existent location"));
-			free(path);
-			return 1;
-		} else {
-			*wtpath = path;
-			return 0;
+			rc = 1;
+			goto done;
 		}
 	}
-	*wtpath = path;
-	return 0;
+	*wtpath = strbuf_detach(&dotgit, NULL);
+done:
+	free(path);
+	free(file);
+	strbuf_release(&dotgit);
+	strbuf_release(&gitdir);
+	strbuf_release(&repo);
+	return rc;
 }
 
 static int move_config_setting(const char *key, const char *value,
-- 
2.46.2
Caleb White· Oct 6, 2024, 04:59 UTC · re: Caleb White · lore

[PATCH 3/4] worktree: sync worktree paths after gitdir move

When re-initializing a repository with a separate gitdir (the original gitdir is moved to a new location), any linked worktrees become broken and must be repaired to reflect the new gitdir location. For absolute paths, this breakage is one-way, but is both ways for relative paths (the `<worktree>/.git` and the `<repo>/worktrees/<id>/gitdir`).

Previously, `repair_worktrees` was being called which loops through all the worktrees in the repository and updates the `<worktree>/.git` files to point to the new gitdir. However, when both sides of the worktrees are broken, the previous gitdir location is required to reestablish the link.

To fix this, the function `repair_worktrees_after_gitdir_move` is introduced. It takes the old gitdir path as an argument and repairs both sides of the worktree.

This change fixes the following test cases in t0001-init.sh:
- re-init to move gitdir with linked worktrees
- re-init to move gitdir within linked worktree
Signed-off-by: Caleb 
White <cdwhite3@pm.me>
---
 setup.c    |  2 +-
 worktree.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 worktree.h | 10 ++++++++++
 3 files changed, 65 insertions(+), 1 deletion(-)
Show changes to 3 files +65 −1

setup.c, worktree.c, worktree.h

diff --git a/setup.c b/setup.c
index 94e79b2..7b648de 100644
--- a/setup.c
+++ b/setup.c
@@ -2420,7 +2420,7 @@ static void separate_git_dir(const char *git_dir, const char *git_link)
 
 		if (rename(src, git_dir))
 			die_errno(_("unable to move %s to %s"), src, git_dir);
-		repair_worktrees(NULL, NULL);
+		repair_worktrees_after_gitdir_move(src);
 	}
 
 	write_file(git_link, "gitdir: %s", git_dir);
diff --git a/worktree.c b/worktree.c
index fc14e9a..b08ecce 100644
--- a/worktree.c
+++ b/worktree.c
@@ -650,6 +650,60 @@ void repair_worktrees(worktree_repair_fn fn, void *cb_data)
 	free_worktrees(worktrees);
 }
 
+void repair_worktree_after_gitdir_move(struct worktree *wt, const char *old_path)
+{
+	struct strbuf path = STRBUF_INIT;
+	struct strbuf repo = STRBUF_
INIT;
+	struct strbuf gitdir = STRBUF_INIT;
+	struct strbuf dotgit = STRBUF_INIT;
+	struct strbuf olddotgit = STRBUF_INIT;
+	struct strbuf tmp = STRBUF_INIT;
+
+	if (is_main_worktree(wt))
+		goto done;
+
+	strbuf_realpath(&repo, git_common_path("worktrees/%s", wt->id), 1);
+	strbuf_addf(&gitdir, "%s/gitdir", repo.buf);
+
+	if (strbuf_read_file(&olddotgit, gitdir.buf, 0) < 0)
+		goto done;
+
+	strbuf_rtrim(&olddotgit);
+	if (is_absolute_path(olddotgit.buf)) {
+		strbuf_addbuf(&dotgit, &olddotgit);
+	} else {
+		strbuf_addf(&dotgit, "%s/worktrees/%s/%s", old_path, wt->id, olddotgit.buf);
+		strbuf_realpath_forgiving(&dotgit, dotgit.buf, 0);
+	}
+
+	if (!file_exists(dotgit.buf))
+		goto done;
+
+	strbuf_addbuf(&path, &dotgit);
+	strbuf_strip_suffix(&path, "/.git");
+
+	write_file(dotgit.buf, "gitdir: %s", relative_path(repo.buf, path.buf, &tmp));
+	strbuf_reset(&tmp);
+	write_file(gitdir.buf, "%s", relative_path(dotgit.buf, repo.buf, &tmp));
+done:
+	strbu
f_release(&path);
+	strbuf_release(&repo);
+	strbuf_release(&gitdir);
+	strbuf_release(&dotgit);
+	strbuf_release(&olddotgit);
+	strbuf_release(&tmp);
+}
+
+void repair_worktrees_after_gitdir_move(const char *old_path)
+{
+	struct worktree **worktrees = get_worktrees_internal(1);
+	struct worktree **wt = worktrees + 1; /* +1 skips main worktree */
+
+	for (; *wt; wt++)
+		repair_worktree_after_gitdir_move(*wt, old_path);
+	free_worktrees(worktrees);
+}
+
 static int is_main_worktree_path(const char *path)
 {
 	struct strbuf target = STRBUF_INIT;
diff --git a/worktree.h b/worktree.h
index 11279d0..e961186 100644
--- a/worktree.h
+++ b/worktree.h
@@ -131,6 +131,16 @@ typedef void (* worktree_repair_fn)(int iserr, const char *path,
  */
 void repair_worktrees(worktree_repair_fn, void *cb_data);
 
+/*
+ * Repair the linked worktrees after the gitdir has been moved.
+ */
+void repair_worktrees_after_gitdir_move(const char *old_path);
+
+/*
+ * Repair the l
inked worktree after the gitdir has been moved.
+ */
+void repair_worktree_after_gitdir_move(struct worktree *wt, const char *old_path);
+
 /*
  * Repair administrative files corresponding to the worktree at the given path.
  * The worktree's .git file pointing at the repository must be intact for the
-- 
2.46.2
Caleb White· Oct 6, 2024, 04:59 UTC · re: Caleb White · lore

[PATCH 4/4] worktree: prevent null pointer dereference

If worktrees is NULL, free_worktrees() should return immediately to prevent a null pointer dereference.

Signed-off-by: Caleb White <cdwhite3@pm.me>
---
 worktree.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
Show changes to worktree.c +3 −2
diff --git a/worktree.c b/worktree.c
index b08ecce..1cf15b0 100644
--- a/worktree.c
+++ b/worktree.c
@@ -28,8 +28,9 @@ void free_worktree(struct worktree *worktree)
 
 void free_worktrees(struct worktree **worktrees)
 {
-	int i = 0;
-	for (i = 0; worktrees[i]; i++)
+	if  (!worktrees)
+		return;
+	for (int i = 0; worktrees[i]; i++)
 		free_worktree(worktrees[i]);
 	free (worktrees);
 }
-- 
2.46.2
Eric Sunshine· Oct 6, 2024, 05:04 UTC · re: Caleb White · lore

Re: [PATCH 0/4] Link worktrees with relative paths

On Sun, Oct 6, 2024 at 12:59 AM Caleb White <cdwhite3@pm.me> wrote:
Show 11 quoted lines
> This patch series modifies Git's handling of worktree linking to use
> relative paths instead of absolute paths. The motivation behind this
> change is to enhance the robustness of worktree links when the main
> repository is moved, or when used in environments like containerized
> development where absolute paths differ across systems.
> [...]
> Caleb White (4):
>   worktree: refactor infer_backlink() to use *strbuf
>   worktree: link worktrees with relative paths
>   worktree: sync worktree paths after gitdir move
>   worktree: prevent null pointer dereference

Unfortunately, these patches are whitespace-damaged. Can you please resubmit using either `git send-email` or GitGitGadget[1]?

[1]: https://gitgitgadget.github.io/
Caleb White· Oct 6, 2024, 05:11 UTC · re: Eric Sunshine · lore

Re: [PATCH 0/4] Link worktrees with relative paths

On Sunday, October 6th, 2024 at 00:04, Eric Sunshine <sunshine@sunshineco.com> wrote:
> On Sun, Oct 6, 2024 at 12:59 AM Caleb White cdwhite3@pm.me wrote:
> 
Show 12 quoted lines
> > This patch series modifies Git's handling of worktree linking to use
> > relative paths instead of absolute paths. The motivation behind this
> > change is to enhance the robustness of worktree links when the main
> > repository is moved, or when used in environments like containerized
> > development where absolute paths differ across systems.
> > [...]
> > Caleb White (4):
> > worktree: refactor infer_backlink() to use *strbuf
> > worktree: link worktrees with relative paths
> > worktree: sync worktree paths after gitdir move
> > worktree: prevent null pointer dereference
> 
> 
> Unfortunately, these patches are whitespace-damaged. Can you please
> resubmit using either `git send-email` or GitGitGadget[1]?
> 
> [1]: https://gitgitgadget.github.io/

I sent them with `git send-email`, let me try again and then if that doesn't work then I'll try GitGitGadget. Just out of curiosity, how could you tell that they are whitespaced-damaged (so I know what to look for)?

Thanks,
Eric Sunshine· Oct 6, 2024, 05:14 UTC · re: Caleb White · lore

Re: [PATCH 0/4] Link worktrees with relative paths

On Sun, Oct 6, 2024 at 1:11 AM Caleb White <cdwhite3@pm.me> wrote:
Show 8 quoted lines
> On Sunday, October 6th, 2024 at 00:04, Eric Sunshine <sunshine@sunshineco.com> wrote:
> > Unfortunately, these patches are whitespace-damaged. Can you please
> > resubmit using either `git send-email` or GitGitGadget[1]?
>
> I sent them with `git send-email`, let me try again and then if that
> doesn't work then I'll try GitGitGadget. Just out of curiosity, how
> could you tell that they are whitespaced-damaged (so I know what to
> look for)?
Hmm, that's very strange; git-send-email shouldn't be damaging them like that.

I noticed the whitespace-damage just by visual inspection as I quickly scanned my eyes over the patches. For instance, in patch [1/4], I see:

    + struct strbuf backlink = STRBUF_INIT;
       struct strbuf gitd
    ir = STRBUF_INIT;
The "gitdir" variable got split.
Eric Sunshine· Oct 6, 2024, 05:16 UTC · re: Eric Sunshine · lore

Re: [PATCH 0/4] Link worktrees with relative paths

On Sun, Oct 6, 2024 at 1:14 AM Eric Sunshine <sunshine@sunshineco.com> wrote:
Show 8 quoted lines
> I noticed the whitespace-damage just by visual inspection as I quickly
> scanned my eyes over the patches. For instance, in patch [1/4], I see:
>
>     + struct strbuf backlink = STRBUF_INIT;
>        struct strbuf gitd
>     ir = STRBUF_INIT;
>
> The "gitdir" variable got split.

Maybe "whitespace-damaged" was the wrong terminology. They are incorrectly wrapped/line-split.

Eric Sunshine· Oct 6, 2024, 05:11 UTC · re: Eric Sunshine · lore

Re: [PATCH 0/4] Link worktrees with relative paths

On Sun, Oct 6, 2024 at 1:04 AM Eric Sunshine <sunshine@sunshineco.com> wrote:
Show 9 quoted lines
> On Sun, Oct 6, 2024 at 12:59 AM Caleb White <cdwhite3@pm.me> wrote:
> > This patch series modifies Git's handling of worktree linking to use
> > relative paths instead of absolute paths. The motivation behind this
> > change is to enhance the robustness of worktree links when the main
> > repository is moved, or when used in environments like containerized
> > development where absolute paths differ across systems.
>
> Unfortunately, these patches are whitespace-damaged. Can you please
> resubmit using either `git send-email` or GitGitGadget[1]?

Also, since you're touching infer_backlink(), see [1] which makes some changes nearby.

And you might be interested in [2] which may indicate that there are some holes in the tests around worktrees which might need filling. (Since your patches are whitespace-damaged, I haven't checked whether your series succeeds or fails in the way the series to which [2] is a response fails.)

[1]: https://lore.kernel.org/git/20240923075416.54289-1-ericsunshine@charter.net/ [2]: https://lore.kernel.org/git/CAPig+cQXFy=xPVpoSq6Wq0pxMRCjS=WbkgdO+3LySPX=q0nPCw@mail.gmail.com/

Caleb White· Oct 6, 2024, 22:01 UTC · re: Eric Sunshine · lore

Re: [PATCH 0/4] Link worktrees with relative paths

On Sunday, October 6th, 2024 at 00:11, Eric Sunshine <sunshine@sunshineco.com> wrote:
 
> Also, since you're touching infer_backlink(), see [1] which makes some
> changes nearby.
> [1]: https://lore.kernel.org/git/20240923075416.54289-1-ericsunshine@charter.net/

Thanks for the heads up, what is the best way to move forward on this? Should I base my patch series on that topic branch or bring in that particular patch?

Show 6 quoted lines
> And you might be interested in [2] which may indicate that there are
> some holes in the tests around worktrees which might need filling.
> (Since your patches are whitespace-damaged, I haven't checked whether
> your series succeeds or fails in the way the series to which [2] is a
> response fails.)
> [2]: https://lore.kernel.org/git/CAPig+cQXFy=xPVpoSq6Wq0pxMRCjS=WbkgdO+3LySPX=q0nPCw@mail.gmail.com/

I just ran through the scenarios you described and everything works as you would expect. This is because internally the code handles both absolute and relative paths, and the worktree.path has been updated to always be the absolute path. I will make this clear when I rewrite that patch's commit message.

Eric Sunshine· Oct 6, 2024, 22:19 UTC · re: Caleb White · lore

Re: [PATCH 0/4] Link worktrees with relative paths

On Sun, Oct 6, 2024 at 6:01 PM Caleb White <cdwhite3@pm.me> wrote:
Show 8 quoted lines
> On Sunday, October 6th, 2024 at 00:11, Eric Sunshine <sunshine@sunshineco.com> wrote:
> > Also, since you're touching infer_backlink(), see [1] which makes some
> > changes nearby.
> > [1]: https://lore.kernel.org/git/20240923075416.54289-1-ericsunshine@charter.net/
>
> Thanks for the heads up, what is the best way to move forward on this?
> Should I base my patch series on that topic branch or bring in that
> particular patch?

If that particular patch impacts your patch series in a way which would require Junio (or any reviewer who wants to test your series) to make non-trivial changes to resolve merge conflicts, then it would probably be best to base your series atop that one (rather than bringing that patch into your series). When composing the cover material for your series, be sure to mention that you have based your series atop that patch.

Show 12 quoted lines
> > And you might be interested in [2] which may indicate that there are
> > some holes in the tests around worktrees which might need filling.
> > (Since your patches are whitespace-damaged, I haven't checked whether
> > your series succeeds or fails in the way the series to which [2] is a
> > response fails.)
> > [2]: https://lore.kernel.org/git/CAPig+cQXFy=xPVpoSq6Wq0pxMRCjS=WbkgdO+3LySPX=q0nPCw@mail.gmail.com/
>
> I just ran through the scenarios you described and everything works as
> you would expect. This is because internally the code handles both
> absolute and relative paths, and the worktree.path has been updated to
> always be the absolute path. I will make this clear when I rewrite that
> patch's commit message.
Thanks.
Caleb White· Oct 6, 2024, 05:41 UTC · lore

Re: [PATCH 0/4] Link worktrees with relative paths

On Sunday, October 6th, 2024 at 00:32, Eric Sunshine <sunshine@sunshineco.com> wrote:
> On Sun, Oct 6, 2024 at 1:21 AM Caleb White cdwhite3@pm.me wrote:
> 
> > On Sunday, October 6th, 2024 at 00:16, Eric Sunshine sunshine@sunshineco.com wrote:
> > 
> > > > + struct strbuf backlink = STRBUF_INIT;
> > > > struct strbuf gitd
> > > > ir = STRBUF_INIT;
> > 
> > That's strange, the code came through just fine on my end:
> 
> 
> It's broken in the Git mailing list archive, as well. If you search
> for "mbox.gz" at [], you can download this email thread. The patches
> in that mbox file have the same damage as I'm seeing.
> 
> [] https://lore.kernel.org/git/CAPig+cTVbXVffSeyNAV3c0zuSUozY7giWtMw3GpHs+xVEpaNvA@mail.gmail.com/T/

That's interesting...what's the best way to try and resubmit? As a new thread or as a v2?

Eric Sunshine· Oct 6, 2024, 05:50 UTC · re: Caleb White · lore

Re: [PATCH 0/4] Link worktrees with relative paths

On Sun, Oct 6, 2024 at 1:41 AM Caleb White <cdwhite3@pm.me> wrote:
Show 7 quoted lines
> On Sunday, October 6th, 2024 at 00:32, Eric Sunshine <sunshine@sunshineco.com> wrote:
> > It's broken in the Git mailing list archive, as well. If you search
> > for "mbox.gz" at [], you can download this email thread. The patches
> > in that mbox file have the same damage as I'm seeing.
>
> That's interesting...what's the best way to try and resubmit? As
> a new thread or as a v2?

For continuity in the archive, probably best to submit it as v2 as a reply to the original cover letter:

    git send-email -v2 --reply-to='20241006045847.159937-1-cdwhite3@pm.me' ...
Caleb White· Oct 6, 2024, 06:05 UTC · re: Eric Sunshine · lore

Re: [PATCH 0/4] Link worktrees with relative paths

On Sunday, October 6th, 2024 at 00:50, Eric Sunshine <sunshine@sunshineco.com> wrote:
> On Sun, Oct 6, 2024 at 1:41 AM Caleb White cdwhite3@pm.me wrote:
> 
> > On Sunday, October 6th, 2024 at 00:32, Eric Sunshine sunshine@sunshineco.com wrote:
> > 
> > > It's broken in the Git mailing list archive, as well. If you search
> > > for "mbox.gz" at [], you can download this email thread. The patches
> > > in that mbox file have the same damage as I'm seeing.
> > 
> > That's interesting...what's the best way to try and resubmit? As
> > a new thread or as a v2?
> 
> 
> For continuity in the archive, probably best to submit it as v2 as a
> reply to the original cover letter:
> 
> git send-email -v2 --reply-to='20241006045847.159937-1-cdwhite3@pm.me' ...

Thanks, I regenerated v2 patches with `format-patch` and then sent with `send-email`. However, the issue seems to have persisted (at least it's consistent?). I looked through the the patches on the listserv and that seems to be the only location where this occurs.

Eric Sunshine· Oct 6, 2024, 06:16 UTC · re: Caleb White · lore

Re: [PATCH 0/4] Link worktrees with relative paths

On Sun, Oct 6, 2024 at 2:05 AM Caleb White <cdwhite3@pm.me> wrote:
Show 9 quoted lines
> On Sunday, October 6th, 2024 at 00:50, Eric Sunshine <sunshine@sunshineco.com> wrote:
> > For continuity in the archive, probably best to submit it as v2 as a
> > reply to the original cover letter:
> > git send-email -v2 --reply-to='20241006045847.159937-1-cdwhite3@pm.me' ...
>
> Thanks, I regenerated v2 patches with `format-patch` and then sent with
> `send-email`. However, the issue seems to have persisted (at least it's
> consistent?). I looked through the the patches on the listserv and that
> seems to be the only location where this occurs.

Indeed, I'm seeing the same line-wrapping breakage in the mailing list archive and in my Inbox for v2.

I presume that you've verified that the raw patches are not broken like that, and that the problem is happening at email time?

Caleb White· Oct 6, 2024, 06:23 UTC · re: Eric Sunshine · lore

Re: [PATCH 0/4] Link worktrees with relative paths

On 10/6/24 01:16, Eric Sunshine <sunshine@sunshineco.com> wrote:
Show 5 quoted lines
>  Indeed, I'm seeing the same line-wrapping breakage in the mailing list
>  archive and in my Inbox for v2.
>  
>  I presume that you've verified that the raw patches are not broken
>  like that, and that the problem is happening at email time?
Yes, I've checked the patch files and they are good. I'm also not seeing any breakages in my inbox which is strange.
Eric Sunshine· Oct 6, 2024, 07:45 UTC · re: Caleb White · lore

Re: [PATCH 0/4] Link worktrees with relative paths

On Sun, Oct 6, 2024 at 2:23 AM Caleb White <cdwhite3@pm.me> wrote:
Show 8 quoted lines
> On 10/6/24 01:16, Eric Sunshine <sunshine@sunshineco.com> wrote:
> >  Indeed, I'm seeing the same line-wrapping breakage in the mailing list
> >  archive and in my Inbox for v2.
> >
> >  I presume that you've verified that the raw patches are not broken
> >  like that, and that the problem is happening at email time?
>
> Yes, I've checked the patch files and they are good. I'm also not seeing any breakages in my inbox which is strange.

I've been trying to hand-edit v2 to fix the breakage in order to get it applied, but I'm not having much success. I did manage to get patch [1/4] applied, but patch [2/4] is base64-encoded for some reason (you can see it here[*]), so I haven't been able to edit it easily in order to fix the breakage. `git am` isn't particularly happy with it either; it complains:

    % git am PATCH-v2-0-4-Link-worktrees-with-relative-paths.mbox
    Applying: worktree: refactor infer_backlink() to use *strbuf
    warning: quoted CRLF detected
    Applying: worktree: link worktrees with relative paths
    .git/rebase-apply/patch:60: trailing whitespace.
    #!/bin/sh
    .git/rebase-apply/patch:61: trailing whitespace.
    .git/rebase-apply/patch:62: trailing whitespace.
    test_description='test worktrees linked with relative paths'
    .git/rebase-apply/patch:63: trailing whitespace.
    .git/rebase-apply/patch:64: trailing whitespace.
    TEST_PASSES_SANITIZE_LEAK=true
    error: patch failed: builtin/worktree.c:414
    error: builtin/worktree.c: patch does not apply
    error: patch failed: worktree.c:110
    error: worktree.c: patch does not apply
    Patch failed at 0002 worktree: link worktrees with relative paths

Can you try GitGitGadget instead (preferable) or perhaps publish this series somewhere (less preferable).

[*]: https://lore.kernel.org/git/20241006060017.171788-3-cdwhite3@pm.me/raw
Kristoffer Haugsbakk· Oct 6, 2024, 09:00 UTC · re: Eric Sunshine · lore

Re: [PATCH 0/4] Link worktrees with relative paths

On Sun, Oct 6, 2024, at 09:45, EricSunshine wrote:
> Can you try GitGitGadget instead (preferable) or perhaps publish this
> series somewhere (less preferable).
>
> [*]: https://lore.kernel.org/git/20241006060017.171788-3-cdwhite3@pm.me/raw
If Caleb gives us a clone link then I could try to send.
Caleb White· Oct 6, 2024, 22:10 UTC · re: Kristoffer Haugsbakk · lore

Re: [PATCH 0/4] Link worktrees with relative paths

On Sunday, October 6th, 2024 at 04:00, Kristoffer Haugsbakk <kristofferhaugsbakk@fastmail.com> wrote:
> If Caleb gives us a clone link then I could try to send.
Here it is!
[1]: https://github.com/gitgitgadget/git/pull/1808
Caleb White· Oct 6, 2024, 22:08 UTC · re: Eric Sunshine · lore

Re: [PATCH 0/4] Link worktrees with relative paths

On Sunday, October 6th, 2024 at 02:45, Eric Sunshine <sunshine@sunshineco.com> wrote:
> Can you try GitGitGadget instead (preferable) or perhaps publish this
> series somewhere (less preferable).

I just created a GGG PR [1], can someone please give me /access? I will integrate the changes requested before I send out the v3 series through GGG.

Speaking of which, is there a way to tell GGG that I want to start on v3 to keep the continuity? I suppose I could always just reference the other versions in the PR body but I wasn't sure if there were other commands available besides just `/submit`.

[1]: https://github.com/gitgitgadget/git/pull/1808
Eric Sunshine· Oct 6, 2024, 22:24 UTC · re: Caleb White · lore

Re: [PATCH 0/4] Link worktrees with relative paths

On Sun, Oct 6, 2024 at 6:08 PM Caleb White <cdwhite3@pm.me> wrote:
Show 5 quoted lines
> On Sunday, October 6th, 2024 at 02:45, Eric Sunshine <sunshine@sunshineco.com> wrote:
> > Can you try GitGitGadget instead (preferable) or perhaps publish this
> > series somewhere (less preferable).
>
> I just created a GGG PR [1], can someone please give me /access?
Done.
> Speaking of which, is there a way to tell GGG that I want to start on v3 to
> keep the continuity? I suppose I could always just reference the other versions
> in the PR body but I wasn't sure if there were other commands available besides
> just `/submit`.

Um, I'm not a GitGitGadget user, so I can't say definitively, but I don't believe there is a way to do that.

Referencing the previous versions is always appreciated by reviewers anyhow since it makes their task simpler. In particular, in the cover commentary for any given version, reviewers like to see an explanation of the series, references to previous versions, and a prose description of what has changed in the current version as compared with the previous version. Reviewers also really appreciate seeing a mechanically-generated difference between versions, such as generated by `git format-patch --cover-letter --range-diff=...`, or generated automatically by GitGitGadget.

Caleb White· Oct 6, 2024, 22:32 UTC · re: Eric Sunshine · lore

Re: [PATCH 0/4] Link worktrees with relative paths

On Sunday, October 6th, 2024 at 17:24, Eric Sunshine <sunshine@sunshineco.com> wrote:
> On Sun, Oct 6, 2024 at 6:08 PM Caleb White cdwhite3@pm.me wrote:
> > I just created a GGG PR [1], can someone please give me /access?
> 
> Done.
Thank you!
Show 9 quoted lines
> Referencing the previous versions is always appreciated by reviewers
> anyhow since it makes their task simpler. In particular, in the cover
> commentary for any given version, reviewers like to see an explanation
> of the series, references to previous versions, and a prose
> description of what has changed in the current version as compared
> with the previous version. Reviewers also really appreciate seeing a
> mechanically-generated difference between versions, such as generated
> by `git format-patch --cover-letter --range-diff=...`, or generated
> automatically by GitGitGadget.
Thanks for the info, I'll make sure to include that in the PR.

← back to recent threads