diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index 2a240f53ba7..c4f59229194 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -105,6 +105,15 @@ passed to the command. In the event the repository has a remote and
 `--guess-remote` is used, but no remote or local branches exist, then the
 command fails with a warning reminding the user to fetch from their remote
 first (or override by using `-f/--force`).
++
+By default, the `git worktree add` command links the main worktree with the
+newly created worktree using absolute paths.
+However, this behavior may not be desirable when a repository needs
+to be accessed from different operating systems. To address this, Git can
+be configured to use relative paths instead. To enable this behavior, set the
+`worktree.useRelativePaths` configuration option to `true`. Once enabled,
+all newly added worktrees will be linked using relative paths.
+
 
 list::
 
@@ -128,6 +137,11 @@ Move a worktree to a new location. Note that the main worktree or linked
 worktrees containing submodules cannot be moved with this command. (The
 `git worktree repair` command, however, can reestablish the connection
 with linked worktrees if you move the main worktree manually.)
++
+When moving a worktree, the path formats will be adjusted to align with the
+current value of `worktree.useRelativePaths`. This means that if the worktree
+was created using absolute paths and the configuration is later set to `true`,
+the link will be reestablished using relative paths during the move.
 
 prune::
 
@@ -357,6 +371,10 @@ directory (e.g. `/path/main/.git/worktrees/test-next` in the example) and
 (e.g. `/path/main/.git`). These settings are made in a `.git` file located at
 the top directory of the linked worktree.
 
+If `worktree.useRelativePaths` is enabled, the paths in this `.git` file
+will be stored in a relative format. Conversely, if it is disabled, the paths
+will be stored in an absolute format.
+
 Path resolution via `git rev-parse --git-path` uses either
 `$GIT_DIR` or `$GIT_COMMON_DIR` depending on the path. For example, in the
 linked worktree `git rev-parse --git-path HEAD` returns
diff --git a/abspath.c b/abspath.c
index 1202cde23db..7a03becce99 100644
--- a/abspath.c
+++ b/abspath.c
@@ -1,6 +1,9 @@
 #include "git-compat-util.h"
 #include "abspath.h"
+#include "environment.h"
+#include "path.h"
 #include "strbuf.h"
+#include "worktree.h"
 
 /*
  * Do not use this for inspecting *tracked* content.  When path is a
@@ -290,6 +293,41 @@ char *prefix_filename_except_for_dash(const char *pfx, const char *arg)
 	return prefix_filename(pfx, arg);
 }
 
+char *worktree_real_pathdup(const char *wt_path)
+{
+	struct strbuf abs_wt_path_sb = STRBUF_INIT;
+	char *retval = NULL;
+	char *git_dir_real_path = NULL;
+	char *repo_real_path = NULL;
+
+	if (!is_absolute_path(wt_path)) {
+		git_dir_real_path = real_pathdup(get_git_common_dir(), 1);
+		repo_real_path = strip_path_suffix(git_dir_real_path, ".git");
+		if (repo_real_path) {
+			strbuf_addf(&abs_wt_path_sb, "%s/%s", repo_real_path,
+				    wt_path);
+		} else {
+			strbuf_addf(&abs_wt_path_sb, "%s/%s", git_dir_real_path,
+				    wt_path);
+		}
+
+		strbuf_realpath_forgiving(&abs_wt_path_sb, abs_wt_path_sb.buf, 1);
+		retval = strbuf_detach(&abs_wt_path_sb, NULL);
+	} else {
+		retval = xstrdup(wt_path);
+	}
+
+	free(git_dir_real_path);
+	free(repo_real_path);
+	strbuf_release(&abs_wt_path_sb);
+	return retval;
+}
+
+char *worktree_real_pathdup_for_wt(struct worktree *wt)
+{
+	return worktree_real_pathdup(wt->path);
+}
+
 void strbuf_add_absolute_path(struct strbuf *sb, const char *path)
 {
 	if (!*path)
diff --git a/abspath.h b/abspath.h
index 4653080d5e4..4163ac9c18d 100644
--- a/abspath.h
+++ b/abspath.h
@@ -1,6 +1,8 @@
 #ifndef ABSPATH_H
 #define ABSPATH_H
 
+struct worktree;
+
 int is_directory(const char *);
 char *strbuf_realpath(struct strbuf *resolved, const char *path,
 		      int die_on_error);
@@ -25,6 +27,48 @@ char *prefix_filename(const char *prefix, const char *path);
 /* Likewise, but path=="-" always yields "-" */
 char *prefix_filename_except_for_dash(const char *prefix, const char *path);
 
+/**
+ * worktree_real_pathdup - Duplicate the absolute path of a worktree.
+ *
+ * @wt_path: The path to the worktree. This can be either an absolute or
+ *           relative path.
+ *
+ * Return: A newly allocated string containing the absolute path. If the input
+ *         path is already absolute, it returns a duplicate of the input path.
+ *         If the path is relative, it constructs the absolute path by appending
+ *         the relative path to the repository directory. The repository
+ *         directory is derived from get_git_common_dir(), and the '.git' suffix
+ *         is removed if present.
+ *
+ *         The returned path is resolved into its canonical form using
+ *         strbuf_realpath_forgiving to handle symbolic links or non-existent
+ *         paths gracefully.
+ *
+ * The caller is responsible for freeing the returned string when it is no
+ * longer needed.
+ */
+char *worktree_real_pathdup(const char *wt_path);
+
+/**
+ * worktree_real_pathdup_for_wt - Duplicate the absolute path of a worktree from
+ *                                a worktree structure.
+ *
+ * @wt: A pointer to the worktree structure.
+ *
+ * Return: A newly allocated string containing the absolute path of the worktree.
+ *         If the worktree's path is relative, it constructs the absolute path
+ *         by appending the relative path to the repository directory (derived
+ *         from get_git_common_dir()). If the path is already absolute, it returns
+ *         a duplicate of the worktree's path.
+ *
+ * The caller is responsible for freeing the returned string when it is no
+ * longer needed.
+ *
+ * This function is similar to worktree_real_pathdup() but takes a pointer to a
+ * worktree structure instead of a raw path.
+ */
+char *worktree_real_pathdup_for_wt(struct worktree *wt);
+
 static inline int is_absolute_path(const char *path)
 {
 	return is_dir_sep(path[0]) || has_dos_drive_prefix(path);
diff --git a/builtin/mv.c b/builtin/mv.c
index 6c69033c5f5..8604525b85c 100644
--- a/builtin/mv.c
+++ b/builtin/mv.c
@@ -490,9 +490,9 @@ remove_entry:
 			if (!update_path_in_gitmodules(src, dst))
 				gitmodules_modified = 1;
 			if (submodule_gitfiles[i] != SUBMODULE_WITH_GITDIR)
-				connect_work_tree_and_git_dir(dst,
-							      submodule_gitfiles[i],
-							      1);
+				connect_submodule_work_tree_and_git_dir(dst,
+									submodule_gitfiles[i],
+									1);
 		}
 
 		if (mode & (WORKING_DIRECTORY | SKIP_WORKTREE_DIR))
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 3e0b6c45c03..98c0c1f6722 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -1806,7 +1806,7 @@ static int clone_submodule(const struct module_clone_data *clone_data,
 		      "git dir"), sm_gitdir);
 	}
 
-	connect_work_tree_and_git_dir(clone_data_path, sm_gitdir, 0);
+	connect_submodule_work_tree_and_git_dir(clone_data_path, sm_gitdir, 0);
 
 	p = git_pathdup_submodule(clone_data_path, "config");
 	if (!p)
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 41e7f6a3271..d217857843e 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -214,8 +214,12 @@ static void prune_worktrees(void)
 		strbuf_reset(&reason);
 		if (should_prune_worktree(d->d_name, &reason, &path, expire))
 			prune_worktree(d->d_name, reason.buf);
-		else if (path)
-			string_list_append_nodup(&kept, path)->util = xstrdup(d->d_name);
+		else if (path) {
+			string_list_append_nodup(&kept,
+						 worktree_real_pathdup(path))
+				->util = xstrdup(d->d_name);
+			free(path);
+		}
 	}
 	closedir(dir);
 
@@ -414,7 +418,7 @@ 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;
 	const char *name;
 	struct strvec child_env = STRVEC_INIT;
 	unsigned int counter = 0;
@@ -425,6 +429,7 @@ static int add_worktree(const char *path, const char *refname,
 	struct strbuf sb_name = STRBUF_INIT;
 	struct worktree **worktrees, *wt = NULL;
 	struct ref_store *wt_refs;
+	int use_relative_paths = 0;
 
 	worktrees = get_worktrees();
 	check_candidate_path(path, opts->force, worktrees, "add");
@@ -482,19 +487,14 @@ static int add_worktree(const char *path, const char *refname,
 	else
 		write_file(sb.buf, _("initializing"));
 
-	strbuf_addf(&sb_git, "%s/.git", path);
-	if (safe_create_leading_directories_const(sb_git.buf))
-		die_errno(_("could not create leading directories of '%s'"),
-			  sb_git.buf);
 	junk_work_tree = xstrdup(path);
 
 	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, get_git_common_dir(), 1);
-	write_file(sb_git.buf, "gitdir: %s/worktrees/%s",
-		   realpath.buf, name);
+	use_relative_paths = repo_config_get_worktree_use_relative_paths(the_repository);
+	strbuf_realpath_forgiving(&sb, path, 1);
+	connect_work_tree_and_git_dir(sb.buf, sb_repo.buf, use_relative_paths);
+	connect_gitdir_file_and_work_tree(sb.buf, name, use_relative_paths);
+
 	strbuf_reset(&sb);
 	strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
 	write_file(sb.buf, "../..");
@@ -536,6 +536,7 @@ static int add_worktree(const char *path, const char *refname,
 	if (the_repository->repository_format_worktree_config)
 		copy_filtered_worktree_config(sb_repo.buf);
 
+	strbuf_addf(&sb_git, "%s/.git", path);
 	strvec_pushf(&child_env, "%s=%s", GIT_DIR_ENVIRONMENT, sb_git.buf);
 	strvec_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path);
 
@@ -582,7 +583,6 @@ done:
 	strbuf_release(&sb_repo);
 	strbuf_release(&sb_git);
 	strbuf_release(&sb_name);
-	strbuf_release(&realpath);
 	free_worktree(wt);
 	return ret;
 }
@@ -1185,8 +1185,8 @@ static int move_worktree(int ac, const char **av, const char *prefix)
 	int force = 0;
 	struct option options[] = {
 		OPT__FORCE(&force,
-			 N_("force move even if worktree is dirty or locked"),
-			 PARSE_OPT_NOCOMPLETE),
+			   N_("force move even if worktree is dirty or locked"),
+			   PARSE_OPT_NOCOMPLETE),
 		OPT_END()
 	};
 	struct worktree **worktrees, *wt;
@@ -1263,6 +1263,7 @@ static void check_clean_worktree(struct worktree *wt,
 	struct child_process cp;
 	char buf[1];
 	int ret;
+	char *absolute_wt_path = NULL;
 
 	/*
 	 * Until we sort this out, all submodules are "dirty" and
@@ -1271,15 +1272,15 @@ static void check_clean_worktree(struct worktree *wt,
 	validate_no_submodules(wt);
 
 	child_process_init(&cp);
-	strvec_pushf(&cp.env, "%s=%s/.git",
-		     GIT_DIR_ENVIRONMENT, wt->path);
-	strvec_pushf(&cp.env, "%s=%s",
-		     GIT_WORK_TREE_ENVIRONMENT, wt->path);
-	strvec_pushl(&cp.args, "status",
-		     "--porcelain", "--ignore-submodules=none",
-		     NULL);
+	absolute_wt_path = worktree_real_pathdup_for_wt(wt);
+	strvec_pushf(&cp.env, "%s=%s/.git", GIT_DIR_ENVIRONMENT,
+		     absolute_wt_path);
+	strvec_pushf(&cp.env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT,
+		     absolute_wt_path);
+	strvec_pushl(&cp.args, "status", "--porcelain",
+		     "--ignore-submodules=none", NULL);
 	cp.git_cmd = 1;
-	cp.dir = wt->path;
+	cp.dir = absolute_wt_path;
 	cp.out = -1;
 	ret = start_command(&cp);
 	if (ret)
@@ -1294,6 +1295,8 @@ static void check_clean_worktree(struct worktree *wt,
 	if (ret)
 		die_errno(_("failed to run 'git status' on '%s', code %d"),
 			  original_path, ret);
+
+	free(absolute_wt_path);
 }
 
 static int delete_git_work_tree(struct worktree *wt)
diff --git a/config.c b/config.c
index 56b5862e59d..5750d258ac9 100644
--- a/config.c
+++ b/config.c
@@ -2731,6 +2731,16 @@ int repo_config_get_expiry_in_days(struct repository *r, const char *key,
 	return -1; /* thing exists but cannot be parsed */
 }
 
+int repo_config_get_worktree_use_relative_paths(struct repository *r)
+{
+	int val;
+
+	if (!repo_config_get_maybe_bool(r, "worktree.userelativepaths", &val))
+		return val;
+
+	return 0; /* by default, absolute paths are used */
+}
+
 int repo_config_get_split_index(struct repository *r)
 {
 	int val;
diff --git a/config.h b/config.h
index d0497157c52..77e1c3812fb 100644
--- a/config.h
+++ b/config.h
@@ -678,6 +678,21 @@ int repo_config_get_expiry(struct repository *r, const char *key, char **output)
 int repo_config_get_expiry_in_days(struct repository *r, const char *key,
 				   timestamp_t *, timestamp_t now);
 
+/**
+ * Retrieves the configuration that determines if worktrees should use relative paths.
+ *
+ * This function checks the "worktree.userelativepaths" configuration.
+ * If the configuration was found and is a valid boolean value, the function returns that value.
+ * If the configuration was not found or invalid, the function returns 0,
+ * indicating that by default, absolute paths are used for worktrees.
+ *
+ * Return:
+ * - 1: If "worktree.userelativepaths" is set to true (use relative paths).
+ * - 0: If "worktree.userelativepaths" is set to false or is not set
+ *      (use absolute paths by default).
+ */
+int repo_config_get_worktree_use_relative_paths(struct repository *r);
+
 /**
  * First prints the error message specified by the caller in `err` and then
  * dies printing the line number and the file name of the highest priority
diff --git a/dir.c b/dir.c
index 5a23376bdae..49aba2c0a72 100644
--- a/dir.c
+++ b/dir.c
@@ -4041,43 +4041,117 @@ static void connect_wt_gitdir_in_nested(const char *sub_worktree,
 		strbuf_addf(&sub_wt, "%s/%s", sub_worktree, sub->path);
 		submodule_name_to_gitdir(&sub_gd, &subrepo, sub->name);
 
-		connect_work_tree_and_git_dir(sub_wt.buf, sub_gd.buf, 1);
+		connect_submodule_work_tree_and_git_dir(sub_wt.buf, sub_gd.buf,
+							1);
 	}
 	strbuf_release(&sub_wt);
 	strbuf_release(&sub_gd);
 	repo_clear(&subrepo);
 }
 
-void connect_work_tree_and_git_dir(const char *work_tree_,
-				   const char *git_dir_,
-				   int recurse_into_nested)
+void connect_work_tree_and_git_dir(const char *work_tree_, const char *git_dir_,
+				   int use_relative_paths_)
 {
+	struct strbuf rel_path_sb = STRBUF_INIT;
 	struct strbuf gitfile_sb = STRBUF_INIT;
+	char *git_dir = NULL, *work_tree = NULL;
+	const char *rel_path = NULL;
+	int free_gitdir = 0;
+
+	work_tree = worktree_real_pathdup(work_tree_);
+	strbuf_addf(&gitfile_sb, "%s/.git", work_tree);
+	if (safe_create_leading_directories_const(gitfile_sb.buf))
+		die(_("could not create directories for %s"), gitfile_sb.buf);
+
+	if (!is_absolute_path(git_dir_)) {
+		git_dir = real_pathdup(git_dir_, 1);
+		free_gitdir = 1;
+	} else
+		git_dir = (char *)git_dir_;
+
+	if (use_relative_paths_) {
+		rel_path = relative_path(git_dir, work_tree, &rel_path_sb);
+
+		/* Write a relative path */
+		write_file(gitfile_sb.buf, "gitdir: %s", rel_path);
+	} else {
+		/* Write an absolute path */
+		write_file(gitfile_sb.buf, "gitdir: %s", git_dir);
+	}
+
+	if (free_gitdir)
+		free(git_dir);
+
+	free(work_tree);
+	strbuf_release(&rel_path_sb);
+	strbuf_release(&gitfile_sb);
+}
+
+void connect_gitdir_file_and_work_tree(const char *wt_dir_,
+				       const char *wt_name_,
+				       int use_relative_paths_)
+{
+	struct strbuf gitdirfile_sb = STRBUF_INIT;
+	struct strbuf rel_path_sb = STRBUF_INIT;
+	char *git_dir_real_path = NULL;
+	char *repo_real_path = NULL, *work_tree = NULL;
+	const char *rel_path = NULL;
+
+	git_path_buf(&gitdirfile_sb, "worktrees/%s/gitdir", wt_name_);
+	if (safe_create_leading_directories_const(gitdirfile_sb.buf)) {
+		die(_("could not create directories for %s"),
+		    gitdirfile_sb.buf);
+	}
+
+	work_tree = worktree_real_pathdup(wt_dir_);
+	if (use_relative_paths_) {
+		git_dir_real_path = real_pathdup(get_git_common_dir(), 1);
+		repo_real_path = strip_path_suffix(git_dir_real_path, ".git");
+		if (repo_real_path) {
+			rel_path = relative_path(work_tree, repo_real_path,
+						 &rel_path_sb);
+		} else {
+			rel_path = relative_path(work_tree, git_dir_real_path,
+						 &rel_path_sb);
+		}
+
+		/* Write a relative path */
+		write_file(gitdirfile_sb.buf, "%s/.git", rel_path);
+	} else {
+		/* Write an absolute path */
+		write_file(gitdirfile_sb.buf, "%s/.git", work_tree);
+	}
+
+	free(repo_real_path);
+	free(work_tree);
+	free(git_dir_real_path);
+	strbuf_release(&rel_path_sb);
+	strbuf_release(&gitdirfile_sb);
+}
+
+void connect_submodule_work_tree_and_git_dir(const char *work_tree_,
+					     const char *git_dir_,
+					     int recurse_into_nested)
+{
 	struct strbuf cfg_sb = STRBUF_INIT;
 	struct strbuf rel_path = STRBUF_INIT;
 	char *git_dir, *work_tree;
 
-	/* Prepare .git file */
-	strbuf_addf(&gitfile_sb, "%s/.git", work_tree_);
-	if (safe_create_leading_directories_const(gitfile_sb.buf))
-		die(_("could not create directories for %s"), gitfile_sb.buf);
+	git_dir = real_pathdup(git_dir_, 1);
+	work_tree = real_pathdup(work_tree_, 1);
+
+	/* Write .git file */
+	connect_work_tree_and_git_dir(work_tree, git_dir, 1);
 
 	/* Prepare config file */
 	strbuf_addf(&cfg_sb, "%s/config", git_dir_);
 	if (safe_create_leading_directories_const(cfg_sb.buf))
 		die(_("could not create directories for %s"), cfg_sb.buf);
 
-	git_dir = real_pathdup(git_dir_, 1);
-	work_tree = real_pathdup(work_tree_, 1);
-
-	/* Write .git file */
-	write_file(gitfile_sb.buf, "gitdir: %s",
-		   relative_path(git_dir, work_tree, &rel_path));
 	/* Update core.worktree setting */
 	git_config_set_in_file(cfg_sb.buf, "core.worktree",
 			       relative_path(work_tree, git_dir, &rel_path));
 
-	strbuf_release(&gitfile_sb);
 	strbuf_release(&cfg_sb);
 	strbuf_release(&rel_path);
 
@@ -4097,7 +4171,7 @@ void relocate_gitdir(const char *path, const char *old_git_dir, const char *new_
 		die_errno(_("could not migrate git directory from '%s' to '%s'"),
 			old_git_dir, new_git_dir);
 
-	connect_work_tree_and_git_dir(path, new_git_dir, 0);
+	connect_submodule_work_tree_and_git_dir(path, new_git_dir, 0);
 }
 
 int path_match_flags(const char *const str, const enum path_match_flags flags)
diff --git a/dir.h b/dir.h
index a3a2f00f5d9..56e8224167e 100644
--- a/dir.h
+++ b/dir.h
@@ -597,6 +597,75 @@ void write_untracked_extension(struct strbuf *out, struct untracked_cache *untra
 void add_untracked_cache(struct index_state *istate);
 void remove_untracked_cache(struct index_state *istate);
 
+/**
+ * connect_work_tree_and_git_dir - Create a .git file in the worktree that
+ *                                 points to the main repository.
+ *
+ * @work_tree_: The path to the worktree that needs to be connected. This can
+ *              be either an absolute or relative path.
+ * @git_dir_: The path to the main repository's git directory (.git). This can
+ *            be either an absolute or relative path.
+ * @use_relative_paths_: Flag indicating whether to use relative paths when
+ *                       creating the connection. If non-zero, a relative path
+ *                       between the git directory and the worktree is used.
+ *                       Otherwise, absolute paths are used.
+ *
+ * This function creates a `.git` file in the specified worktree directory,
+ * which contains either a relative or absolute path pointing back to the main
+ * repository's .git directory. This connection allows the worktree to function
+ * as an independent working directory while still being linked to the main
+ * repository.
+ *
+ * If `use_relative_paths_` is set to non-zero, the function writes a relative
+ * path from the worktree to the git directory in the `.git` file. If the
+ * worktree path is provided as a relative path, it is first converted to an
+ * absolute path before determining the relative path.
+ *
+ * If `use_relative_paths_` is zero, the function writes an absolute path to
+ * the git directory in the `.git` file.
+ *
+ * In case of any errors during directory creation or file writing, the
+ * function terminates execution with an error message.
+ */
+void connect_work_tree_and_git_dir(const char *work_tree_,
+				   const char *git_dir_,
+				   int use_relative_paths_);
+
+/**
+ * connect_gitdir_file_and_work_tree - Connect a gitdir file to a worktree.
+ *
+ * @wt_dir_: The path to the worktree directory. This can be either an
+ *           absolute or relative path.
+ * @wt_name_: The name of the worktree as it appears in the git repository's
+ *            worktrees directory.
+ * @use_relative_paths_: Flag indicating whether to use relative paths when
+ *                       creating the connection. If non-zero, a relative path
+ *                       between the git directory and the worktree is used.
+ *                       Otherwise, absolute paths are used.
+ *
+ * This function creates a gitdir file in the git repository's worktrees
+ * directory, which contains a path to the worktree directory. The path
+ * can be relative or absolute based on the value of `use_relative_paths_`.
+ *
+ * The gitdir file is created under the worktrees directory with a name that
+ * matches the `wt_name_` parameter. The file contains a reference to the
+ * worktree's `.git` file.
+ *
+ * If `use_relative_paths_` is set to non-zero, the function writes a relative
+ * path from the repository root to the worktree in the gitdir file. If the
+ * worktree path is provided as a relative path, it is first converted to an
+ * absolute path before determining the relative path.
+ *
+ * If `use_relative_paths_` is zero, the function writes an absolute path to
+ * the work tree in the gitdir file.
+ *
+ * In case of any errors during directory creation or file writing, the
+ * function terminates execution with an error message.
+ */
+void connect_gitdir_file_and_work_tree(const char *wt_dir_,
+				       const char *wt_name_,
+				       int use_relative_paths_);
+
 /*
  * Connect a worktree to a git directory by creating (or overwriting) a
  * '.git' file containing the location of the git directory. In the git
@@ -604,9 +673,9 @@ void remove_untracked_cache(struct index_state *istate);
  * When `recurse_into_nested` is set, recurse into any nested submodules,
  * connecting them as well.
  */
-void connect_work_tree_and_git_dir(const char *work_tree,
-				   const char *git_dir,
-				   int recurse_into_nested);
+void connect_submodule_work_tree_and_git_dir(const char *work_tree,
+					     const char *git_dir,
+					     int recurse_into_nested);
 void relocate_gitdir(const char *path,
 		     const char *old_git_dir,
 		     const char *new_git_dir);
diff --git a/submodule.c b/submodule.c
index 97516b0fec1..1b3c6bde4a9 100644
--- a/submodule.c
+++ b/submodule.c
@@ -1672,7 +1672,7 @@ get_fetch_task_from_changed(struct submodule_parallel_fetch *spf,
 		 * core.worktree when they are populated/unpopulated by
 		 * "git checkout" (and similar commands, see
 		 * submodule_move_head() and
-		 * connect_work_tree_and_git_dir()), but if the
+		 * connect_submodule_work_tree_and_git_dir()), but if the
 		 * submodule is unpopulated in another way (e.g. "git
 		 * rm", "rm -r"), core.worktree will still be set even
 		 * though the directory doesn't exist, and the child
@@ -2147,7 +2147,7 @@ int submodule_move_head(const char *path, const char *super_prefix,
 	if (flags & SUBMODULE_MOVE_HEAD_FORCE)
 		/*
 		 * Pass non NULL pointer to is_submodule_populated_gently
-		 * to prevent die()-ing. We'll use connect_work_tree_and_git_dir
+		 * to prevent die()-ing. We'll use connect_submodule_work_tree_and_git_dir
 		 * to fixup the submodule in the force case later.
 		 */
 		error_code_ptr = &error_code;
@@ -2194,7 +2194,8 @@ int submodule_move_head(const char *path, const char *super_prefix,
 				die(_("refusing to create/use '%s' in another "
 				      "submodule's git dir"),
 				    gitdir.buf);
-			connect_work_tree_and_git_dir(path, gitdir.buf, 0);
+			connect_submodule_work_tree_and_git_dir(path,
+								gitdir.buf, 0);
 			strbuf_release(&gitdir);
 
 			/* make sure the index is clean as well */
@@ -2205,7 +2206,8 @@ int submodule_move_head(const char *path, const char *super_prefix,
 			struct strbuf gitdir = STRBUF_INIT;
 			submodule_name_to_gitdir(&gitdir, the_repository,
 						 sub->name);
-			connect_work_tree_and_git_dir(path, gitdir.buf, 1);
+			connect_submodule_work_tree_and_git_dir(path,
+								gitdir.buf, 1);
 			strbuf_release(&gitdir);
 		}
 	}
@@ -2457,7 +2459,7 @@ void absorb_git_dir_into_superproject(const char *path,
 		if (!sub)
 			die(_("could not lookup name for submodule '%s'"), path);
 		submodule_name_to_gitdir(&sub_gitdir, the_repository, sub->name);
-		connect_work_tree_and_git_dir(path, sub_gitdir.buf, 0);
+		connect_submodule_work_tree_and_git_dir(path, sub_gitdir.buf, 0);
 		strbuf_release(&sub_gitdir);
 	} else {
 		/* Is it already absorbed into the superprojects git dir? */
diff --git a/t/lib-worktree.sh b/t/lib-worktree.sh
new file mode 100644
index 00000000000..245612b6e4d
--- /dev/null
+++ b/t/lib-worktree.sh
@@ -0,0 +1,156 @@
+# Helper functions for git worktree tests
+
+# is_absolute_path - Determine if a given path is absolute.
+#
+# This function checks if the provided path is an absolute path.
+# It handles Unix-like and Windows-style paths.
+#
+# Parameters:
+# $1 - The path to check.
+#
+# Returns:
+# 0 if the path is absolute.
+# 1 if the path is relative.
+is_absolute_path() {
+	local path="$1"
+
+	# Check for Unix-style absolute path (starts with /)
+	case "$path" in
+		/*) return 0 ;;
+	esac
+
+	# Check for Windows-style absolute path with backslashes (starts with drive letter followed by :\)
+	case "$path" in
+		[a-zA-Z]:\\*) return 0 ;;
+	esac
+
+	# Check for Windows-style absolute path with forward slashes (starts with drive letter followed by :/)
+	case "$path" in
+		[a-zA-Z]:/*) return 0 ;;
+	esac
+
+	return 1
+}
+
+# check_worktree_paths - Verify the format of the worktree paths.
+#
+# This function checks whether the paths specified in the worktree's
+# configuration files (.git and gitdir files) are in the expected format,
+# based on the provided configuration for relative or absolute paths.
+#
+# Parameters:
+# $1 - Boolean value ("true" or "false") indicating whether relative paths
+#      are expected. If "true", the function expects relative paths; otherwise,
+#      it expects absolute paths.
+# $2 - The path to the worktree directory.
+#
+# Functionality:
+# - Reads the .git file in the specified worktree directory to extract the path
+#   to the gitdir file.
+# - Determines whether the extracted path is relative or absolute based
+#   on the value of $1.
+# - Checks if the gitdir file exists and if its contents match the expected path
+#   format (relative or absolute).
+# - Verify that the path in the gitdir file points back to the original worktree .git file.
+# - Prints an error message and returns a non-zero exit code if any issues are
+#   found, such as missing files or incorrect path formats.
+# - Returns 0 if all checks pass and the paths are in the expected format.
+check_worktree_paths() {
+	local func_name="check_worktree_paths"
+	local use_relative_paths="$1"
+	local worktree_path="$2"
+	if [ -d "$worktree_path" ]; then
+		worktree_path="$(cd "$worktree_path" && pwd -P)"
+	else
+		echo "[$func_name] Error: Directory "$worktree_path" does not exist."
+		return 1
+	fi
+
+	# Full path to the .git file in the worktree
+	local git_file="$worktree_path/.git"
+
+	# Check if the .git file exists
+	if [ ! -f "$git_file" ]; then
+		echo "[$func_name] Error: .git file not found in $worktree_path"
+		return 1
+	fi
+
+	# Extract the path from the .git file
+	local gitdir_path="$(sed 's/^gitdir: //' "$git_file")"
+
+	# Check if the path is absolute or relative
+	if [ "$use_relative_paths" = "true" ]; then
+		# Ensure the path is relative
+		if is_absolute_path "$gitdir_path"; then
+			echo "[$func_name] Error: .git file contains an absolute path when a relative path was expected."
+			echo "[$func_name] Path read from .git file: $gitdir_path"
+			return 1
+		fi
+	else
+		# Ensure the path is absolute
+		if ! is_absolute_path "$gitdir_path"; then
+			echo "[$func_name] Error: .git file contains a relative path when an absolute path was expected."
+			echo "[$func_name] Path read from .git file: $gitdir_path"
+			return 1
+		fi
+	fi
+
+	# Resolve the gitdir path relative to worktree if necessary
+	if ! is_absolute_path "$gitdir_path"; then
+		gitdir_path="$(cd "$worktree_path"/"$gitdir_path" && pwd -P)"
+	fi
+
+	# Verify if gitdir_path is correct and the gitdir file exists
+	local gitdir_file="$gitdir_path/gitdir"
+	if [ ! -f "$gitdir_file" ]; then
+		echo "[$func_name] Error: $gitdir_file not found"
+		return 1
+	fi
+
+	# Read the stored path from the gitdir file
+	local stored_path="$(cat "$gitdir_file")"
+
+	if [ "$use_relative_paths" = "true" ]; then
+		# Ensure the path is relative
+		if is_absolute_path "$stored_path"; then
+			echo "[$func_name] Error: $gitdir_file contains an absolute path when a relative path was expected."
+			echo "[$func_name] Path read from gitdir file: $stored_path"
+			return 1
+		fi
+	else
+		# Ensure the path is absolute
+		if ! is_absolute_path "$stored_path"; then
+			echo "[$func_name] Error: $gitdir_file contains a relative path when an absolute path was expected."
+			echo "[$func_name] Path read from gitdir file: $stored_path"
+			return 1
+		fi
+	fi
+
+	# Resolve the stored_path path to an absolute path
+	if ! is_absolute_path "$stored_path"; then
+		# Determine the repo dir, by removing the /.git/worktrees/<worktree_dir> or /worktrees/<worktree_dir> part
+		local repo_dir="${gitdir_path%/*/*}"
+		repo_dir="${repo_dir%/.git}"
+
+		# If repo_dir is a relative path, resolve it against worktree_path
+		if ! is_absolute_path "$repo_dir"; then
+			repo_dir="$(cd "$worktree_path/$repo_dir" && pwd -P)"
+		fi
+
+		stored_path="$(cd "$(dirname "$repo_dir/$stored_path")" && pwd -P)/.git"
+		if [ ! -f "$stored_path" ]; then
+			echo "[$func_name] Error: File $stored_path does not exist."
+			return 1
+		fi
+	fi
+
+	# Verify that the stored_path points back to the original worktree .git file
+	if [ "$stored_path" != "$git_file" ]; then
+		echo "[$func_name]  Error: The gitdir file does not correctly reference the original .git file."
+		echo "Expected: $git_file"
+		echo "Found: $stored_path"
+		return 1
+	fi
+
+	return 0
+}
diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh
index cfc4aeb1798..ca9bbf0aac5 100755
--- a/t/t2400-worktree-add.sh
+++ b/t/t2400-worktree-add.sh
@@ -10,421 +10,21 @@ TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 . "$TEST_DIRECTORY"/lib-rebase.sh
+. "$TEST_DIRECTORY"/lib-worktree.sh
 
-test_expect_success 'setup' '
-	test_commit init
-'
-
-test_expect_success '"add" an existing worktree' '
-	mkdir -p existing/subtree &&
-	test_must_fail git worktree add --detach existing main
-'
-
-test_expect_success '"add" an existing empty worktree' '
-	mkdir existing_empty &&
-	git worktree add --detach existing_empty main
-'
-
-test_expect_success '"add" using shorthand - fails when no previous branch' '
-	test_must_fail git worktree add existing_short -
-'
-
-test_expect_success '"add" using - shorthand' '
-	git checkout -b newbranch &&
-	echo hello >myworld &&
-	git add myworld &&
-	git commit -m myworld &&
-	git checkout main &&
-	git worktree add short-hand - &&
-	echo refs/heads/newbranch >expect &&
-	git -C short-hand rev-parse --symbolic-full-name HEAD >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success '"add" refuses to checkout locked branch' '
-	test_must_fail git worktree add zere main &&
-	! test -d zere &&
-	! test -d .git/worktrees/zere
-'
-
-test_expect_success 'checking out paths not complaining about linked checkouts' '
-	(
-	cd existing_empty &&
-	echo dirty >>init.t &&
-	git checkout main -- init.t
-	)
-'
-
-test_expect_success '"add" worktree' '
-	git rev-parse HEAD >expect &&
-	git worktree add --detach here main &&
-	(
-		cd here &&
-		test_cmp ../init.t init.t &&
-		test_must_fail git symbolic-ref HEAD &&
-		git rev-parse HEAD >actual &&
-		test_cmp ../expect actual &&
-		git fsck
-	)
-'
-
-test_expect_success '"add" worktree with lock' '
-	git worktree add --detach --lock here-with-lock main &&
-	test_when_finished "git worktree unlock here-with-lock || :" &&
-	test -f .git/worktrees/here-with-lock/locked
-'
-
-test_expect_success '"add" worktree with lock and reason' '
-	lock_reason="why not" &&
-	git worktree add --detach --lock --reason "$lock_reason" here-with-lock-reason main &&
-	test_when_finished "git worktree unlock here-with-lock-reason || :" &&
-	test -f .git/worktrees/here-with-lock-reason/locked &&
-	echo "$lock_reason" >expect &&
-	test_cmp expect .git/worktrees/here-with-lock-reason/locked
-'
-
-test_expect_success '"add" worktree with reason but no lock' '
-	test_must_fail git worktree add --detach --reason "why not" here-with-reason-only main &&
-	test_path_is_missing .git/worktrees/here-with-reason-only/locked
-'
-
-test_expect_success '"add" worktree from a subdir' '
-	(
-		mkdir sub &&
-		cd sub &&
-		git worktree add --detach here main &&
-		cd here &&
-		test_cmp ../../init.t init.t
-	)
-'
-
-test_expect_success '"add" from a linked checkout' '
-	(
-		cd here &&
-		git worktree add --detach nested-here main &&
-		cd nested-here &&
-		git fsck
-	)
-'
-
-test_expect_success '"add" worktree creating new branch' '
-	git worktree add -b newmain there main &&
-	(
-		cd there &&
-		test_cmp ../init.t init.t &&
-		git symbolic-ref HEAD >actual &&
-		echo refs/heads/newmain >expect &&
-		test_cmp expect actual &&
-		git fsck
-	)
-'
-
-test_expect_success 'die the same branch is already checked out' '
-	(
-		cd here &&
-		test_must_fail git checkout newmain 2>actual &&
-		grep "already used by worktree at" actual
-	)
-'
-
-test_expect_success 'refuse to reset a branch in use elsewhere' '
-	(
-		cd here &&
-
-		# we know we are on detached HEAD but just in case ...
-		git checkout --detach HEAD &&
-		git rev-parse --verify HEAD >old.head &&
-
-		git rev-parse --verify refs/heads/newmain >old.branch &&
-		test_must_fail git checkout -B newmain 2>error &&
-		git rev-parse --verify refs/heads/newmain >new.branch &&
-		git rev-parse --verify HEAD >new.head &&
-
-		grep "already used by worktree at" error &&
-		test_cmp old.branch new.branch &&
-		test_cmp old.head new.head &&
-
-		# and we must be still on the same detached HEAD state
-		test_must_fail git symbolic-ref HEAD
-	)
-'
-
-test_expect_success SYMLINKS 'die the same branch is already checked out (symlink)' '
-	head=$(git -C there rev-parse --git-path HEAD) &&
-	ref=$(git -C there symbolic-ref HEAD) &&
-	rm "$head" &&
-	ln -s "$ref" "$head" &&
-	test_must_fail git -C here checkout newmain
-'
-
-test_expect_success 'not die the same branch is already checked out' '
-	(
-		cd here &&
-		git worktree add --force anothernewmain newmain
-	)
-'
-
-test_expect_success 'not die on re-checking out current branch' '
-	(
-		cd there &&
-		git checkout newmain
-	)
-'
-
-test_expect_success '"add" from a bare repo' '
-	(
-		git clone --bare . bare &&
-		cd bare &&
-		git worktree add -b bare-main ../there2 main
-	)
-'
-
-test_expect_success 'checkout from a bare repo without "add"' '
-	(
-		cd bare &&
-		test_must_fail git checkout main
-	)
-'
-
-test_expect_success '"add" default branch of a bare repo' '
-	(
-		git clone --bare . bare2 &&
-		cd bare2 &&
-		git worktree add ../there3 main &&
-		cd ../there3 &&
-		# Simple check that a Git command does not
-		# immediately fail with the current setup
-		git status
-	) &&
-	cat >expect <<-EOF &&
-	init.t
-	EOF
-	ls there3 >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success '"add" to bare repo with worktree config' '
-	(
-		git clone --bare . bare3 &&
-		cd bare3 &&
-		git config extensions.worktreeconfig true &&
-
-		# Add config values that are erroneous to have in
-		# a config.worktree file outside of the main
-		# working tree, to check that Git filters them out
-		# when copying config during "git worktree add".
-		git config --worktree core.bare true &&
-		git config --worktree core.worktree "$(pwd)" &&
-
-		# We want to check that bogus.key is copied
-		git config --worktree bogus.key value &&
-		git config --unset core.bare &&
-		git worktree add ../there4 main &&
-		cd ../there4 &&
-
-		# Simple check that a Git command does not
-		# immediately fail with the current setup
-		git status &&
-		git worktree add --detach ../there5 &&
-		cd ../there5 &&
-		git status
-	) &&
-
-	# the worktree has the arbitrary value copied.
-	test_cmp_config -C there4 value bogus.key &&
-	test_cmp_config -C there5 value bogus.key &&
-
-	# however, core.bare and core.worktree were removed.
-	test_must_fail git -C there4 config core.bare &&
-	test_must_fail git -C there4 config core.worktree &&
-
-	cat >expect <<-EOF &&
-	init.t
-	EOF
-
-	ls there4 >actual &&
-	test_cmp expect actual &&
-	ls there5 >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'checkout with grafts' '
-	test_when_finished rm .git/info/grafts &&
-	test_commit abc &&
-	SHA1=$(git rev-parse HEAD) &&
-	test_commit def &&
-	test_commit xyz &&
-	mkdir .git/info &&
-	echo "$(git rev-parse HEAD) $SHA1" >.git/info/grafts &&
-	cat >expected <<-\EOF &&
-	xyz
-	abc
-	EOF
-	git log --format=%s -2 >actual &&
-	test_cmp expected actual &&
-	git worktree add --detach grafted main &&
-	git --git-dir=grafted/.git log --format=%s -2 >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success '"add" from relative HEAD' '
-	test_commit a &&
-	test_commit b &&
-	test_commit c &&
-	git rev-parse HEAD~1 >expected &&
-	git worktree add relhead HEAD~1 &&
-	git -C relhead rev-parse HEAD >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success '"add -b" with <branch> omitted' '
-	git worktree add -b burble flornk &&
-	test_cmp_rev HEAD burble
-'
-
-test_expect_success '"add --detach" with <branch> omitted' '
-	git worktree add --detach fishhook &&
-	git rev-parse HEAD >expected &&
-	git -C fishhook rev-parse HEAD >actual &&
-	test_cmp expected actual &&
-	test_must_fail git -C fishhook symbolic-ref HEAD
-'
-
-test_expect_success '"add" with <branch> omitted' '
-	git worktree add wiffle/bat &&
-	test_cmp_rev HEAD bat
-'
-
-test_expect_success '"add" checks out existing branch of dwimd name' '
-	git branch dwim HEAD~1 &&
-	git worktree add dwim &&
-	test_cmp_rev HEAD~1 dwim &&
-	(
-		cd dwim &&
-		test_cmp_rev HEAD dwim
-	)
-'
-
-test_expect_success '"add <path>" dwim fails with checked out branch' '
-	git checkout -b test-branch &&
-	test_must_fail git worktree add test-branch &&
-	test_path_is_missing test-branch
-'
-
-test_expect_success '"add --force" with existing dwimd name doesnt die' '
-	git checkout test-branch &&
-	git worktree add --force test-branch
-'
-
-test_expect_success '"add" no auto-vivify with --detach and <branch> omitted' '
-	git worktree add --detach mish/mash &&
-	test_must_fail git rev-parse mash -- &&
-	test_must_fail git -C mish/mash symbolic-ref HEAD
-'
 
 # Helper function to test mutually exclusive options.
 #
 # Note: Quoted arguments containing spaces are not supported.
 test_wt_add_excl () {
-	local opts="$*" &&
+	local opts="$*"
+
 	test_expect_success "'worktree add' with '$opts' has mutually exclusive options" '
 		test_must_fail git worktree add $opts 2>actual &&
 		grep -E "fatal:( options)? .* cannot be used together" actual
 	'
 }
 
-test_wt_add_excl -b poodle -B poodle bamboo main
-test_wt_add_excl -b poodle --detach bamboo main
-test_wt_add_excl -B poodle --detach bamboo main
-test_wt_add_excl --orphan --detach bamboo
-test_wt_add_excl --orphan --no-checkout bamboo
-test_wt_add_excl --orphan bamboo main
-test_wt_add_excl --orphan -b bamboo wtdir/ main
-
-test_expect_success '"add -B" fails if the branch is checked out' '
-	git rev-parse newmain >before &&
-	test_must_fail git worktree add -B newmain bamboo main &&
-	git rev-parse newmain >after &&
-	test_cmp before after
-'
-
-test_expect_success 'add -B' '
-	git worktree add -B poodle bamboo2 main^ &&
-	git -C bamboo2 symbolic-ref HEAD >actual &&
-	echo refs/heads/poodle >expected &&
-	test_cmp expected actual &&
-	test_cmp_rev main^ poodle
-'
-
-test_expect_success 'add --quiet' '
-	test_when_finished "git worktree remove -f -f another-worktree" &&
-	git worktree add --quiet another-worktree main 2>actual &&
-	test_must_be_empty actual
-'
-
-test_expect_success 'add --quiet -b' '
-	test_when_finished "git branch -D quietnewbranch" &&
-	test_when_finished "git worktree remove -f -f another-worktree" &&
-	git worktree add --quiet -b quietnewbranch another-worktree 2>actual &&
-	test_must_be_empty actual
-'
-
-test_expect_success '"add --orphan"' '
-	test_when_finished "git worktree remove -f -f orphandir" &&
-	git worktree add --orphan -b neworphan orphandir &&
-	echo refs/heads/neworphan >expected &&
-	git -C orphandir symbolic-ref HEAD >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success '"add --orphan (no -b)"' '
-	test_when_finished "git worktree remove -f -f neworphan" &&
-	git worktree add --orphan neworphan &&
-	echo refs/heads/neworphan >expected &&
-	git -C neworphan symbolic-ref HEAD >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success '"add --orphan --quiet"' '
-	test_when_finished "git worktree remove -f -f orphandir" &&
-	git worktree add --quiet --orphan -b neworphan orphandir 2>log.actual &&
-	test_must_be_empty log.actual &&
-	echo refs/heads/neworphan >expected &&
-	git -C orphandir symbolic-ref HEAD >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success '"add --orphan" fails if the branch already exists' '
-	test_when_finished "git branch -D existingbranch" &&
-	git worktree add -b existingbranch orphandir main &&
-	git worktree remove orphandir &&
-	test_must_fail git worktree add --orphan -b existingbranch orphandir
-'
-
-test_expect_success '"add --orphan" with empty repository' '
-	test_when_finished "rm -rf empty_repo" &&
-	echo refs/heads/newbranch >expected &&
-	GIT_DIR="empty_repo" git init --bare &&
-	git -C empty_repo worktree add --orphan -b newbranch worktreedir &&
-	git -C empty_repo/worktreedir symbolic-ref HEAD >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success '"add" worktree with orphan branch and lock' '
-	git worktree add --lock --orphan -b orphanbr orphan-with-lock &&
-	test_when_finished "git worktree unlock orphan-with-lock || :" &&
-	test -f .git/worktrees/orphan-with-lock/locked
-'
-
-test_expect_success '"add" worktree with orphan branch, lock, and reason' '
-	lock_reason="why not" &&
-	git worktree add --detach --lock --reason "$lock_reason" orphan-with-lock-reason main &&
-	test_when_finished "git worktree unlock orphan-with-lock-reason || :" &&
-	test -f .git/worktrees/orphan-with-lock-reason/locked &&
-	echo "$lock_reason" >expect &&
-	test_cmp expect .git/worktrees/orphan-with-lock-reason/locked
-'
-
 # Note: Quoted arguments containing spaces are not supported.
 test_wt_add_orphan_hint () {
 	local context="$1" &&
@@ -449,100 +49,6 @@ test_wt_add_orphan_hint () {
 	'
 }
 
-test_wt_add_orphan_hint 'no opts' 0
-test_wt_add_orphan_hint '-b' 1 -b foobar_branch
-test_wt_add_orphan_hint '-B' 1 -B foobar_branch
-
-test_expect_success "'worktree add' doesn't show orphan hint in bad/orphan HEAD w/ --quiet" '
-	test_when_finished "rm -rf repo" &&
-	git init repo &&
-	(cd repo && test_commit commit) &&
-	test_must_fail git -C repo worktree add --quiet foobar_branch foobar/ 2>actual &&
-	! grep "error: unknown switch" actual &&
-	! grep "hint: If you meant to create a worktree containing a new unborn branch" actual
-'
-
-test_expect_success 'local clone from linked checkout' '
-	git clone --local here here-clone &&
-	( cd here-clone && git fsck )
-'
-
-test_expect_success 'local clone --shared from linked checkout' '
-	git -C bare worktree add --detach ../baretree &&
-	git clone --local --shared baretree bare-clone &&
-	grep /bare/ bare-clone/.git/objects/info/alternates
-'
-
-test_expect_success '"add" worktree with --no-checkout' '
-	git worktree add --no-checkout -b swamp swamp &&
-	! test -e swamp/init.t &&
-	git -C swamp reset --hard &&
-	test_cmp init.t swamp/init.t
-'
-
-test_expect_success '"add" worktree with --checkout' '
-	git worktree add --checkout -b swmap2 swamp2 &&
-	test_cmp init.t swamp2/init.t
-'
-
-test_expect_success 'put a worktree under rebase' '
-	git worktree add under-rebase &&
-	(
-		cd under-rebase &&
-		set_fake_editor &&
-		FAKE_LINES="edit 1" git rebase -i HEAD^ &&
-		git worktree list >actual &&
-		grep "under-rebase.*detached HEAD" actual
-	)
-'
-
-test_expect_success 'add a worktree, checking out a rebased branch' '
-	test_must_fail git worktree add new-rebase under-rebase &&
-	! test -d new-rebase
-'
-
-test_expect_success 'checking out a rebased branch from another worktree' '
-	git worktree add new-place &&
-	test_must_fail git -C new-place checkout under-rebase
-'
-
-test_expect_success 'not allow to delete a branch under rebase' '
-	(
-		cd under-rebase &&
-		test_must_fail git branch -D under-rebase
-	)
-'
-
-test_expect_success 'rename a branch under rebase not allowed' '
-	test_must_fail git branch -M under-rebase rebase-with-new-name
-'
-
-test_expect_success 'check out from current worktree branch ok' '
-	(
-		cd under-rebase &&
-		git checkout under-rebase &&
-		git checkout - &&
-		git rebase --abort
-	)
-'
-
-test_expect_success 'checkout a branch under bisect' '
-	git worktree add under-bisect &&
-	(
-		cd under-bisect &&
-		git bisect start &&
-		git bisect bad &&
-		git bisect good HEAD~2 &&
-		git worktree list >actual &&
-		grep "under-bisect.*detached HEAD" actual &&
-		test_must_fail git worktree add new-bisect under-bisect &&
-		! test -d new-bisect
-	)
-'
-
-test_expect_success 'rename a branch under bisect not allowed' '
-	test_must_fail git branch -M under-bisect bisect-with-new-name
-'
 # Is branch "refs/heads/$1" set to pull from "$2/$3"?
 test_branch_upstream () {
 	printf "%s\n" "$2" "refs/heads/$3" >expect.upstream &&
@@ -553,12 +59,6 @@ test_branch_upstream () {
 	test_cmp expect.upstream actual.upstream
 }
 
-test_expect_success '--track sets up tracking' '
-	test_when_finished rm -rf track &&
-	git worktree add --track -b track track main &&
-	test_branch_upstream track . main
-'
-
 # setup remote repository $1 and repository $2 with $1 set up as
 # remote.  The remote has two branches, main and foo.
 setup_remote_repo () {
@@ -580,157 +80,6 @@ setup_remote_repo () {
 	)
 }
 
-test_expect_success '"add" <path> <remote/branch> w/ no HEAD' '
-	test_when_finished rm -rf repo_upstream repo_local foo &&
-	setup_remote_repo repo_upstream repo_local &&
-	git -C repo_local config --bool core.bare true &&
-	git -C repo_local branch -D main &&
-	git -C repo_local worktree add ./foo repo_upstream/foo
-'
-
-test_expect_success '--no-track avoids setting up tracking' '
-	test_when_finished rm -rf repo_upstream repo_local foo &&
-	setup_remote_repo repo_upstream repo_local &&
-	(
-		cd repo_local &&
-		git worktree add --no-track -b foo ../foo repo_upstream/foo
-	) &&
-	(
-		cd foo &&
-		test_must_fail git config "branch.foo.remote" &&
-		test_must_fail git config "branch.foo.merge" &&
-		test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo
-	)
-'
-
-test_expect_success '"add" <path> <non-existent-branch> fails' '
-	test_must_fail git worktree add foo non-existent
-'
-
-test_expect_success '"add" <path> <branch> dwims' '
-	test_when_finished rm -rf repo_upstream repo_dwim foo &&
-	setup_remote_repo repo_upstream repo_dwim &&
-	git init repo_dwim &&
-	(
-		cd repo_dwim &&
-		git worktree add ../foo foo
-	) &&
-	(
-		cd foo &&
-		test_branch_upstream foo repo_upstream foo &&
-		test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo
-	)
-'
-
-test_expect_success '"add" <path> <branch> dwims with checkout.defaultRemote' '
-	test_when_finished rm -rf repo_upstream repo_dwim foo &&
-	setup_remote_repo repo_upstream repo_dwim &&
-	git init repo_dwim &&
-	(
-		cd repo_dwim &&
-		git remote add repo_upstream2 ../repo_upstream &&
-		git fetch repo_upstream2 &&
-		test_must_fail git worktree add ../foo foo &&
-		git -c checkout.defaultRemote=repo_upstream worktree add ../foo foo &&
-		git status -uno --porcelain >status.actual &&
-		test_must_be_empty status.actual
-	) &&
-	(
-		cd foo &&
-		test_branch_upstream foo repo_upstream foo &&
-		test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo
-	)
-'
-
-test_expect_success 'git worktree add does not match remote' '
-	test_when_finished rm -rf repo_a repo_b foo &&
-	setup_remote_repo repo_a repo_b &&
-	(
-		cd repo_b &&
-		git worktree add ../foo
-	) &&
-	(
-		cd foo &&
-		test_must_fail git config "branch.foo.remote" &&
-		test_must_fail git config "branch.foo.merge" &&
-		test_cmp_rev ! refs/remotes/repo_a/foo refs/heads/foo
-	)
-'
-
-test_expect_success 'git worktree add --guess-remote sets up tracking' '
-	test_when_finished rm -rf repo_a repo_b foo &&
-	setup_remote_repo repo_a repo_b &&
-	(
-		cd repo_b &&
-		git worktree add --guess-remote ../foo
-	) &&
-	(
-		cd foo &&
-		test_branch_upstream foo repo_a foo &&
-		test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
-	)
-'
-test_expect_success 'git worktree add --guess-remote sets up tracking (quiet)' '
-	test_when_finished rm -rf repo_a repo_b foo &&
-	setup_remote_repo repo_a repo_b &&
-	(
-		cd repo_b &&
-		git worktree add --quiet --guess-remote ../foo 2>actual &&
-		test_must_be_empty actual
-	) &&
-	(
-		cd foo &&
-		test_branch_upstream foo repo_a foo &&
-		test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
-	)
-'
-
-test_expect_success 'git worktree --no-guess-remote (quiet)' '
-	test_when_finished rm -rf repo_a repo_b foo &&
-	setup_remote_repo repo_a repo_b &&
-	(
-		cd repo_b &&
-		git worktree add --quiet --no-guess-remote ../foo
-	) &&
-	(
-		cd foo &&
-		test_must_fail git config "branch.foo.remote" &&
-		test_must_fail git config "branch.foo.merge" &&
-		test_cmp_rev ! refs/remotes/repo_a/foo refs/heads/foo
-	)
-'
-
-test_expect_success 'git worktree add with worktree.guessRemote sets up tracking' '
-	test_when_finished rm -rf repo_a repo_b foo &&
-	setup_remote_repo repo_a repo_b &&
-	(
-		cd repo_b &&
-		git config worktree.guessRemote true &&
-		git worktree add ../foo
-	) &&
-	(
-		cd foo &&
-		test_branch_upstream foo repo_a foo &&
-		test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
-	)
-'
-
-test_expect_success 'git worktree --no-guess-remote option overrides config' '
-	test_when_finished rm -rf repo_a repo_b foo &&
-	setup_remote_repo repo_a repo_b &&
-	(
-		cd repo_b &&
-		git config worktree.guessRemote true &&
-		git worktree add --no-guess-remote ../foo
-	) &&
-	(
-		cd foo &&
-		test_must_fail git config "branch.foo.remote" &&
-		test_must_fail git config "branch.foo.merge" &&
-		test_cmp_rev ! refs/remotes/repo_a/foo refs/heads/foo
-	)
-'
-
 test_dwim_orphan () {
 	local info_text="No possible source branch, inferring '--orphan'" &&
 	local fetch_error_text="fatal: No local or remote refs exist despite at least one remote" &&
@@ -751,10 +100,11 @@ test_dwim_orphan () {
 	local use_detach=0 &&
 	local use_new_branch=0 &&
 
-	local outcome="$1" &&
+	local use_relative_paths="$1" &&
+	local outcome="$2" &&
 	local outcome_text &&
 	local success &&
-	shift &&
+	shift 2 &&
 	local args="" &&
 	local context="" &&
 	case "$outcome" in
@@ -791,25 +141,25 @@ test_dwim_orphan () {
 			use_cd=0 &&
 			git_ns="repo" &&
 			dashc_args="-C $git_ns" &&
-			context="$context, 'git -C repo'"
+			context="$context, 'git -C $git_ns'"
 			;;
 		"-C_wt")
 			use_cd=0 &&
 			git_ns="wt" &&
 			dashc_args="-C $git_ns" &&
-			context="$context, 'git -C wt'"
+			context="$context, 'git -C $git_ns'"
 			;;
 		"cd_repo")
 			use_cd=1 &&
 			git_ns="repo" &&
 			dashc_args="" &&
-			context="$context, 'cd repo && git'"
+			context="$context, 'cd $git_ns && git'"
 			;;
 		"cd_wt")
 			use_cd=1 &&
 			git_ns="wt" &&
 			dashc_args="" &&
-			context="$context, 'cd wt && git'"
+			context="$context, 'cd $git_ns && git'"
 			;;
 
 		# Bypass the "pull first" warning
@@ -948,6 +298,7 @@ test_dwim_orphan () {
 		then
 			test_when_finished "git -C repo worktree remove -f ../wt" &&
 			git -C repo worktree add --orphan -b main ../wt &&
+			check_worktree_paths "$use_relative_paths" wt &&
 			(cd wt && test_commit commit) &&
 			if [ $bad_head -eq 1 ]
 			then
@@ -956,7 +307,8 @@ test_dwim_orphan () {
 		elif [ $local_ref -eq 0 ] && [ "$git_ns" = "wt" ]
 		then
 			test_when_finished "git -C repo worktree remove -f ../wt" &&
-			git -C repo worktree add --orphan -b orphanbranch ../wt
+			git -C repo worktree add --orphan -b orphanbranch ../wt &&
+			check_worktree_paths "$use_relative_paths" wt
 		fi &&
 
 		if [ $remote -eq 1 ]
@@ -977,13 +329,18 @@ test_dwim_orphan () {
 			test_when_finished git -C repo worktree remove ../foo
 		fi &&
 		(
+			local wt_prefix &&
 			if [ $use_cd -eq 1 ]
 			then
-				cd $git_ns
+				cd $git_ns &&
+				wt_prefix=".."
+			else
+				wt_prefix="."
 			fi &&
 			if [ "$outcome" = "infer" ]
 			then
 				git $dashc_args worktree add $args 2>actual &&
+				check_worktree_paths "$use_relative_paths" "$wt_prefix/foo" &&
 				if [ $use_quiet -eq 1 ]
 				then
 					test_must_be_empty actual
@@ -993,6 +350,7 @@ test_dwim_orphan () {
 			elif [ "$outcome" = "no_infer" ]
 			then
 				git $dashc_args worktree add $args 2>actual &&
+				check_worktree_paths "$use_relative_paths" "$wt_prefix/foo" &&
 				if [ $use_quiet -eq 1 ]
 				then
 					test_must_be_empty actual
@@ -1042,169 +400,906 @@ test_dwim_orphan () {
 	'
 }
 
-for quiet_mode in "no_quiet" "quiet"
-do
-	for changedir_type in "cd_repo" "cd_wt" "-C_repo" "-C_wt"
-	do
-		dwim_test_args="$quiet_mode $changedir_type"
-		test_dwim_orphan 'infer' $dwim_test_args no_-b
-		test_dwim_orphan 'no_infer' $dwim_test_args no_-b local_ref good_head
-		test_dwim_orphan 'infer' $dwim_test_args no_-b no_local_ref no_remote no_remote_ref no_guess_remote
-		test_dwim_orphan 'infer' $dwim_test_args no_-b no_local_ref remote no_remote_ref no_guess_remote
-		test_dwim_orphan 'fetch_error' $dwim_test_args no_-b no_local_ref remote no_remote_ref guess_remote
-		test_dwim_orphan 'infer' $dwim_test_args no_-b no_local_ref remote no_remote_ref guess_remote force
-		test_dwim_orphan 'no_infer' $dwim_test_args no_-b no_local_ref remote remote_ref guess_remote
-
-		test_dwim_orphan 'infer' $dwim_test_args -b
-		test_dwim_orphan 'no_infer' $dwim_test_args -b local_ref good_head
-		test_dwim_orphan 'infer' $dwim_test_args -b no_local_ref no_remote no_remote_ref no_guess_remote
-		test_dwim_orphan 'infer' $dwim_test_args -b no_local_ref remote no_remote_ref no_guess_remote
-		test_dwim_orphan 'infer' $dwim_test_args -b no_local_ref remote no_remote_ref guess_remote
-		test_dwim_orphan 'infer' $dwim_test_args -b no_local_ref remote remote_ref guess_remote
-
-		test_dwim_orphan 'warn_bad_head' $dwim_test_args no_-b local_ref bad_head
-		test_dwim_orphan 'warn_bad_head' $dwim_test_args -b local_ref bad_head
-		test_dwim_orphan 'warn_bad_head' $dwim_test_args detach local_ref bad_head
-	done
-
-	test_dwim_orphan 'fatal_orphan_bad_combo' $quiet_mode no_-b no_checkout
-	test_dwim_orphan 'fatal_orphan_bad_combo' $quiet_mode no_-b track
-	test_dwim_orphan 'fatal_orphan_bad_combo' $quiet_mode -b no_checkout
-	test_dwim_orphan 'fatal_orphan_bad_combo' $quiet_mode -b track
-done
-
 post_checkout_hook () {
 	test_when_finished "rm -rf .git/hooks" &&
 	mkdir .git/hooks &&
-	test_hook -C "$1" post-checkout <<-\EOF
+	test_hook -C "$1" post-checkout <<-'EOF'
 	{
 		echo $*
 		git rev-parse --git-dir --show-toplevel
 	} >hook.actual
-	EOF
+EOF
 }
 
-test_expect_success '"add" invokes post-checkout hook (branch)' '
-	post_checkout_hook &&
-	{
-		echo $ZERO_OID $(git rev-parse HEAD) 1 &&
-		echo $(pwd)/.git/worktrees/gumby &&
-		echo $(pwd)/gumby
-	} >hook.expect &&
-	git worktree add gumby &&
-	test_cmp hook.expect gumby/hook.actual
-'
-
-test_expect_success '"add" invokes post-checkout hook (detached)' '
-	post_checkout_hook &&
-	{
-		echo $ZERO_OID $(git rev-parse HEAD) 1 &&
-		echo $(pwd)/.git/worktrees/grumpy &&
-		echo $(pwd)/grumpy
-	} >hook.expect &&
-	git worktree add --detach grumpy &&
-	test_cmp hook.expect grumpy/hook.actual
-'
-
-test_expect_success '"add --no-checkout" suppresses post-checkout hook' '
-	post_checkout_hook &&
-	rm -f hook.actual &&
-	git worktree add --no-checkout gloopy &&
-	test_path_is_missing gloopy/hook.actual
-'
-
-test_expect_success '"add" in other worktree invokes post-checkout hook' '
-	post_checkout_hook &&
-	{
-		echo $ZERO_OID $(git rev-parse HEAD) 1 &&
-		echo $(pwd)/.git/worktrees/guppy &&
-		echo $(pwd)/guppy
-	} >hook.expect &&
-	git -C gloopy worktree add --detach ../guppy &&
-	test_cmp hook.expect guppy/hook.actual
-'
-
-test_expect_success '"add" in bare repo invokes post-checkout hook' '
-	rm -rf bare &&
-	git clone --bare . bare &&
-	{
-		echo $ZERO_OID $(git --git-dir=bare rev-parse HEAD) 1 &&
-		echo $(pwd)/bare/worktrees/goozy &&
-		echo $(pwd)/goozy
-	} >hook.expect &&
-	post_checkout_hook bare &&
-	git -C bare worktree add --detach ../goozy &&
-	test_cmp hook.expect goozy/hook.actual
-'
-
-test_expect_success '"add" an existing but missing worktree' '
-	git worktree add --detach pneu &&
-	test_must_fail git worktree add --detach pneu &&
-	rm -fr pneu &&
-	test_must_fail git worktree add --detach pneu &&
-	git worktree add --force --detach pneu
-'
-
-test_expect_success '"add" an existing locked but missing worktree' '
-	git worktree add --detach gnoo &&
-	git worktree lock gnoo &&
-	test_when_finished "git worktree unlock gnoo || :" &&
-	rm -fr gnoo &&
-	test_must_fail git worktree add --detach gnoo &&
-	test_must_fail git worktree add --force --detach gnoo &&
-	git worktree add --force --force --detach gnoo
-'
-
-test_expect_success '"add" not tripped up by magic worktree matching"' '
-	# if worktree "sub1/bar" exists, "git worktree add bar" in distinct
-	# directory `sub2` should not mistakenly complain that `bar` is an
-	# already-registered worktree
-	mkdir sub1 sub2 &&
-	git -C sub1 --git-dir=../.git worktree add --detach bozo &&
-	git -C sub2 --git-dir=../.git worktree add --detach bozo
-'
-
-test_expect_success FUNNYNAMES 'sanitize generated worktree name' '
-	git worktree add --detach ".  weird*..?.lock.lock" &&
-	test -d .git/worktrees/---weird-.-
-'
-
-test_expect_success '"add" should not fail because of another bad worktree' '
-	git init add-fail &&
-	(
-		cd add-fail &&
-		test_commit first &&
-		mkdir sub &&
-		git worktree add sub/to-be-deleted &&
-		rm -rf sub &&
-		git worktree add second
-	)
-'
-
-test_expect_success '"add" with uninitialized submodule, with submodule.recurse unset' '
-	test_config_global protocol.file.allow always &&
-	test_create_repo submodule &&
-	test_commit -C submodule first &&
-	test_create_repo project &&
-	git -C project submodule add ../submodule &&
-	git -C project add submodule &&
-	test_tick &&
-	git -C project commit -m add_sub &&
-	git clone project project-clone &&
-	git -C project-clone worktree add ../project-2
-'
-test_expect_success '"add" with uninitialized submodule, with submodule.recurse set' '
-	git -C project-clone -c submodule.recurse worktree add ../project-3
-'
-
-test_expect_success '"add" with initialized submodule, with submodule.recurse unset' '
-	test_config_global protocol.file.allow always &&
-	git -C project-clone submodule update --init &&
-	git -C project-clone worktree add ../project-4
-'
-
-test_expect_success '"add" with initialized submodule, with submodule.recurse set' '
-	git -C project-clone -c submodule.recurse worktree add ../project-5
-'
+run_worktree_add_tests() {
+	local use_relative_paths="$1"
+
+	# Set the Git config variable based on the parameter
+	test_config_global worktree.userelativepaths "$use_relative_paths"
+
+	test_expect_success 'setup' '
+		test_commit init
+	'
+
+	test_expect_success '"add" an existing worktree' '
+		mkdir -p existing/subtree &&
+		test_must_fail git worktree add --detach existing main
+	'
+
+	test_expect_success '"add" an existing empty worktree' '
+		mkdir existing_empty &&
+		git worktree add --detach existing_empty main &&
+		check_worktree_paths "$use_relative_paths" existing_empty
+	'
+
+	test_expect_success '"add" using shorthand - fails when no previous branch' '
+		test_must_fail git worktree add existing_short -
+	'
+
+	test_expect_success '"add" using - shorthand' '
+		git checkout -b newbranch &&
+		echo hello >myworld &&
+		git add myworld &&
+		git commit -m myworld &&
+		git checkout main &&
+		git worktree add short-hand - &&
+		echo refs/heads/newbranch >expect &&
+		git -C short-hand rev-parse --symbolic-full-name HEAD >actual &&
+		test_cmp expect actual &&
+		check_worktree_paths "$use_relative_paths" short-hand
+	'
+
+	test_expect_success '"add" refuses to checkout locked branch' '
+		test_must_fail git worktree add zere main &&
+		! test -d zere &&
+		! test -d .git/worktrees/zere
+	'
+
+	test_expect_success 'checking out paths not complaining about linked checkouts' '
+		(
+		cd existing_empty &&
+		echo dirty >>init.t &&
+		git checkout main -- init.t
+		)
+	'
+
+	test_expect_success '"add" worktree' '
+		git rev-parse HEAD >expect &&
+		git worktree add --detach here main &&
+		(
+			cd here &&
+			test_cmp ../init.t init.t &&
+			test_must_fail git symbolic-ref HEAD &&
+			git rev-parse HEAD >actual &&
+			test_cmp ../expect actual &&
+			git fsck
+		) &&
+		check_worktree_paths "$use_relative_paths" here
+	'
+
+	test_expect_success '"add" worktree with lock' '
+		git worktree add --detach --lock here-with-lock main &&
+		test_when_finished "git worktree unlock here-with-lock || :" &&
+		test -f .git/worktrees/here-with-lock/locked &&
+		check_worktree_paths "$use_relative_paths" here-with-lock
+	'
+
+	test_expect_success '"add" worktree with lock and reason' '
+		lock_reason="why not" &&
+		git worktree add --detach --lock --reason "$lock_reason" here-with-lock-reason main &&
+		test_when_finished "git worktree unlock here-with-lock-reason || :" &&
+		test -f .git/worktrees/here-with-lock-reason/locked &&
+		echo "$lock_reason" >expect &&
+		test_cmp expect .git/worktrees/here-with-lock-reason/locked &&
+		check_worktree_paths "$use_relative_paths" here-with-lock-reason
+	'
+
+	test_expect_success '"add" worktree with reason but no lock' '
+		test_must_fail git worktree add --detach --reason "why not" here-with-reason-only main &&
+		test_path_is_missing .git/worktrees/here-with-reason-only/locked
+	'
+
+	test_expect_success '"add" worktree from a subdir' '
+		(
+			mkdir sub &&
+			cd sub &&
+			git worktree add --detach here main &&
+			cd here &&
+			test_cmp ../../init.t init.t &&
+			check_worktree_paths "$use_relative_paths" .
+		)
+	'
+
+	test_expect_success '"add" from a linked checkout' '
+		(
+			cd here &&
+			git worktree add --detach nested-here main &&
+			cd nested-here &&
+			git fsck &&
+			check_worktree_paths "$use_relative_paths" .
+		)
+	'
+
+	test_expect_success '"add" worktree creating new branch' '
+		git worktree add -b newmain there main &&
+		(
+			cd there &&
+			test_cmp ../init.t init.t &&
+			git symbolic-ref HEAD >actual &&
+			echo refs/heads/newmain >expect &&
+			test_cmp expect actual &&
+			git fsck &&
+			check_worktree_paths "$use_relative_paths" .
+		)
+	'
+
+	test_expect_success 'die the same branch is already checked out' '
+		(
+			cd here &&
+			test_must_fail git checkout newmain 2>actual &&
+			grep "already used by worktree at" actual
+		)
+	'
+
+	test_expect_success 'refuse to reset a branch in use elsewhere' '
+		(
+			cd here &&
+
+			# we know we are on detached HEAD but just in case ...
+			git checkout --detach HEAD &&
+			git rev-parse --verify HEAD >old.head &&
+
+			git rev-parse --verify refs/heads/newmain >old.branch &&
+			test_must_fail git checkout -B newmain 2>error &&
+			git rev-parse --verify refs/heads/newmain >new.branch &&
+			git rev-parse --verify HEAD >new.head &&
+
+			grep "already used by worktree at" error &&
+			test_cmp old.branch new.branch &&
+			test_cmp old.head new.head &&
+
+			# and we must be still on the same detached HEAD state
+			test_must_fail git symbolic-ref HEAD
+		)
+	'
+
+	test_expect_success SYMLINKS 'die the same branch is already checked out (symlink)' '
+		head=$(git -C there rev-parse --git-path HEAD) &&
+		ref=$(git -C there symbolic-ref HEAD) &&
+		rm "$head" &&
+		ln -s "$ref" "$head" &&
+		test_must_fail git -C here checkout newmain
+	'
+
+	test_expect_success 'not die the same branch is already checked out' '
+		(
+			cd here &&
+			git worktree add --force anothernewmain newmain &&
+			check_worktree_paths "$use_relative_paths" anothernewmain
+		)
+	'
+
+	test_expect_success 'not die on re-checking out current branch' '
+		(
+			cd there &&
+			git checkout newmain
+		)
+	'
+
+	test_expect_success '"add" from a bare repo' '
+		(
+			git clone --bare . bare &&
+			cd bare &&
+			git worktree add -b bare-main ../there2 main &&
+			check_worktree_paths "$use_relative_paths" ../there2
+		)
+	'
+
+	test_expect_success 'checkout from a bare repo without "add"' '
+		(
+			cd bare &&
+			test_must_fail git checkout main
+		)
+	'
+
+	test_expect_success '"add" default branch of a bare repo' '
+		(
+			git clone --bare . bare2 &&
+			cd bare2 &&
+			git worktree add ../there3 main &&
+			check_worktree_paths "$use_relative_paths" ../there3 &&
+			cd ../there3 &&
+			# Simple check that a Git command does not
+			# immediately fail with the current setup
+			git status
+		) &&
+		cat >expect <<-EOF &&
+		init.t
+		EOF
+		ls there3 >actual &&
+		test_cmp expect actual
+	'
+
+	test_expect_success '"add" to bare repo with worktree config' '
+		(
+			git clone --bare . bare3 &&
+			cd bare3 &&
+			git config extensions.worktreeconfig true &&
+
+			# Add config values that are erroneous to have in
+			# a config.worktree file outside of the main
+			# working tree, to check that Git filters them out
+			# when copying config during "git worktree add".
+			git config --worktree core.bare true &&
+			git config --worktree core.worktree "$(pwd)" &&
+
+			# We want to check that bogus.key is copied
+			git config --worktree bogus.key value &&
+			git config --unset core.bare &&
+			git worktree add ../there4 main &&
+			check_worktree_paths "$use_relative_paths" ../there4 &&
+			cd ../there4 &&
+
+			# Simple check that a Git command does not
+			# immediately fail with the current setup
+			git status &&
+			git worktree add --detach ../there5 &&
+			check_worktree_paths "$use_relative_paths" ../there5 &&
+			cd ../there5 &&
+			git status
+		) &&
+
+		# the worktree has the arbitrary value copied.
+		test_cmp_config -C there4 value bogus.key &&
+		test_cmp_config -C there5 value bogus.key &&
+
+		# however, core.bare and core.worktree were removed.
+		test_must_fail git -C there4 config core.bare &&
+		test_must_fail git -C there4 config core.worktree &&
+
+		cat >expect <<-EOF &&
+		init.t
+		EOF
+
+		ls there4 >actual &&
+		test_cmp expect actual &&
+		ls there5 >actual &&
+		test_cmp expect actual
+	'
+
+	test_expect_success 'checkout with grafts' '
+		test_when_finished rm .git/info/grafts &&
+		test_commit abc &&
+		SHA1=$(git rev-parse HEAD) &&
+		test_commit def &&
+		test_commit xyz &&
+		mkdir .git/info &&
+		echo "$(git rev-parse HEAD) $SHA1" >.git/info/grafts &&
+		cat >expected <<-\EOF &&
+		xyz
+		abc
+		EOF
+		git log --format=%s -2 >actual &&
+		test_cmp expected actual &&
+		git worktree add --detach grafted main &&
+		check_worktree_paths "$use_relative_paths" grafted &&
+		git --git-dir=grafted/.git log --format=%s -2 >actual &&
+		test_cmp expected actual
+	'
+
+	test_expect_success '"add" from relative HEAD' '
+		test_commit a &&
+		test_commit b &&
+		test_commit c &&
+		git rev-parse HEAD~1 >expected &&
+		git worktree add relhead HEAD~1 &&
+		check_worktree_paths "$use_relative_paths" relhead &&
+		git -C relhead rev-parse HEAD >actual &&
+		test_cmp expected actual
+	'
+
+	test_expect_success '"add -b" with <branch> omitted' '
+		git worktree add -b burble flornk &&
+		check_worktree_paths "$use_relative_paths" flornk &&
+		test_cmp_rev HEAD burble
+	'
+
+	test_expect_success '"add --detach" with <branch> omitted' '
+		git worktree add --detach fishhook &&
+		check_worktree_paths "$use_relative_paths" fishhook &&
+		git rev-parse HEAD >expected &&
+		git -C fishhook rev-parse HEAD >actual &&
+		test_cmp expected actual &&
+		test_must_fail git -C fishhook symbolic-ref HEAD
+	'
+
+	test_expect_success '"add" with <branch> omitted' '
+		git worktree add wiffle/bat &&
+		check_worktree_paths "$use_relative_paths" wiffle/bat &&
+		test_cmp_rev HEAD bat
+	'
+
+	test_expect_success '"add" checks out existing branch of dwimd name' '
+		git branch dwim HEAD~1 &&
+		git worktree add dwim &&
+		check_worktree_paths "$use_relative_paths" dwim &&
+		test_cmp_rev HEAD~1 dwim &&
+		(
+			cd dwim &&
+			test_cmp_rev HEAD dwim
+		)
+	'
+
+	test_expect_success '"add <path>" dwim fails with checked out branch' '
+		git checkout -b test-branch &&
+		test_must_fail git worktree add test-branch &&
+		test_path_is_missing test-branch
+	'
+
+	test_expect_success '"add --force" with existing dwimd name doesnt die' '
+		git checkout test-branch &&
+		git worktree add --force test-branch
+	'
+
+	test_expect_success '"add" no auto-vivify with --detach and <branch> omitted' '
+		git worktree add --detach mish/mash &&
+		check_worktree_paths "$use_relative_paths" mish/mash &&
+		test_must_fail git rev-parse mash -- &&
+		test_must_fail git -C mish/mash symbolic-ref HEAD
+	'
+
+	test_expect_success '"add -B" fails if the branch is checked out' '
+		git rev-parse newmain >before &&
+		test_must_fail git worktree add -B newmain bamboo main &&
+		git rev-parse newmain >after &&
+		test_cmp before after
+	'
+
+	test_expect_success 'add -B' '
+		git worktree add -B poodle bamboo2 main^ &&
+		check_worktree_paths "$use_relative_paths" bamboo2 &&
+		git -C bamboo2 symbolic-ref HEAD >actual &&
+		echo refs/heads/poodle >expected &&
+		test_cmp expected actual &&
+		test_cmp_rev main^ poodle
+	'
+
+	test_expect_success 'add --quiet' '
+		test_when_finished "git worktree remove -f -f another-worktree" &&
+		git worktree add --quiet another-worktree main 2>actual &&
+		check_worktree_paths "$use_relative_paths" another-worktree &&
+		test_must_be_empty actual
+	'
+
+	test_expect_success 'add --quiet -b' '
+		test_when_finished "git branch -D quietnewbranch" &&
+		test_when_finished "git worktree remove -f -f another-worktree" &&
+		git worktree add --quiet -b quietnewbranch another-worktree 2>actual &&
+		check_worktree_paths "$use_relative_paths" another-worktree &&
+		test_must_be_empty actual
+	'
+
+	test_expect_success '"add --orphan"' '
+		test_when_finished "git worktree remove -f -f orphandir" &&
+		git worktree add --orphan -b neworphan orphandir &&
+		check_worktree_paths "$use_relative_paths" orphandir &&
+		echo refs/heads/neworphan >expected &&
+		git -C orphandir symbolic-ref HEAD >actual &&
+		test_cmp expected actual
+	'
+
+	test_expect_success '"add --orphan (no -b)"' '
+		test_when_finished "git worktree remove -f -f neworphan" &&
+		git worktree add --orphan neworphan &&
+		check_worktree_paths "$use_relative_paths" neworphan &&
+		echo refs/heads/neworphan >expected &&
+		git -C neworphan symbolic-ref HEAD >actual &&
+		test_cmp expected actual
+	'
+
+	test_expect_success '"add --orphan --quiet"' '
+		test_when_finished "git worktree remove -f -f orphandir" &&
+		git worktree add --quiet --orphan -b neworphan orphandir 2>log.actual &&
+		check_worktree_paths "$use_relative_paths" orphandir &&
+		test_must_be_empty log.actual &&
+		echo refs/heads/neworphan >expected &&
+		git -C orphandir symbolic-ref HEAD >actual &&
+		test_cmp expected actual
+	'
+
+	test_expect_success '"add --orphan" fails if the branch already exists' '
+		test_when_finished "git branch -D existingbranch" &&
+		git worktree add -b existingbranch orphandir main &&
+		check_worktree_paths "$use_relative_paths" orphandir &&
+		git worktree remove orphandir &&
+		test_must_fail git worktree add --orphan -b existingbranch orphandir
+	'
+
+	test_expect_success '"add --orphan" with empty repository' '
+		test_when_finished "rm -rf empty_repo" &&
+		echo refs/heads/newbranch >expected &&
+		GIT_DIR="empty_repo" git init --bare &&
+		git -C empty_repo worktree add --orphan -b newbranch worktreedir &&
+		check_worktree_paths "$use_relative_paths" empty_repo/worktreedir &&
+		git -C empty_repo/worktreedir symbolic-ref HEAD >actual &&
+		test_cmp expected actual
+	'
+
+	test_expect_success '"add" worktree with orphan branch and lock' '
+		git worktree add --lock --orphan -b orphanbr orphan-with-lock &&
+		check_worktree_paths "$use_relative_paths" orphan-with-lock &&
+		test_when_finished "git worktree unlock orphan-with-lock || :" &&
+		test -f .git/worktrees/orphan-with-lock/locked
+	'
+
+	test_expect_success '"add" worktree with orphan branch, lock, and reason' '
+		lock_reason="why not" &&
+		git worktree add --detach --lock --reason "$lock_reason" orphan-with-lock-reason main &&
+		check_worktree_paths "$use_relative_paths" orphan-with-lock-reason &&
+		test_when_finished "git worktree unlock orphan-with-lock-reason || :" &&
+		test -f .git/worktrees/orphan-with-lock-reason/locked &&
+		echo "$lock_reason" >expect &&
+		test_cmp expect .git/worktrees/orphan-with-lock-reason/locked
+	'
+
+	test_expect_success "'worktree add' doesn't show orphan hint in bad/orphan HEAD w/ --quiet" '
+		test_when_finished "rm -rf repo" &&
+		git init repo &&
+		(cd repo && test_commit commit) &&
+		test_must_fail git -C repo worktree add --quiet foobar_branch foobar/ 2>actual &&
+		! grep "error: unknown switch" actual &&
+		! grep "hint: If you meant to create a worktree containing a new unborn branch" actual
+	'
+
+	test_expect_success 'local clone from linked checkout' '
+		git clone --local here here-clone &&
+		( cd here-clone && git fsck )
+	'
+
+	test_expect_success 'local clone --shared from linked checkout' '
+		git -C bare worktree add --detach ../baretree &&
+		check_worktree_paths "$use_relative_paths" baretree &&
+		git clone --local --shared baretree bare-clone &&
+		grep /bare/ bare-clone/.git/objects/info/alternates
+	'
+
+	test_expect_success '"add" worktree with --no-checkout' '
+		git worktree add --no-checkout -b swamp swamp &&
+		check_worktree_paths "$use_relative_paths" swamp &&
+		! test -e swamp/init.t &&
+		git -C swamp reset --hard &&
+		test_cmp init.t swamp/init.t
+	'
+
+	test_expect_success '"add" worktree with --checkout' '
+		git worktree add --checkout -b swmap2 swamp2 &&
+		check_worktree_paths "$use_relative_paths" swamp2 &&
+		test_cmp init.t swamp2/init.t
+	'
+
+	test_expect_success 'put a worktree under rebase' '
+		git worktree add under-rebase &&
+		check_worktree_paths "$use_relative_paths" under-rebase &&
+		(
+			cd under-rebase &&
+			set_fake_editor &&
+			FAKE_LINES="edit 1" git rebase -i HEAD^ &&
+			git worktree list >actual &&
+			grep "under-rebase.*detached HEAD" actual
+		)
+	'
+
+	test_expect_success 'add a worktree, checking out a rebased branch' '
+		test_must_fail git worktree add new-rebase under-rebase &&
+		! test -d new-rebase
+	'
+
+	test_expect_success 'checking out a rebased branch from another worktree' '
+		git worktree add new-place &&
+		check_worktree_paths "$use_relative_paths" new-place &&
+		test_must_fail git -C new-place checkout under-rebase
+	'
+
+	test_expect_success 'not allow to delete a branch under rebase' '
+		(
+			cd under-rebase &&
+			test_must_fail git branch -D under-rebase
+		)
+	'
+
+	test_expect_success 'rename a branch under rebase not allowed' '
+		test_must_fail git branch -M under-rebase rebase-with-new-name
+	'
+
+	test_expect_success 'check out from current worktree branch ok' '
+		(
+			cd under-rebase &&
+			git checkout under-rebase &&
+			git checkout - &&
+			git rebase --abort
+		)
+	'
+
+	test_expect_success 'checkout a branch under bisect' '
+		git worktree add under-bisect &&
+		check_worktree_paths "$use_relative_paths" under-bisect &&
+		(
+			cd under-bisect &&
+			git bisect start &&
+			git bisect bad &&
+			git bisect good HEAD~2 &&
+			git worktree list >actual &&
+			grep "under-bisect.*detached HEAD" actual &&
+			test_must_fail git worktree add new-bisect under-bisect &&
+			! test -d new-bisect
+		)
+	'
+
+	test_expect_success 'rename a branch under bisect not allowed' '
+		test_must_fail git branch -M under-bisect bisect-with-new-name
+	'
+
+	test_expect_success '--track sets up tracking' '
+		test_when_finished rm -rf track &&
+		git worktree add --track -b track track main &&
+		check_worktree_paths "$use_relative_paths" track &&
+		test_branch_upstream track . main
+	'
+
+	test_expect_success '"add" <path> <remote/branch> w/ no HEAD' '
+		test_when_finished rm -rf repo_upstream repo_local foo &&
+		setup_remote_repo repo_upstream repo_local &&
+		git -C repo_local config --bool core.bare true &&
+		git -C repo_local branch -D main &&
+		git -C repo_local worktree add ./foo repo_upstream/foo &&
+		check_worktree_paths "$use_relative_paths" repo_local/foo
+	'
+
+	test_expect_success '--no-track avoids setting up tracking' '
+		test_when_finished rm -rf repo_upstream repo_local foo &&
+		setup_remote_repo repo_upstream repo_local &&
+		(
+			cd repo_local &&
+			git worktree add --no-track -b foo ../foo repo_upstream/foo &&
+			check_worktree_paths "$use_relative_paths" ../foo
+		) &&
+		(
+			cd foo &&
+			test_must_fail git config "branch.foo.remote" &&
+			test_must_fail git config "branch.foo.merge" &&
+			test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo
+		)
+	'
+
+	test_expect_success '"add" <path> <non-existent-branch> fails' '
+		test_must_fail git worktree add foo non-existent
+	'
+
+	test_expect_success '"add" <path> <branch> dwims' '
+		test_when_finished rm -rf repo_upstream repo_dwim foo &&
+		setup_remote_repo repo_upstream repo_dwim &&
+		git init repo_dwim &&
+		(
+			cd repo_dwim &&
+			git worktree add ../foo foo &&
+			check_worktree_paths "$use_relative_paths" ../foo
+		) &&
+		(
+			cd foo &&
+			test_branch_upstream foo repo_upstream foo &&
+			test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo
+		)
+	'
+
+	test_expect_success '"add" <path> <branch> dwims with checkout.defaultRemote' '
+		test_when_finished rm -rf repo_upstream repo_dwim foo &&
+		setup_remote_repo repo_upstream repo_dwim &&
+		git init repo_dwim &&
+		(
+			cd repo_dwim &&
+			git remote add repo_upstream2 ../repo_upstream &&
+			git fetch repo_upstream2 &&
+			test_must_fail git worktree add ../foo foo &&
+			git -c checkout.defaultRemote=repo_upstream worktree add ../foo foo &&
+			check_worktree_paths "$use_relative_paths" ../foo &&
+			git status -uno --porcelain >status.actual &&
+			test_must_be_empty status.actual
+		) &&
+		(
+			cd foo &&
+			test_branch_upstream foo repo_upstream foo &&
+			test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo
+		)
+	'
+
+	test_expect_success 'git worktree add does not match remote' '
+		test_when_finished rm -rf repo_a repo_b foo &&
+		setup_remote_repo repo_a repo_b &&
+		(
+			cd repo_b &&
+			git worktree add ../foo &&
+			check_worktree_paths "$use_relative_paths" ../foo
+		) &&
+		(
+			cd foo &&
+			test_must_fail git config "branch.foo.remote" &&
+			test_must_fail git config "branch.foo.merge" &&
+			test_cmp_rev ! refs/remotes/repo_a/foo refs/heads/foo
+		)
+	'
+
+	test_expect_success 'git worktree add --guess-remote sets up tracking' '
+		test_when_finished rm -rf repo_a repo_b foo &&
+		setup_remote_repo repo_a repo_b &&
+		(
+			cd repo_b &&
+			git worktree add --guess-remote ../foo &&
+			check_worktree_paths "$use_relative_paths" ../foo
+		) &&
+		(
+			cd foo &&
+			test_branch_upstream foo repo_a foo &&
+			test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
+		)
+	'
+	test_expect_success 'git worktree add --guess-remote sets up tracking (quiet)' '
+		test_when_finished rm -rf repo_a repo_b foo &&
+		setup_remote_repo repo_a repo_b &&
+		(
+			cd repo_b &&
+			git worktree add --quiet --guess-remote ../foo 2>actual &&
+			check_worktree_paths "$use_relative_paths" ../foo &&
+			test_must_be_empty actual
+		) &&
+		(
+			cd foo &&
+			test_branch_upstream foo repo_a foo &&
+			test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
+		)
+	'
+
+	test_expect_success 'git worktree --no-guess-remote (quiet)' '
+		test_when_finished rm -rf repo_a repo_b foo &&
+		setup_remote_repo repo_a repo_b &&
+		(
+			cd repo_b &&
+			git worktree add --quiet --no-guess-remote ../foo &&
+			check_worktree_paths "$use_relative_paths" ../foo
+		) &&
+		(
+			cd foo &&
+			test_must_fail git config "branch.foo.remote" &&
+			test_must_fail git config "branch.foo.merge" &&
+			test_cmp_rev ! refs/remotes/repo_a/foo refs/heads/foo
+		)
+	'
+
+	test_expect_success 'git worktree add with worktree.guessRemote sets up tracking' '
+		test_when_finished rm -rf repo_a repo_b foo &&
+		setup_remote_repo repo_a repo_b &&
+		(
+			cd repo_b &&
+			git config worktree.guessRemote true &&
+			git worktree add ../foo &&
+			check_worktree_paths "$use_relative_paths" ../foo
+		) &&
+		(
+			cd foo &&
+			test_branch_upstream foo repo_a foo &&
+			test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo
+		)
+	'
+
+	test_expect_success 'git worktree --no-guess-remote option overrides config' '
+		test_when_finished rm -rf repo_a repo_b foo &&
+		setup_remote_repo repo_a repo_b &&
+		(
+			cd repo_b &&
+			git config worktree.guessRemote true &&
+			git worktree add --no-guess-remote ../foo &&
+			check_worktree_paths "$use_relative_paths" ../foo
+		) &&
+		(
+			cd foo &&
+			test_must_fail git config "branch.foo.remote" &&
+			test_must_fail git config "branch.foo.merge" &&
+			test_cmp_rev ! refs/remotes/repo_a/foo refs/heads/foo
+		)
+	'
+
+	for quiet_mode in "no_quiet" "quiet"
+	do
+		for changedir_type in "cd_repo" "cd_wt" "-C_repo" "-C_wt"
+		do
+			dwim_test_args="$quiet_mode $changedir_type"
+			test_dwim_orphan $use_relative_paths 'infer' $dwim_test_args no_-b
+			test_dwim_orphan $use_relative_paths 'no_infer' $dwim_test_args no_-b local_ref good_head
+			test_dwim_orphan $use_relative_paths 'infer' $dwim_test_args no_-b no_local_ref no_remote no_remote_ref no_guess_remote
+			test_dwim_orphan $use_relative_paths 'infer' $dwim_test_args no_-b no_local_ref remote no_remote_ref no_guess_remote
+			test_dwim_orphan $use_relative_paths 'fetch_error' $dwim_test_args no_-b no_local_ref remote no_remote_ref guess_remote
+			test_dwim_orphan $use_relative_paths 'infer' $dwim_test_args no_-b no_local_ref remote no_remote_ref guess_remote force
+			test_dwim_orphan $use_relative_paths 'no_infer' $dwim_test_args no_-b no_local_ref remote remote_ref guess_remote
+
+			test_dwim_orphan $use_relative_paths 'infer' $dwim_test_args -b
+			test_dwim_orphan $use_relative_paths 'no_infer' $dwim_test_args -b local_ref good_head
+			test_dwim_orphan $use_relative_paths 'infer' $dwim_test_args -b no_local_ref no_remote no_remote_ref no_guess_remote
+			test_dwim_orphan $use_relative_paths 'infer' $dwim_test_args -b no_local_ref remote no_remote_ref no_guess_remote
+			test_dwim_orphan $use_relative_paths 'infer' $dwim_test_args -b no_local_ref remote no_remote_ref guess_remote
+			test_dwim_orphan $use_relative_paths 'infer' $dwim_test_args -b no_local_ref remote remote_ref guess_remote
+
+			test_dwim_orphan $use_relative_paths 'warn_bad_head' $dwim_test_args no_-b local_ref bad_head
+			test_dwim_orphan $use_relative_paths 'warn_bad_head' $dwim_test_args -b local_ref bad_head
+			test_dwim_orphan $use_relative_paths 'warn_bad_head' $dwim_test_args detach local_ref bad_head
+		done
+
+		test_dwim_orphan $use_relative_paths 'fatal_orphan_bad_combo' $quiet_mode no_-b no_checkout
+		test_dwim_orphan $use_relative_paths 'fatal_orphan_bad_combo' $quiet_mode no_-b track
+		test_dwim_orphan $use_relative_paths 'fatal_orphan_bad_combo' $quiet_mode -b no_checkout
+		test_dwim_orphan $use_relative_paths 'fatal_orphan_bad_combo' $quiet_mode -b track
+	done
+
+	test_expect_success '"add" invokes post-checkout hook (branch)' '
+		post_checkout_hook &&
+		{
+			echo $ZERO_OID $(git rev-parse HEAD) 1 &&
+			echo $(pwd)/.git/worktrees/gumby &&
+			echo $(pwd)/gumby
+		} >hook.expect &&
+		git worktree add gumby &&
+		check_worktree_paths "$use_relative_paths" gumby &&
+		test_cmp hook.expect gumby/hook.actual
+	'
+
+	test_expect_success '"add" invokes post-checkout hook (detached)' '
+		post_checkout_hook &&
+		{
+			echo $ZERO_OID $(git rev-parse HEAD) 1 &&
+			echo $(pwd)/.git/worktrees/grumpy &&
+			echo $(pwd)/grumpy
+		} >hook.expect &&
+		git worktree add --detach grumpy &&
+		check_worktree_paths "$use_relative_paths" grumpy &&
+		test_cmp hook.expect grumpy/hook.actual
+	'
+
+	test_expect_success '"add --no-checkout" suppresses post-checkout hook' '
+		post_checkout_hook &&
+		rm -f hook.actual &&
+		git worktree add --no-checkout gloopy &&
+		check_worktree_paths "$use_relative_paths" gloopy &&
+		test_path_is_missing gloopy/hook.actual
+	'
+
+	test_expect_success '"add" in other worktree invokes post-checkout hook' '
+		post_checkout_hook &&
+		{
+			echo $ZERO_OID $(git rev-parse HEAD) 1 &&
+			echo $(pwd)/.git/worktrees/guppy &&
+			echo $(pwd)/guppy
+		} >hook.expect &&
+		git -C gloopy worktree add --detach ../guppy &&
+		check_worktree_paths "$use_relative_paths" guppy &&
+		test_cmp hook.expect guppy/hook.actual
+	'
+
+	test_expect_success '"add" in bare repo invokes post-checkout hook' '
+		rm -rf bare &&
+		git clone --bare . bare &&
+		{
+			echo $ZERO_OID $(git --git-dir=bare rev-parse HEAD) 1 &&
+			echo $(pwd)/bare/worktrees/goozy &&
+			echo $(pwd)/goozy
+		} >hook.expect &&
+		post_checkout_hook bare &&
+		git -C bare worktree add --detach ../goozy &&
+		check_worktree_paths "$use_relative_paths" goozy &&
+		test_cmp hook.expect goozy/hook.actual
+	'
+
+	test_expect_success '"add" an existing but missing worktree' '
+		git worktree add --detach pneu &&
+		check_worktree_paths "$use_relative_paths" pneu &&
+		test_must_fail git worktree add --detach pneu &&
+		rm -fr pneu &&
+		test_must_fail git worktree add --detach pneu &&
+		git worktree add --force --detach pneu &&
+		check_worktree_paths "$use_relative_paths" pneu
+	'
+
+	test_expect_success '"add" an existing locked but missing worktree' '
+		git worktree add --detach gnoo &&
+		check_worktree_paths "$use_relative_paths" gnoo &&
+		git worktree lock gnoo &&
+		test_when_finished "git worktree unlock gnoo || :" &&
+		rm -fr gnoo &&
+		test_must_fail git worktree add --detach gnoo &&
+		test_must_fail git worktree add --force --detach gnoo &&
+		git worktree add --force --force --detach gnoo &&
+		check_worktree_paths "$use_relative_paths" gnoo
+	'
+
+	test_expect_success '"add" not tripped up by magic worktree matching"' '
+		# if worktree "sub1/bar" exists, "git worktree add bar" in distinct
+		# directory `sub2` should not mistakenly complain that `bar` is an
+		# already-registered worktree
+		mkdir sub1 sub2 &&
+		git -C sub1 --git-dir=../.git worktree add --detach bozo &&
+		check_worktree_paths "$use_relative_paths" sub1/bozo &&
+		git -C sub2 --git-dir=../.git worktree add --detach bozo &&
+		check_worktree_paths "$use_relative_paths" sub2/bozo
+	'
+
+	test_expect_success FUNNYNAMES 'sanitize generated worktree name' '
+		git worktree add --detach ".  weird*..?.lock.lock" &&
+		check_worktree_paths "$use_relative_paths" ".  weird*..?.lock.lock" &&
+		test -d .git/worktrees/---weird-.-
+	'
+
+	test_expect_success '"add" should not fail because of another bad worktree' '
+		git init add-fail &&
+		(
+			cd add-fail &&
+			test_commit first &&
+			mkdir sub &&
+			git worktree add sub/to-be-deleted &&
+			check_worktree_paths "$use_relative_paths" sub/to-be-deleted &&
+			rm -rf sub &&
+			git worktree add second &&
+			check_worktree_paths "$use_relative_paths" second
+		)
+	'
+
+	test_expect_success '"add" with uninitialized submodule, with submodule.recurse unset' '
+		test_config_global protocol.file.allow always &&
+		test_create_repo submodule &&
+		test_commit -C submodule first &&
+		test_create_repo project &&
+		git -C project submodule add ../submodule &&
+		git -C project add submodule &&
+		test_tick &&
+		git -C project commit -m add_sub &&
+		git clone project project-clone &&
+		git -C project-clone worktree add ../project-2 &&
+		check_worktree_paths "$use_relative_paths" project-2
+	'
+	test_expect_success '"add" with uninitialized submodule, with submodule.recurse set' '
+		git -C project-clone -c submodule.recurse worktree add ../project-3 &&
+		check_worktree_paths "$use_relative_paths" project-3
+	'
+
+	test_expect_success '"add" with initialized submodule, with submodule.recurse unset' '
+		test_config_global protocol.file.allow always &&
+		git -C project-clone submodule update --init &&
+		git -C project-clone worktree add ../project-4 &&
+		check_worktree_paths "$use_relative_paths" project-4
+	'
+
+	test_expect_success '"add" with initialized submodule, with submodule.recurse set' '
+		git -C project-clone -c submodule.recurse worktree add ../project-5 &&
+		check_worktree_paths "$use_relative_paths" project-5
+	'
+}
+
+say "Run tests with worktree.userelativepaths set to false"
+run_worktree_add_tests false
+
+cd ..
+setup_test_repo
+
+say "Run tests with worktree.userelativepaths set to true"
+run_worktree_add_tests true
+
+say "All other tests"
+test_wt_add_excl -b poodle -B poodle bamboo main
+test_wt_add_excl -b poodle --detach bamboo main
+test_wt_add_excl -B poodle --detach bamboo main
+test_wt_add_excl --orphan --detach bamboo
+test_wt_add_excl --orphan --no-checkout bamboo
+test_wt_add_excl --orphan bamboo main
+test_wt_add_excl --orphan -b bamboo wtdir/ main
+
+test_wt_add_orphan_hint 'no opts' 0
+test_wt_add_orphan_hint '-b' 1 -b foobar_branch
+test_wt_add_orphan_hint '-B' 1 -B foobar_branch
 
 test_done
diff --git a/t/t2401-worktree-prune.sh b/t/t2401-worktree-prune.sh
index 71aa9bcd620..7ff6e794027 100755
--- a/t/t2401-worktree-prune.sh
+++ b/t/t2401-worktree-prune.sh
@@ -80,44 +80,66 @@ test_expect_success 'not prune locked checkout' '
 	test -d .git/worktrees/ghi
 '
 
-test_expect_success 'not prune recent checkouts' '
-	test_when_finished rm -r .git/worktrees &&
-	git worktree add jlm HEAD &&
-	test -d .git/worktrees/jlm &&
-	rm -rf jlm &&
-	git worktree prune --verbose --expire=2.days.ago &&
-	test -d .git/worktrees/jlm
-'
-
-test_expect_success 'not prune proper checkouts' '
-	test_when_finished rm -r .git/worktrees &&
-	git worktree add --detach "$PWD/nop" main &&
-	git worktree prune &&
-	test -d .git/worktrees/nop
-'
-
-test_expect_success 'prune duplicate (linked/linked)' '
-	test_when_finished rm -fr .git/worktrees w1 w2 &&
-	git worktree add --detach w1 &&
-	git worktree add --detach w2 &&
-	sed "s/w2/w1/" .git/worktrees/w2/gitdir >.git/worktrees/w2/gitdir.new &&
-	mv .git/worktrees/w2/gitdir.new .git/worktrees/w2/gitdir &&
-	git worktree prune --verbose 2>actual &&
-	test_grep "duplicate entry" actual &&
-	test -d .git/worktrees/w1 &&
-	! test -d .git/worktrees/w2
+run_worktree_prune_tests() {
+	local use_relative_paths="$1"
+
+	# Set the Git config variable based on the parameter
+	test_config_global worktree.userelativepaths "$use_relative_paths"
+
+	test_expect_success 'not prune recent checkouts' '
+		test_when_finished rm -r .git/worktrees &&
+		git worktree add jlm HEAD &&
+		test -d .git/worktrees/jlm &&
+		rm -rf jlm &&
+		git worktree prune --verbose --expire=2.days.ago &&
+		test -d .git/worktrees/jlm
+	'
+
+	test_expect_success 'not prune proper checkouts' '
+		test_when_finished rm -r .git/worktrees &&
+		git worktree add --detach "$PWD/nop" main &&
+		git worktree prune &&
+		test -d .git/worktrees/nop
+	'
+
+	test_expect_success 'prune duplicate (linked/linked)' '
+		test_when_finished rm -fr .git/worktrees w1 w2 &&
+		git worktree add --detach w1 &&
+		git worktree add --detach w2 &&
+		sed "s/w2/w1/" .git/worktrees/w2/gitdir >.git/worktrees/w2/gitdir.new &&
+		mv .git/worktrees/w2/gitdir.new .git/worktrees/w2/gitdir &&
+		git worktree prune --verbose 2>actual &&
+		test_grep "duplicate entry" actual &&
+		test -d .git/worktrees/w1 &&
+		! test -d .git/worktrees/w2
+	'
+
+	test_expect_success 'prune duplicate (main/linked)' '
+		test_when_finished rm -fr repo wt &&
+		test_create_repo repo &&
+		test_commit -C repo x &&
+		git -C repo worktree add --detach ../wt &&
+		rm -fr wt &&
+		mv repo wt &&
+		git -C wt worktree prune --verbose 2>actual &&
+		echo "FOO" &&
+		cat actual &&
+		echo "BAR" &&
+		test_grep "duplicate entry" actual &&
+		! test -d .git/worktrees/wt
+	'
+}
+
+say "Run tests with worktree.userelativepaths set to false"
+run_worktree_prune_tests false
+
+cd ..
+setup_test_repo
+test_expect_success initialize '
+	git commit --allow-empty -m init
 '
 
-test_expect_success 'prune duplicate (main/linked)' '
-	test_when_finished rm -fr repo wt &&
-	test_create_repo repo &&
-	test_commit -C repo x &&
-	git -C repo worktree add --detach ../wt &&
-	rm -fr wt &&
-	mv repo wt &&
-	git -C wt worktree prune --verbose 2>actual &&
-	test_grep "duplicate entry" actual &&
-	! test -d .git/worktrees/wt
-'
+say "Run tests with worktree.userelativepaths set to true"
+run_worktree_prune_tests true
 
 test_done
diff --git a/t/t2403-worktree-move.sh b/t/t2403-worktree-move.sh
index 901342ea09b..90fab3800bc 100755
--- a/t/t2403-worktree-move.sh
+++ b/t/t2403-worktree-move.sh
@@ -4,6 +4,7 @@ test_description='test git worktree move, remove, lock and unlock'
 
 TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-worktree.sh
 
 test_expect_success 'setup' '
 	test_commit init &&
@@ -247,4 +248,13 @@ test_expect_success 'not remove a repo with initialized submodule' '
 	)
 '
 
+test_expect_success 'move worktree and verify path format' '
+	test_config worktree.userelativepaths false &&
+	git worktree add fred &&
+	check_worktree_paths false fred &&
+	test_config worktree.userelativepaths true &&
+	git worktree move fred barney &&
+	check_worktree_paths true barney
+'
+
 test_done
diff --git a/t/t2406-worktree-repair.sh b/t/t2406-worktree-repair.sh
index edbf502ec57..ae9cdd2f031 100755
--- a/t/t2406-worktree-repair.sh
+++ b/t/t2406-worktree-repair.sh
@@ -5,6 +5,145 @@ test_description='test git worktree repair'
 TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
+. "$TEST_DIRECTORY"/lib-worktree.sh
+
+test_corrupt_gitfile () {
+	local use_relative_paths="$1" &&
+	local butcher="$2" &&
+	local problem="$3" &&
+	local repairdir="${4:-.}" &&
+	test_when_finished 'rm -rf corrupt && git worktree prune' &&
+	git worktree add --detach corrupt &&
+	git -C corrupt rev-parse --absolute-git-dir >expect &&
+	eval "$butcher" &&
+	git -C "$repairdir" worktree repair 2>err &&
+	check_worktree_paths "$use_relative_paths" corrupt &&
+	test_grep "$problem" err &&
+	git -C corrupt rev-parse --absolute-git-dir >actual &&
+	test_cmp expect actual
+}
+
+run_worktree_repair_tests() {
+	local use_relative_paths="$1"
+
+	# Set the Git config variable based on the parameter
+	test_config_global worktree.userelativepaths "$use_relative_paths"
+
+	test_expect_success setup '
+		test_commit init
+	'
+
+	test_expect_success 'repair missing .git file' '
+		test_corrupt_gitfile "$use_relative_paths" "rm -f corrupt/.git" ".git file broken"
+	'
+
+	test_expect_success 'repair bogus .git file' '
+		test_corrupt_gitfile "$use_relative_paths" "echo \"gitdir: /nowhere\" >corrupt/.git" \
+			".git file broken"
+	'
+
+	test_expect_success 'repair incorrect .git file' '
+		test_when_finished "rm -rf other && git worktree prune" &&
+		test_create_repo other &&
+		other=$(git -C other rev-parse --absolute-git-dir) &&
+		test_corrupt_gitfile "$use_relative_paths" "echo \"gitdir: $other\" >corrupt/.git" \
+			".git file incorrect"
+	'
+
+	test_expect_success 'repair .git file from main/.git' '
+		test_corrupt_gitfile "$use_relative_paths" "rm -f corrupt/.git" ".git file broken" .git
+	'
+
+	test_expect_success 'repair .git file from linked worktree' '
+		test_when_finished "rm -rf other && git worktree prune" &&
+		git worktree add --detach other &&
+		test_corrupt_gitfile "$use_relative_paths" "rm -f corrupt/.git" ".git file broken" other
+	'
+
+	test_expect_success 'repair .git file from bare.git' '
+		test_when_finished "rm -rf bare.git corrupt && git worktree prune" &&
+		git clone --bare . bare.git &&
+		git -C bare.git worktree add --detach ../corrupt &&
+		git -C corrupt rev-parse --absolute-git-dir >expect &&
+		rm -f corrupt/.git &&
+		git -C bare.git worktree repair &&
+		check_worktree_paths "$use_relative_paths" corrupt &&
+		git -C corrupt rev-parse --absolute-git-dir >actual &&
+		test_cmp expect actual
+	'
+
+	test_expect_success 'repair broken gitdir' '
+		test_when_finished "rm -rf orig moved && git worktree prune" &&
+		git worktree add --detach orig &&
+		sed s,orig/\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&
+		rm .git/worktrees/orig/gitdir &&
+		mv orig moved &&
+		git worktree repair moved 2>err &&
+		check_worktree_paths "$use_relative_paths" moved &&
+		test_cmp expect .git/worktrees/orig/gitdir &&
+		test_grep "gitdir unreadable" err
+	'
+
+	test_expect_success 'repair incorrect gitdir' '
+		test_when_finished "rm -rf orig moved && git worktree prune" &&
+		git worktree add --detach orig &&
+		sed s,orig/\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&
+		mv orig moved &&
+		git worktree repair moved 2>err &&
+		check_worktree_paths "$use_relative_paths" moved &&
+		test_cmp expect .git/worktrees/orig/gitdir &&
+		test_grep "gitdir incorrect" err
+	'
+
+	test_expect_success 'repair gitdir (implicit) from linked worktree' '
+		test_when_finished "rm -rf orig moved && git worktree prune" &&
+		git worktree add --detach orig &&
+		sed s,orig/\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&
+		mv orig moved &&
+		git -C moved worktree repair 2>err &&
+		check_worktree_paths "$use_relative_paths" moved &&
+		test_cmp expect .git/worktrees/orig/gitdir &&
+		test_grep "gitdir incorrect" err
+	'
+
+	test_expect_success 'repair multiple gitdir files' '
+		test_when_finished "rm -rf orig1 orig2 moved1 moved2 &&
+			git worktree prune" &&
+		git worktree add --detach orig1 &&
+		git worktree add --detach orig2 &&
+		sed s,orig1/\.git$,moved1/.git, .git/worktrees/orig1/gitdir >expect1 &&
+		sed s,orig2/\.git$,moved2/.git, .git/worktrees/orig2/gitdir >expect2 &&
+		mv orig1 moved1 &&
+		mv orig2 moved2 &&
+		git worktree repair moved1 moved2 2>err &&
+		check_worktree_paths "$use_relative_paths" moved1 &&
+		check_worktree_paths "$use_relative_paths" moved2 &&
+		test_cmp expect1 .git/worktrees/orig1/gitdir &&
+		test_cmp expect2 .git/worktrees/orig2/gitdir &&
+		test_grep "gitdir incorrect:.*orig1/gitdir$" err &&
+		test_grep "gitdir incorrect:.*orig2/gitdir$" err
+	'
+
+	test_expect_success 'repair moved main and linked worktrees' '
+		test_when_finished "rm -rf main side mainmoved sidemoved" &&
+		test_create_repo main &&
+		test_commit -C main init &&
+		git -C main worktree add --detach ../side &&
+		sed "s,side/\.git$,sidemoved/.git," \
+			main/.git/worktrees/side/gitdir >expect-gitdir &&
+		sed "s,main/.git/worktrees/side$,mainmoved/.git/worktrees/side," \
+			side/.git >expect-gitfile &&
+		mv main mainmoved &&
+		mv side sidemoved &&
+		git -C mainmoved worktree repair ../sidemoved &&
+		check_worktree_paths "$use_relative_paths" sidemoved &&
+		test_cmp expect-gitdir mainmoved/.git/worktrees/side/gitdir &&
+		test_cmp expect-gitfile sidemoved/.git
+	'
+}
+
+say "Expected failures due to inability to repair the broken worktree."
+
 test_expect_success setup '
 	test_commit init
 '
@@ -38,58 +177,6 @@ test_expect_success "don't clobber .git repo" '
 	test_grep ".git is not a file" err
 '
 
-test_corrupt_gitfile () {
-	butcher=$1 &&
-	problem=$2 &&
-	repairdir=${3:-.} &&
-	test_when_finished 'rm -rf corrupt && git worktree prune' &&
-	git worktree add --detach corrupt &&
-	git -C corrupt rev-parse --absolute-git-dir >expect &&
-	eval "$butcher" &&
-	git -C "$repairdir" worktree repair 2>err &&
-	test_grep "$problem" err &&
-	git -C corrupt rev-parse --absolute-git-dir >actual &&
-	test_cmp expect actual
-}
-
-test_expect_success 'repair missing .git file' '
-	test_corrupt_gitfile "rm -f corrupt/.git" ".git file broken"
-'
-
-test_expect_success 'repair bogus .git file' '
-	test_corrupt_gitfile "echo \"gitdir: /nowhere\" >corrupt/.git" \
-		".git file broken"
-'
-
-test_expect_success 'repair incorrect .git file' '
-	test_when_finished "rm -rf other && git worktree prune" &&
-	test_create_repo other &&
-	other=$(git -C other rev-parse --absolute-git-dir) &&
-	test_corrupt_gitfile "echo \"gitdir: $other\" >corrupt/.git" \
-		".git file incorrect"
-'
-
-test_expect_success 'repair .git file from main/.git' '
-	test_corrupt_gitfile "rm -f corrupt/.git" ".git file broken" .git
-'
-
-test_expect_success 'repair .git file from linked worktree' '
-	test_when_finished "rm -rf other && git worktree prune" &&
-	git worktree add --detach other &&
-	test_corrupt_gitfile "rm -f corrupt/.git" ".git file broken" other
-'
-
-test_expect_success 'repair .git file from bare.git' '
-	test_when_finished "rm -rf bare.git corrupt && git worktree prune" &&
-	git clone --bare . bare.git &&
-	git -C bare.git worktree add --detach ../corrupt &&
-	git -C corrupt rev-parse --absolute-git-dir >expect &&
-	rm -f corrupt/.git &&
-	git -C bare.git worktree repair &&
-	git -C corrupt rev-parse --absolute-git-dir >actual &&
-	test_cmp expect actual
-'
-
 test_expect_success 'invalid worktree path' '
 	test_must_fail git worktree repair /notvalid >out 2>err &&
 	test_must_be_empty out &&
@@ -124,37 +211,6 @@ test_expect_success 'repo not found; .git file broken' '
 	test_grep ".git file broken" err
 '
 
-test_expect_success 'repair broken gitdir' '
-	test_when_finished "rm -rf orig moved && git worktree prune" &&
-	git worktree add --detach orig &&
-	sed s,orig/\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&
-	rm .git/worktrees/orig/gitdir &&
-	mv orig moved &&
-	git worktree repair moved 2>err &&
-	test_cmp expect .git/worktrees/orig/gitdir &&
-	test_grep "gitdir unreadable" err
-'
-
-test_expect_success 'repair incorrect gitdir' '
-	test_when_finished "rm -rf orig moved && git worktree prune" &&
-	git worktree add --detach orig &&
-	sed s,orig/\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&
-	mv orig moved &&
-	git worktree repair moved 2>err &&
-	test_cmp expect .git/worktrees/orig/gitdir &&
-	test_grep "gitdir incorrect" err
-'
-
-test_expect_success 'repair gitdir (implicit) from linked worktree' '
-	test_when_finished "rm -rf orig moved && git worktree prune" &&
-	git worktree add --detach orig &&
-	sed s,orig/\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&
-	mv orig moved &&
-	git -C moved worktree repair 2>err &&
-	test_cmp expect .git/worktrees/orig/gitdir &&
-	test_grep "gitdir incorrect" err
-'
-
 test_expect_success 'unable to repair gitdir (implicit) from main worktree' '
 	test_when_finished "rm -rf orig moved && git worktree prune" &&
 	git worktree add --detach orig &&
@@ -165,36 +221,16 @@ test_expect_success 'unable to repair gitdir (implicit) from main worktree' '
 	test_must_be_empty err
 '
 
-test_expect_success 'repair multiple gitdir files' '
-	test_when_finished "rm -rf orig1 orig2 moved1 moved2 &&
-		git worktree prune" &&
-	git worktree add --detach orig1 &&
-	git worktree add --detach orig2 &&
-	sed s,orig1/\.git$,moved1/.git, .git/worktrees/orig1/gitdir >expect1 &&
-	sed s,orig2/\.git$,moved2/.git, .git/worktrees/orig2/gitdir >expect2 &&
-	mv orig1 moved1 &&
-	mv orig2 moved2 &&
-	git worktree repair moved1 moved2 2>err &&
-	test_cmp expect1 .git/worktrees/orig1/gitdir &&
-	test_cmp expect2 .git/worktrees/orig2/gitdir &&
-	test_grep "gitdir incorrect:.*orig1/gitdir$" err &&
-	test_grep "gitdir incorrect:.*orig2/gitdir$" err
-'
+cd ..
+setup_test_repo
 
-test_expect_success 'repair moved main and linked worktrees' '
-	test_when_finished "rm -rf main side mainmoved sidemoved" &&
-	test_create_repo main &&
-	test_commit -C main init &&
-	git -C main worktree add --detach ../side &&
-	sed "s,side/\.git$,sidemoved/.git," \
-		main/.git/worktrees/side/gitdir >expect-gitdir &&
-	sed "s,main/.git/worktrees/side$,mainmoved/.git/worktrees/side," \
-		side/.git >expect-gitfile &&
-	mv main mainmoved &&
-	mv side sidemoved &&
-	git -C mainmoved worktree repair ../sidemoved &&
-	test_cmp expect-gitdir mainmoved/.git/worktrees/side/gitdir &&
-	test_cmp expect-gitfile sidemoved/.git
-'
+say "Run tests with worktree.userelativepaths set to false"
+run_worktree_repair_tests false
+
+cd ..
+setup_test_repo
+
+say "Run tests with worktree.userelativepaths set to true"
+run_worktree_repair_tests true
 
 test_done
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 278d1215f15..c1fe8df2c39 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -1627,26 +1627,30 @@ remove_trash_directory () {
 	! test -d "$dir"
 }
 
-# Test repository
-remove_trash_directory "$TRASH_DIRECTORY" || {
-	BAIL_OUT 'cannot prepare test area'
-}
+setup_test_repo() {
+	# Remove the trash directory if it exists
+	remove_trash_directory "$TRASH_DIRECTORY" || {
+		BAIL_OUT 'cannot prepare test area'
+	}
 
-remove_trash=t
-if test -z "$TEST_NO_CREATE_REPO"
-then
-	git init \
-	    ${TEST_CREATE_REPO_NO_TEMPLATE:+--template=} \
-	    "$TRASH_DIRECTORY" >&3 2>&4 ||
-	error "cannot run git init"
-else
-	mkdir -p "$TRASH_DIRECTORY"
-fi
+	remove_trash=t
 
-# Use -P to resolve symlinks in our working directory so that the cwd
-# in subprocesses like git equals our $PWD (for pathname comparisons).
-cd -P "$TRASH_DIRECTORY" || BAIL_OUT "cannot cd -P to \"$TRASH_DIRECTORY\""
+	# Initialize a new Git repository or create the directory
+	if [ -z "$TEST_NO_CREATE_REPO" ]; then
+		git init \
+			${TEST_CREATE_REPO_NO_TEMPLATE:+--template=} \
+			"$TRASH_DIRECTORY" >&3 2>&4 ||
+		error "cannot run git init"
+	else
+		mkdir -p "$TRASH_DIRECTORY"
+	fi
+
+	# Use -P to resolve symlinks in our working directory so that the cwd
+	# in subprocesses like git equals our $PWD (for pathname comparisons).
+	cd -P "$TRASH_DIRECTORY" || BAIL_OUT "cannot cd -P to \"$TRASH_DIRECTORY\""
+}
 
+setup_test_repo
 start_test_output "$0"
 
 # Convenience
diff --git a/worktree.c b/worktree.c
index 30a947426ee..a3a8c580508 100644
--- a/worktree.c
+++ b/worktree.c
@@ -328,17 +328,6 @@ int validate_worktree(const struct worktree *wt, struct strbuf *errmsg,
 		goto done;
 	}
 
-	/*
-	 * Make sure "gitdir" file points to a real .git file and that
-	 * file points back here.
-	 */
-	if (!is_absolute_path(wt->path)) {
-		strbuf_addf_gently(errmsg,
-				   _("'%s' file does not contain absolute path to the working tree location"),
-				   git_common_path("worktrees/%s/gitdir", wt->id));
-		goto done;
-	}
-
 	if (flags & WT_VALIDATE_WORKTREE_MISSING_OK &&
 	    !file_exists(wt->path)) {
 		ret = 0;
@@ -372,19 +361,30 @@ done:
 
 void update_worktree_location(struct worktree *wt, const char *path_)
 {
-	struct strbuf path = STRBUF_INIT;
+	struct strbuf worktree_new_path = STRBUF_INIT;
+	int use_relative_paths = 0;
 
 	if (is_main_worktree(wt))
 		BUG("can't relocate main worktree");
 
-	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);
-		free(wt->path);
-		wt->path = strbuf_detach(&path, NULL);
-	}
-	strbuf_release(&path);
+	strbuf_realpath(&worktree_new_path, path_, 1);
+
+	use_relative_paths = repo_config_get_worktree_use_relative_paths(the_repository);
+	connect_work_tree_and_git_dir(worktree_new_path.buf,
+				      git_common_path("worktrees/%s", wt->id),
+				      use_relative_paths);
+	connect_gitdir_file_and_work_tree(worktree_new_path.buf, wt->id,
+					  use_relative_paths);
+
+	/* Update worktree's path */
+	free(wt->path);
+	/*
+	TODO: This is setting the worktree's path to an absolute path, not
+	sure if always correct, especially when using relative paths.
+	*/
+	wt->path = strbuf_detach(&worktree_new_path, NULL);
+
+	strbuf_release(&worktree_new_path);
 }
 
 int is_worktree_being_rebased(const struct worktree *wt,
@@ -564,21 +564,25 @@ static void repair_gitfile(struct worktree *wt,
 {
 	struct strbuf dotgit = STRBUF_INIT;
 	struct strbuf repo = STRBUF_INIT;
-	char *backlink;
+	char *backlink = NULL;
 	const char *repair = NULL;
+	char *abs_wt_path = NULL;
 	int err;
+	int use_relative_paths = 0;
+
+	abs_wt_path = worktree_real_pathdup_for_wt(wt);
 
 	/* missing worktree can't be repaired */
-	if (!file_exists(wt->path))
-		return;
+	if (!file_exists(abs_wt_path))
+		goto done;
 
-	if (!is_directory(wt->path)) {
+	if (!is_directory(abs_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);
+	strbuf_addf(&dotgit, "%s/.git", abs_wt_path);
 	backlink = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err));
 
 	if (err == READ_GITFILE_ERR_NOT_A_FILE)
@@ -590,10 +594,16 @@ static void repair_gitfile(struct worktree *wt,
 
 	if (repair) {
 		fn(0, wt->path, repair, cb_data);
-		write_file(dotgit.buf, "gitdir: %s", repo.buf);
+
+		use_relative_paths =
+			repo_config_get_worktree_use_relative_paths(the_repository);
+		connect_work_tree_and_git_dir(abs_wt_path, repo.buf,
+					      use_relative_paths);
 	}
 
+done:
 	free(backlink);
+	free(abs_wt_path);
 	strbuf_release(&repo);
 	strbuf_release(&dotgit);
 }
@@ -682,9 +692,11 @@ void repair_worktree_at_path(const char *path,
 	struct strbuf realdotgit = STRBUF_INIT;
 	struct strbuf gitdir = STRBUF_INIT;
 	struct strbuf olddotgit = STRBUF_INIT;
+	struct strbuf abs_wt_path_sb = STRBUF_INIT;
 	char *backlink = NULL;
 	const char *repair = NULL;
 	int err;
+	int use_relative_paths = 0;
 
 	if (!fn)
 		fn = repair_noop;
@@ -717,16 +729,26 @@ void repair_worktree_at_path(const char *path,
 		repair = _("gitdir unreadable");
 	else {
 		strbuf_rtrim(&olddotgit);
+		if (!is_absolute_path(olddotgit.buf))
+			strbuf_realpath_forgiving(&olddotgit, olddotgit.buf, 1);
+
 		if (fspathcmp(olddotgit.buf, realdotgit.buf))
 			repair = _("gitdir incorrect");
 	}
 
 	if (repair) {
 		fn(0, gitdir.buf, repair, cb_data);
-		write_file(gitdir.buf, "%s", realdotgit.buf);
+
+		use_relative_paths =
+			repo_config_get_worktree_use_relative_paths(the_repository);
+
+		strbuf_add_real_path(&abs_wt_path_sb, path);
+		connect_gitdir_file_and_work_tree(abs_wt_path_sb.buf, basename(backlink),
+						  use_relative_paths);
 	}
 done:
 	free(backlink);
+	strbuf_release(&abs_wt_path_sb);
 	strbuf_release(&olddotgit);
 	strbuf_release(&gitdir);
 	strbuf_release(&realdotgit);
