{"thread":{"id":"62161","subject":"[PATCH] builtin/worktree: support relative paths for worktrees","startedAt":"2024-09-21T01:02:37Z","lastAt":"2024-09-24T08:06:37Z","messageCount":4,"participants":["Francesco Guastella via GitGitGadget","Junio C Hamano","Francesco Guastella","Eric Sunshine"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"503176","messageId":"pull.1783.git.git.1726880551891.gitgitgadget@gmail.com","threadId":"62161","inReplyTo":null,"subject":"[PATCH] builtin/worktree: support relative paths for worktrees","fromName":"Francesco Guastella via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-09-21T01:02:31Z","receivedAt":"2024-09-21T01:02:37Z","isPatch":true,"sender":{"key":"guastella.francesco@gmail.com","avatar":null},"body":"From: Francesco Guastella <guastella.francesco@gmail.com>\n\nAdd a new configuration option, `worktree.useRelativePaths`, which allows\nusers to choose between storing relative or absolute paths in the files\nGit uses to maintain links between the main worktree and any additional\nones.\n\nBy default, `git worktree add` will continue to use absolute paths,\nmaintaining the existing behavior and therefore ensuring compatibility.\nHowever, when the `worktree.useRelativePaths` configuration is set to\ntrue, all newly added worktrees will use relative paths, offering\ngreater flexibility, especially for repositories that are accessed from\ndifferent operating systems.\n\nAdditionally, the `git worktree move` command has been enhanced to\nadhere to the current value of `worktree.useRelativePaths`. When moving\na worktree, the path formats will be adjusted to match the desired\nconfiguration.\n\nThe `git worktree repair` command has also been updated to automatically\nadjust paths between relative and absolute formats based on the\n`worktree.useRelativePaths` setting.\n\nSigned-off-by: Francesco Guastella <guastella.francesco@gmail.com>\n---\n    builtin/worktree: support relative paths for worktrees\n    \n    As of now, when creating or managing worktrees, paths are always stored\n    in absolute form. While this behavior works well in many scenarios, it\n    presents significant challenges when worktrees are accessed across\n    different operating systems or when the repository is moved to a\n    different location in the filesystem. Absolute paths hard-coded in the\n    .git and gitdir files can break these links, causing issues that may\n    require manual intervention to resolve.\n    \n    To address this, I have introduced a new configuration option:\n    worktree.useRelativePaths. This option allows users to specify whether\n    they prefer Git to store worktree paths in relative form rather than\n    absolute. The new feature enhances Git’s flexibility, particularly in\n    environments where repositories need to be portable across different\n    systems or where directories are frequently relocated.\n    \n    Key Changes: The new worktree.useRelativePaths option can be enabled by\n    the user to store paths in relative form. When enabled, any new\n    worktrees added using the git worktree add command will have their paths\n    stored as relative paths in the necessary git files.\n    \n    The git worktree move command has been updated to respect the current\n    value of worktree.useRelativePaths. When a worktree is moved, Git will\n    now automatically adjust the path format (relative or absolute) to match\n    the user's configuration setting.\n    \n    The git worktree repair command has been similarly enhanced. It will now\n    automatically convert paths between relative and absolute forms based on\n    the worktree.useRelativePaths setting, making it easier to maintain\n    consistent links across different environments.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1783%2Fromeoxbm%2Frelative-paths-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1783/romeoxbm/relative-paths-v1\nPull-Request: https://github.com/git/git/pull/1783\n\n Documentation/git-worktree.txt |   18 +\n abspath.c                      |   38 +\n abspath.h                      |   44 +\n builtin/mv.c                   |    6 +-\n builtin/submodule--helper.c    |    2 +-\n builtin/worktree.c             |   51 +-\n config.c                       |   10 +\n config.h                       |   15 +\n dir.c                          |  106 +-\n dir.h                          |   75 +-\n submodule.c                    |   12 +-\n t/lib-worktree.sh              |  156 +++\n t/t2400-worktree-add.sh        | 1729 +++++++++++++++++---------------\n t/t2401-worktree-prune.sh      |   96 +-\n t/t2403-worktree-move.sh       |   10 +\n t/t2406-worktree-repair.sh     |  262 ++---\n t/test-lib.sh                  |   38 +-\n worktree.c                     |   78 +-\n 18 files changed, 1682 insertions(+), 1064 deletions(-)\n create mode 100644 t/lib-worktree.sh\n\ndiff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt\nindex 2a240f53ba7..c4f59229194 100644\n--- a/Documentation/git-worktree.txt\n+++ b/Documentation/git-worktree.txt\n@@ -105,6 +105,15 @@ passed to the command. In the event the repository has a remote and\n `--guess-remote` is used, but no remote or local branches exist, then the\n command fails with a warning reminding the user to fetch from their remote\n first (or override by using `-f/--force`).\n++\n+By default, the `git worktree add` command links the main worktree with the\n+newly created worktree using absolute paths.\n+However, this behavior may not be desirable when a repository needs\n+to be accessed from different operating systems. To address this, Git can\n+be configured to use relative paths instead. To enable this behavior, set the\n+`worktree.useRelativePaths` configuration option to `true`. Once enabled,\n+all newly added worktrees will be linked using relative paths.\n+\n \n list::\n \n@@ -128,6 +137,11 @@ Move a worktree to a new location. Note that the main worktree or linked\n worktrees containing submodules cannot be moved with this command. (The\n `git worktree repair` command, however, can reestablish the connection\n with linked worktrees if you move the main worktree manually.)\n++\n+When moving a worktree, the path formats will be adjusted to align with the\n+current value of `worktree.useRelativePaths`. This means that if the worktree\n+was created using absolute paths and the configuration is later set to `true`,\n+the link will be reestablished using relative paths during the move.\n \n prune::\n \n@@ -357,6 +371,10 @@ directory (e.g. `/path/main/.git/worktrees/test-next` in the example) and\n (e.g. `/path/main/.git`). These settings are made in a `.git` file located at\n the top directory of the linked worktree.\n \n+If `worktree.useRelativePaths` is enabled, the paths in this `.git` file\n+will be stored in a relative format. Conversely, if it is disabled, the paths\n+will be stored in an absolute format.\n+\n Path resolution via `git rev-parse --git-path` uses either\n `$GIT_DIR` or `$GIT_COMMON_DIR` depending on the path. For example, in the\n linked worktree `git rev-parse --git-path HEAD` returns\ndiff --git a/abspath.c b/abspath.c\nindex 1202cde23db..7a03becce99 100644\n--- a/abspath.c\n+++ b/abspath.c\n@@ -1,6 +1,9 @@\n #include \"git-compat-util.h\"\n #include \"abspath.h\"\n+#include \"environment.h\"\n+#include \"path.h\"\n #include \"strbuf.h\"\n+#include \"worktree.h\"\n \n /*\n  * Do not use this for inspecting *tracked* content.  When path is a\n@@ -290,6 +293,41 @@ char *prefix_filename_except_for_dash(const char *pfx, const char *arg)\n \treturn prefix_filename(pfx, arg);\n }\n \n+char *worktree_real_pathdup(const char *wt_path)\n+{\n+\tstruct strbuf abs_wt_path_sb = STRBUF_INIT;\n+\tchar *retval = NULL;\n+\tchar *git_dir_real_path = NULL;\n+\tchar *repo_real_path = NULL;\n+\n+\tif (!is_absolute_path(wt_path)) {\n+\t\tgit_dir_real_path = real_pathdup(get_git_common_dir(), 1);\n+\t\trepo_real_path = strip_path_suffix(git_dir_real_path, \".git\");\n+\t\tif (repo_real_path) {\n+\t\t\tstrbuf_addf(&abs_wt_path_sb, \"%s/%s\", repo_real_path,\n+\t\t\t\t    wt_path);\n+\t\t} else {\n+\t\t\tstrbuf_addf(&abs_wt_path_sb, \"%s/%s\", git_dir_real_path,\n+\t\t\t\t    wt_path);\n+\t\t}\n+\n+\t\tstrbuf_realpath_forgiving(&abs_wt_path_sb, abs_wt_path_sb.buf, 1);\n+\t\tretval = strbuf_detach(&abs_wt_path_sb, NULL);\n+\t} else {\n+\t\tretval = xstrdup(wt_path);\n+\t}\n+\n+\tfree(git_dir_real_path);\n+\tfree(repo_real_path);\n+\tstrbuf_release(&abs_wt_path_sb);\n+\treturn retval;\n+}\n+\n+char *worktree_real_pathdup_for_wt(struct worktree *wt)\n+{\n+\treturn worktree_real_pathdup(wt->path);\n+}\n+\n void strbuf_add_absolute_path(struct strbuf *sb, const char *path)\n {\n \tif (!*path)\ndiff --git a/abspath.h b/abspath.h\nindex 4653080d5e4..4163ac9c18d 100644\n--- a/abspath.h\n+++ b/abspath.h\n@@ -1,6 +1,8 @@\n #ifndef ABSPATH_H\n #define ABSPATH_H\n \n+struct worktree;\n+\n int is_directory(const char *);\n char *strbuf_realpath(struct strbuf *resolved, const char *path,\n \t\t      int die_on_error);\n@@ -25,6 +27,48 @@ char *prefix_filename(const char *prefix, const char *path);\n /* Likewise, but path==\"-\" always yields \"-\" */\n char *prefix_filename_except_for_dash(const char *prefix, const char *path);\n \n+/**\n+ * worktree_real_pathdup - Duplicate the absolute path of a worktree.\n+ *\n+ * @wt_path: The path to the worktree. This can be either an absolute or\n+ *           relative path.\n+ *\n+ * Return: A newly allocated string containing the absolute path. If the input\n+ *         path is already absolute, it returns a duplicate of the input path.\n+ *         If the path is relative, it constructs the absolute path by appending\n+ *         the relative path to the repository directory. The repository\n+ *         directory is derived from get_git_common_dir(), and the '.git' suffix\n+ *         is removed if present.\n+ *\n+ *         The returned path is resolved into its canonical form using\n+ *         strbuf_realpath_forgiving to handle symbolic links or non-existent\n+ *         paths gracefully.\n+ *\n+ * The caller is responsible for freeing the returned string when it is no\n+ * longer needed.\n+ */\n+char *worktree_real_pathdup(const char *wt_path);\n+\n+/**\n+ * worktree_real_pathdup_for_wt - Duplicate the absolute path of a worktree from\n+ *                                a worktree structure.\n+ *\n+ * @wt: A pointer to the worktree structure.\n+ *\n+ * Return: A newly allocated string containing the absolute path of the worktree.\n+ *         If the worktree's path is relative, it constructs the absolute path\n+ *         by appending the relative path to the repository directory (derived\n+ *         from get_git_common_dir()). If the path is already absolute, it returns\n+ *         a duplicate of the worktree's path.\n+ *\n+ * The caller is responsible for freeing the returned string when it is no\n+ * longer needed.\n+ *\n+ * This function is similar to worktree_real_pathdup() but takes a pointer to a\n+ * worktree structure instead of a raw path.\n+ */\n+char *worktree_real_pathdup_for_wt(struct worktree *wt);\n+\n static inline int is_absolute_path(const char *path)\n {\n \treturn is_dir_sep(path[0]) || has_dos_drive_prefix(path);\ndiff --git a/builtin/mv.c b/builtin/mv.c\nindex 6c69033c5f5..8604525b85c 100644\n--- a/builtin/mv.c\n+++ b/builtin/mv.c\n@@ -490,9 +490,9 @@ remove_entry:\n \t\t\tif (!update_path_in_gitmodules(src, dst))\n \t\t\t\tgitmodules_modified = 1;\n \t\t\tif (submodule_gitfiles[i] != SUBMODULE_WITH_GITDIR)\n-\t\t\t\tconnect_work_tree_and_git_dir(dst,\n-\t\t\t\t\t\t\t      submodule_gitfiles[i],\n-\t\t\t\t\t\t\t      1);\n+\t\t\t\tconnect_submodule_work_tree_and_git_dir(dst,\n+\t\t\t\t\t\t\t\t\tsubmodule_gitfiles[i],\n+\t\t\t\t\t\t\t\t\t1);\n \t\t}\n \n \t\tif (mode & (WORKING_DIRECTORY | SKIP_WORKTREE_DIR))\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 3e0b6c45c03..98c0c1f6722 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -1806,7 +1806,7 @@ static int clone_submodule(const struct module_clone_data *clone_data,\n \t\t      \"git dir\"), sm_gitdir);\n \t}\n \n-\tconnect_work_tree_and_git_dir(clone_data_path, sm_gitdir, 0);\n+\tconnect_submodule_work_tree_and_git_dir(clone_data_path, sm_gitdir, 0);\n \n \tp = git_pathdup_submodule(clone_data_path, \"config\");\n \tif (!p)\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 41e7f6a3271..d217857843e 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -214,8 +214,12 @@ static void prune_worktrees(void)\n \t\tstrbuf_reset(&reason);\n \t\tif (should_prune_worktree(d->d_name, &reason, &path, expire))\n \t\t\tprune_worktree(d->d_name, reason.buf);\n-\t\telse if (path)\n-\t\t\tstring_list_append_nodup(&kept, path)->util = xstrdup(d->d_name);\n+\t\telse if (path) {\n+\t\t\tstring_list_append_nodup(&kept,\n+\t\t\t\t\t\t worktree_real_pathdup(path))\n+\t\t\t\t->util = xstrdup(d->d_name);\n+\t\t\tfree(path);\n+\t\t}\n \t}\n \tclosedir(dir);\n \n@@ -414,7 +418,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\t\tconst struct add_opts *opts)\n {\n \tstruct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;\n-\tstruct strbuf sb = STRBUF_INIT, realpath = STRBUF_INIT;\n+\tstruct strbuf sb = STRBUF_INIT;\n \tconst char *name;\n \tstruct strvec child_env = STRVEC_INIT;\n \tunsigned int counter = 0;\n@@ -425,6 +429,7 @@ static int add_worktree(const char *path, const char *refname,\n \tstruct strbuf sb_name = STRBUF_INIT;\n \tstruct worktree **worktrees, *wt = NULL;\n \tstruct ref_store *wt_refs;\n+\tint use_relative_paths = 0;\n \n \tworktrees = get_worktrees();\n \tcheck_candidate_path(path, opts->force, worktrees, \"add\");\n@@ -482,19 +487,14 @@ static int add_worktree(const char *path, const char *refname,\n \telse\n \t\twrite_file(sb.buf, _(\"initializing\"));\n \n-\tstrbuf_addf(&sb_git, \"%s/.git\", path);\n-\tif (safe_create_leading_directories_const(sb_git.buf))\n-\t\tdie_errno(_(\"could not create leading directories of '%s'\"),\n-\t\t\t  sb_git.buf);\n \tjunk_work_tree = xstrdup(path);\n \n \tstrbuf_reset(&sb);\n-\tstrbuf_addf(&sb, \"%s/gitdir\", sb_repo.buf);\n-\tstrbuf_realpath(&realpath, sb_git.buf, 1);\n-\twrite_file(sb.buf, \"%s\", realpath.buf);\n-\tstrbuf_realpath(&realpath, get_git_common_dir(), 1);\n-\twrite_file(sb_git.buf, \"gitdir: %s/worktrees/%s\",\n-\t\t   realpath.buf, name);\n+\tuse_relative_paths = repo_config_get_worktree_use_relative_paths(the_repository);\n+\tstrbuf_realpath_forgiving(&sb, path, 1);\n+\tconnect_work_tree_and_git_dir(sb.buf, sb_repo.buf, use_relative_paths);\n+\tconnect_gitdir_file_and_work_tree(sb.buf, name, use_relative_paths);\n+\n \tstrbuf_reset(&sb);\n \tstrbuf_addf(&sb, \"%s/commondir\", sb_repo.buf);\n \twrite_file(sb.buf, \"../..\");\n@@ -536,6 +536,7 @@ static int add_worktree(const char *path, const char *refname,\n \tif (the_repository->repository_format_worktree_config)\n \t\tcopy_filtered_worktree_config(sb_repo.buf);\n \n+\tstrbuf_addf(&sb_git, \"%s/.git\", path);\n \tstrvec_pushf(&child_env, \"%s=%s\", GIT_DIR_ENVIRONMENT, sb_git.buf);\n \tstrvec_pushf(&child_env, \"%s=%s\", GIT_WORK_TREE_ENVIRONMENT, path);\n \n@@ -582,7 +583,6 @@ done:\n \tstrbuf_release(&sb_repo);\n \tstrbuf_release(&sb_git);\n \tstrbuf_release(&sb_name);\n-\tstrbuf_release(&realpath);\n \tfree_worktree(wt);\n \treturn ret;\n }\n@@ -1185,8 +1185,8 @@ static int move_worktree(int ac, const char **av, const char *prefix)\n \tint force = 0;\n \tstruct option options[] = {\n \t\tOPT__FORCE(&force,\n-\t\t\t N_(\"force move even if worktree is dirty or locked\"),\n-\t\t\t PARSE_OPT_NOCOMPLETE),\n+\t\t\t   N_(\"force move even if worktree is dirty or locked\"),\n+\t\t\t   PARSE_OPT_NOCOMPLETE),\n \t\tOPT_END()\n \t};\n \tstruct worktree **worktrees, *wt;\n@@ -1263,6 +1263,7 @@ static void check_clean_worktree(struct worktree *wt,\n \tstruct child_process cp;\n \tchar buf[1];\n \tint ret;\n+\tchar *absolute_wt_path = NULL;\n \n \t/*\n \t * Until we sort this out, all submodules are \"dirty\" and\n@@ -1271,15 +1272,15 @@ static void check_clean_worktree(struct worktree *wt,\n \tvalidate_no_submodules(wt);\n \n \tchild_process_init(&cp);\n-\tstrvec_pushf(&cp.env, \"%s=%s/.git\",\n-\t\t     GIT_DIR_ENVIRONMENT, wt->path);\n-\tstrvec_pushf(&cp.env, \"%s=%s\",\n-\t\t     GIT_WORK_TREE_ENVIRONMENT, wt->path);\n-\tstrvec_pushl(&cp.args, \"status\",\n-\t\t     \"--porcelain\", \"--ignore-submodules=none\",\n-\t\t     NULL);\n+\tabsolute_wt_path = worktree_real_pathdup_for_wt(wt);\n+\tstrvec_pushf(&cp.env, \"%s=%s/.git\", GIT_DIR_ENVIRONMENT,\n+\t\t     absolute_wt_path);\n+\tstrvec_pushf(&cp.env, \"%s=%s\", GIT_WORK_TREE_ENVIRONMENT,\n+\t\t     absolute_wt_path);\n+\tstrvec_pushl(&cp.args, \"status\", \"--porcelain\",\n+\t\t     \"--ignore-submodules=none\", NULL);\n \tcp.git_cmd = 1;\n-\tcp.dir = wt->path;\n+\tcp.dir = absolute_wt_path;\n \tcp.out = -1;\n \tret = start_command(&cp);\n \tif (ret)\n@@ -1294,6 +1295,8 @@ static void check_clean_worktree(struct worktree *wt,\n \tif (ret)\n \t\tdie_errno(_(\"failed to run 'git status' on '%s', code %d\"),\n \t\t\t  original_path, ret);\n+\n+\tfree(absolute_wt_path);\n }\n \n static int delete_git_work_tree(struct worktree *wt)\ndiff --git a/config.c b/config.c\nindex 56b5862e59d..5750d258ac9 100644\n--- a/config.c\n+++ b/config.c\n@@ -2731,6 +2731,16 @@ int repo_config_get_expiry_in_days(struct repository *r, const char *key,\n \treturn -1; /* thing exists but cannot be parsed */\n }\n \n+int repo_config_get_worktree_use_relative_paths(struct repository *r)\n+{\n+\tint val;\n+\n+\tif (!repo_config_get_maybe_bool(r, \"worktree.userelativepaths\", &val))\n+\t\treturn val;\n+\n+\treturn 0; /* by default, absolute paths are used */\n+}\n+\n int repo_config_get_split_index(struct repository *r)\n {\n \tint val;\ndiff --git a/config.h b/config.h\nindex d0497157c52..77e1c3812fb 100644\n--- a/config.h\n+++ b/config.h\n@@ -678,6 +678,21 @@ int repo_config_get_expiry(struct repository *r, const char *key, char **output)\n int repo_config_get_expiry_in_days(struct repository *r, const char *key,\n \t\t\t\t   timestamp_t *, timestamp_t now);\n \n+/**\n+ * Retrieves the configuration that determines if worktrees should use relative paths.\n+ *\n+ * This function checks the \"worktree.userelativepaths\" configuration.\n+ * If the configuration was found and is a valid boolean value, the function returns that value.\n+ * If the configuration was not found or invalid, the function returns 0,\n+ * indicating that by default, absolute paths are used for worktrees.\n+ *\n+ * Return:\n+ * - 1: If \"worktree.userelativepaths\" is set to true (use relative paths).\n+ * - 0: If \"worktree.userelativepaths\" is set to false or is not set\n+ *      (use absolute paths by default).\n+ */\n+int repo_config_get_worktree_use_relative_paths(struct repository *r);\n+\n /**\n  * First prints the error message specified by the caller in `err` and then\n  * dies printing the line number and the file name of the highest priority\ndiff --git a/dir.c b/dir.c\nindex 5a23376bdae..49aba2c0a72 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -4041,43 +4041,117 @@ static void connect_wt_gitdir_in_nested(const char *sub_worktree,\n \t\tstrbuf_addf(&sub_wt, \"%s/%s\", sub_worktree, sub->path);\n \t\tsubmodule_name_to_gitdir(&sub_gd, &subrepo, sub->name);\n \n-\t\tconnect_work_tree_and_git_dir(sub_wt.buf, sub_gd.buf, 1);\n+\t\tconnect_submodule_work_tree_and_git_dir(sub_wt.buf, sub_gd.buf,\n+\t\t\t\t\t\t\t1);\n \t}\n \tstrbuf_release(&sub_wt);\n \tstrbuf_release(&sub_gd);\n \trepo_clear(&subrepo);\n }\n \n-void connect_work_tree_and_git_dir(const char *work_tree_,\n-\t\t\t\t   const char *git_dir_,\n-\t\t\t\t   int recurse_into_nested)\n+void connect_work_tree_and_git_dir(const char *work_tree_, const char *git_dir_,\n+\t\t\t\t   int use_relative_paths_)\n {\n+\tstruct strbuf rel_path_sb = STRBUF_INIT;\n \tstruct strbuf gitfile_sb = STRBUF_INIT;\n+\tchar *git_dir = NULL, *work_tree = NULL;\n+\tconst char *rel_path = NULL;\n+\tint free_gitdir = 0;\n+\n+\twork_tree = worktree_real_pathdup(work_tree_);\n+\tstrbuf_addf(&gitfile_sb, \"%s/.git\", work_tree);\n+\tif (safe_create_leading_directories_const(gitfile_sb.buf))\n+\t\tdie(_(\"could not create directories for %s\"), gitfile_sb.buf);\n+\n+\tif (!is_absolute_path(git_dir_)) {\n+\t\tgit_dir = real_pathdup(git_dir_, 1);\n+\t\tfree_gitdir = 1;\n+\t} else\n+\t\tgit_dir = (char *)git_dir_;\n+\n+\tif (use_relative_paths_) {\n+\t\trel_path = relative_path(git_dir, work_tree, &rel_path_sb);\n+\n+\t\t/* Write a relative path */\n+\t\twrite_file(gitfile_sb.buf, \"gitdir: %s\", rel_path);\n+\t} else {\n+\t\t/* Write an absolute path */\n+\t\twrite_file(gitfile_sb.buf, \"gitdir: %s\", git_dir);\n+\t}\n+\n+\tif (free_gitdir)\n+\t\tfree(git_dir);\n+\n+\tfree(work_tree);\n+\tstrbuf_release(&rel_path_sb);\n+\tstrbuf_release(&gitfile_sb);\n+}\n+\n+void connect_gitdir_file_and_work_tree(const char *wt_dir_,\n+\t\t\t\t       const char *wt_name_,\n+\t\t\t\t       int use_relative_paths_)\n+{\n+\tstruct strbuf gitdirfile_sb = STRBUF_INIT;\n+\tstruct strbuf rel_path_sb = STRBUF_INIT;\n+\tchar *git_dir_real_path = NULL;\n+\tchar *repo_real_path = NULL, *work_tree = NULL;\n+\tconst char *rel_path = NULL;\n+\n+\tgit_path_buf(&gitdirfile_sb, \"worktrees/%s/gitdir\", wt_name_);\n+\tif (safe_create_leading_directories_const(gitdirfile_sb.buf)) {\n+\t\tdie(_(\"could not create directories for %s\"),\n+\t\t    gitdirfile_sb.buf);\n+\t}\n+\n+\twork_tree = worktree_real_pathdup(wt_dir_);\n+\tif (use_relative_paths_) {\n+\t\tgit_dir_real_path = real_pathdup(get_git_common_dir(), 1);\n+\t\trepo_real_path = strip_path_suffix(git_dir_real_path, \".git\");\n+\t\tif (repo_real_path) {\n+\t\t\trel_path = relative_path(work_tree, repo_real_path,\n+\t\t\t\t\t\t &rel_path_sb);\n+\t\t} else {\n+\t\t\trel_path = relative_path(work_tree, git_dir_real_path,\n+\t\t\t\t\t\t &rel_path_sb);\n+\t\t}\n+\n+\t\t/* Write a relative path */\n+\t\twrite_file(gitdirfile_sb.buf, \"%s/.git\", rel_path);\n+\t} else {\n+\t\t/* Write an absolute path */\n+\t\twrite_file(gitdirfile_sb.buf, \"%s/.git\", work_tree);\n+\t}\n+\n+\tfree(repo_real_path);\n+\tfree(work_tree);\n+\tfree(git_dir_real_path);\n+\tstrbuf_release(&rel_path_sb);\n+\tstrbuf_release(&gitdirfile_sb);\n+}\n+\n+void connect_submodule_work_tree_and_git_dir(const char *work_tree_,\n+\t\t\t\t\t     const char *git_dir_,\n+\t\t\t\t\t     int recurse_into_nested)\n+{\n \tstruct strbuf cfg_sb = STRBUF_INIT;\n \tstruct strbuf rel_path = STRBUF_INIT;\n \tchar *git_dir, *work_tree;\n \n-\t/* Prepare .git file */\n-\tstrbuf_addf(&gitfile_sb, \"%s/.git\", work_tree_);\n-\tif (safe_create_leading_directories_const(gitfile_sb.buf))\n-\t\tdie(_(\"could not create directories for %s\"), gitfile_sb.buf);\n+\tgit_dir = real_pathdup(git_dir_, 1);\n+\twork_tree = real_pathdup(work_tree_, 1);\n+\n+\t/* Write .git file */\n+\tconnect_work_tree_and_git_dir(work_tree, git_dir, 1);\n \n \t/* Prepare config file */\n \tstrbuf_addf(&cfg_sb, \"%s/config\", git_dir_);\n \tif (safe_create_leading_directories_const(cfg_sb.buf))\n \t\tdie(_(\"could not create directories for %s\"), cfg_sb.buf);\n \n-\tgit_dir = real_pathdup(git_dir_, 1);\n-\twork_tree = real_pathdup(work_tree_, 1);\n-\n-\t/* Write .git file */\n-\twrite_file(gitfile_sb.buf, \"gitdir: %s\",\n-\t\t   relative_path(git_dir, work_tree, &rel_path));\n \t/* Update core.worktree setting */\n \tgit_config_set_in_file(cfg_sb.buf, \"core.worktree\",\n \t\t\t       relative_path(work_tree, git_dir, &rel_path));\n \n-\tstrbuf_release(&gitfile_sb);\n \tstrbuf_release(&cfg_sb);\n \tstrbuf_release(&rel_path);\n \n@@ -4097,7 +4171,7 @@ void relocate_gitdir(const char *path, const char *old_git_dir, const char *new_\n \t\tdie_errno(_(\"could not migrate git directory from '%s' to '%s'\"),\n \t\t\told_git_dir, new_git_dir);\n \n-\tconnect_work_tree_and_git_dir(path, new_git_dir, 0);\n+\tconnect_submodule_work_tree_and_git_dir(path, new_git_dir, 0);\n }\n \n int path_match_flags(const char *const str, const enum path_match_flags flags)\ndiff --git a/dir.h b/dir.h\nindex a3a2f00f5d9..56e8224167e 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -597,6 +597,75 @@ void write_untracked_extension(struct strbuf *out, struct untracked_cache *untra\n void add_untracked_cache(struct index_state *istate);\n void remove_untracked_cache(struct index_state *istate);\n \n+/**\n+ * connect_work_tree_and_git_dir - Create a .git file in the worktree that\n+ *                                 points to the main repository.\n+ *\n+ * @work_tree_: The path to the worktree that needs to be connected. This can\n+ *              be either an absolute or relative path.\n+ * @git_dir_: The path to the main repository's git directory (.git). This can\n+ *            be either an absolute or relative path.\n+ * @use_relative_paths_: Flag indicating whether to use relative paths when\n+ *                       creating the connection. If non-zero, a relative path\n+ *                       between the git directory and the worktree is used.\n+ *                       Otherwise, absolute paths are used.\n+ *\n+ * This function creates a `.git` file in the specified worktree directory,\n+ * which contains either a relative or absolute path pointing back to the main\n+ * repository's .git directory. This connection allows the worktree to function\n+ * as an independent working directory while still being linked to the main\n+ * repository.\n+ *\n+ * If `use_relative_paths_` is set to non-zero, the function writes a relative\n+ * path from the worktree to the git directory in the `.git` file. If the\n+ * worktree path is provided as a relative path, it is first converted to an\n+ * absolute path before determining the relative path.\n+ *\n+ * If `use_relative_paths_` is zero, the function writes an absolute path to\n+ * the git directory in the `.git` file.\n+ *\n+ * In case of any errors during directory creation or file writing, the\n+ * function terminates execution with an error message.\n+ */\n+void connect_work_tree_and_git_dir(const char *work_tree_,\n+\t\t\t\t   const char *git_dir_,\n+\t\t\t\t   int use_relative_paths_);\n+\n+/**\n+ * connect_gitdir_file_and_work_tree - Connect a gitdir file to a worktree.\n+ *\n+ * @wt_dir_: The path to the worktree directory. This can be either an\n+ *           absolute or relative path.\n+ * @wt_name_: The name of the worktree as it appears in the git repository's\n+ *            worktrees directory.\n+ * @use_relative_paths_: Flag indicating whether to use relative paths when\n+ *                       creating the connection. If non-zero, a relative path\n+ *                       between the git directory and the worktree is used.\n+ *                       Otherwise, absolute paths are used.\n+ *\n+ * This function creates a gitdir file in the git repository's worktrees\n+ * directory, which contains a path to the worktree directory. The path\n+ * can be relative or absolute based on the value of `use_relative_paths_`.\n+ *\n+ * The gitdir file is created under the worktrees directory with a name that\n+ * matches the `wt_name_` parameter. The file contains a reference to the\n+ * worktree's `.git` file.\n+ *\n+ * If `use_relative_paths_` is set to non-zero, the function writes a relative\n+ * path from the repository root to the worktree in the gitdir file. If the\n+ * worktree path is provided as a relative path, it is first converted to an\n+ * absolute path before determining the relative path.\n+ *\n+ * If `use_relative_paths_` is zero, the function writes an absolute path to\n+ * the work tree in the gitdir file.\n+ *\n+ * In case of any errors during directory creation or file writing, the\n+ * function terminates execution with an error message.\n+ */\n+void connect_gitdir_file_and_work_tree(const char *wt_dir_,\n+\t\t\t\t       const char *wt_name_,\n+\t\t\t\t       int use_relative_paths_);\n+\n /*\n  * Connect a worktree to a git directory by creating (or overwriting) a\n  * '.git' file containing the location of the git directory. In the git\n@@ -604,9 +673,9 @@ void remove_untracked_cache(struct index_state *istate);\n  * When `recurse_into_nested` is set, recurse into any nested submodules,\n  * connecting them as well.\n  */\n-void connect_work_tree_and_git_dir(const char *work_tree,\n-\t\t\t\t   const char *git_dir,\n-\t\t\t\t   int recurse_into_nested);\n+void connect_submodule_work_tree_and_git_dir(const char *work_tree,\n+\t\t\t\t\t     const char *git_dir,\n+\t\t\t\t\t     int recurse_into_nested);\n void relocate_gitdir(const char *path,\n \t\t     const char *old_git_dir,\n \t\t     const char *new_git_dir);\ndiff --git a/submodule.c b/submodule.c\nindex 97516b0fec1..1b3c6bde4a9 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -1672,7 +1672,7 @@ get_fetch_task_from_changed(struct submodule_parallel_fetch *spf,\n \t\t * core.worktree when they are populated/unpopulated by\n \t\t * \"git checkout\" (and similar commands, see\n \t\t * submodule_move_head() and\n-\t\t * connect_work_tree_and_git_dir()), but if the\n+\t\t * connect_submodule_work_tree_and_git_dir()), but if the\n \t\t * submodule is unpopulated in another way (e.g. \"git\n \t\t * rm\", \"rm -r\"), core.worktree will still be set even\n \t\t * though the directory doesn't exist, and the child\n@@ -2147,7 +2147,7 @@ int submodule_move_head(const char *path, const char *super_prefix,\n \tif (flags & SUBMODULE_MOVE_HEAD_FORCE)\n \t\t/*\n \t\t * Pass non NULL pointer to is_submodule_populated_gently\n-\t\t * to prevent die()-ing. We'll use connect_work_tree_and_git_dir\n+\t\t * to prevent die()-ing. We'll use connect_submodule_work_tree_and_git_dir\n \t\t * to fixup the submodule in the force case later.\n \t\t */\n \t\terror_code_ptr = &error_code;\n@@ -2194,7 +2194,8 @@ int submodule_move_head(const char *path, const char *super_prefix,\n \t\t\t\tdie(_(\"refusing to create/use '%s' in another \"\n \t\t\t\t      \"submodule's git dir\"),\n \t\t\t\t    gitdir.buf);\n-\t\t\tconnect_work_tree_and_git_dir(path, gitdir.buf, 0);\n+\t\t\tconnect_submodule_work_tree_and_git_dir(path,\n+\t\t\t\t\t\t\t\tgitdir.buf, 0);\n \t\t\tstrbuf_release(&gitdir);\n \n \t\t\t/* make sure the index is clean as well */\n@@ -2205,7 +2206,8 @@ int submodule_move_head(const char *path, const char *super_prefix,\n \t\t\tstruct strbuf gitdir = STRBUF_INIT;\n \t\t\tsubmodule_name_to_gitdir(&gitdir, the_repository,\n \t\t\t\t\t\t sub->name);\n-\t\t\tconnect_work_tree_and_git_dir(path, gitdir.buf, 1);\n+\t\t\tconnect_submodule_work_tree_and_git_dir(path,\n+\t\t\t\t\t\t\t\tgitdir.buf, 1);\n \t\t\tstrbuf_release(&gitdir);\n \t\t}\n \t}\n@@ -2457,7 +2459,7 @@ void absorb_git_dir_into_superproject(const char *path,\n \t\tif (!sub)\n \t\t\tdie(_(\"could not lookup name for submodule '%s'\"), path);\n \t\tsubmodule_name_to_gitdir(&sub_gitdir, the_repository, sub->name);\n-\t\tconnect_work_tree_and_git_dir(path, sub_gitdir.buf, 0);\n+\t\tconnect_submodule_work_tree_and_git_dir(path, sub_gitdir.buf, 0);\n \t\tstrbuf_release(&sub_gitdir);\n \t} else {\n \t\t/* Is it already absorbed into the superprojects git dir? */\ndiff --git a/t/lib-worktree.sh b/t/lib-worktree.sh\nnew file mode 100644\nindex 00000000000..245612b6e4d\n--- /dev/null\n+++ b/t/lib-worktree.sh\n@@ -0,0 +1,156 @@\n+# Helper functions for git worktree tests\n+\n+# is_absolute_path - Determine if a given path is absolute.\n+#\n+# This function checks if the provided path is an absolute path.\n+# It handles Unix-like and Windows-style paths.\n+#\n+# Parameters:\n+# $1 - The path to check.\n+#\n+# Returns:\n+# 0 if the path is absolute.\n+# 1 if the path is relative.\n+is_absolute_path() {\n+\tlocal path=\"$1\"\n+\n+\t# Check for Unix-style absolute path (starts with /)\n+\tcase \"$path\" in\n+\t\t/*) return 0 ;;\n+\tesac\n+\n+\t# Check for Windows-style absolute path with backslashes (starts with drive letter followed by :\\)\n+\tcase \"$path\" in\n+\t\t[a-zA-Z]:\\\\*) return 0 ;;\n+\tesac\n+\n+\t# Check for Windows-style absolute path with forward slashes (starts with drive letter followed by :/)\n+\tcase \"$path\" in\n+\t\t[a-zA-Z]:/*) return 0 ;;\n+\tesac\n+\n+\treturn 1\n+}\n+\n+# check_worktree_paths - Verify the format of the worktree paths.\n+#\n+# This function checks whether the paths specified in the worktree's\n+# configuration files (.git and gitdir files) are in the expected format,\n+# based on the provided configuration for relative or absolute paths.\n+#\n+# Parameters:\n+# $1 - Boolean value (\"true\" or \"false\") indicating whether relative paths\n+#      are expected. If \"true\", the function expects relative paths; otherwise,\n+#      it expects absolute paths.\n+# $2 - The path to the worktree directory.\n+#\n+# Functionality:\n+# - Reads the .git file in the specified worktree directory to extract the path\n+#   to the gitdir file.\n+# - Determines whether the extracted path is relative or absolute based\n+#   on the value of $1.\n+# - Checks if the gitdir file exists and if its contents match the expected path\n+#   format (relative or absolute).\n+# - Verify that the path in the gitdir file points back to the original worktree .git file.\n+# - Prints an error message and returns a non-zero exit code if any issues are\n+#   found, such as missing files or incorrect path formats.\n+# - Returns 0 if all checks pass and the paths are in the expected format.\n+check_worktree_paths() {\n+\tlocal func_name=\"check_worktree_paths\"\n+\tlocal use_relative_paths=\"$1\"\n+\tlocal worktree_path=\"$2\"\n+\tif [ -d \"$worktree_path\" ]; then\n+\t\tworktree_path=\"$(cd \"$worktree_path\" && pwd -P)\"\n+\telse\n+\t\techo \"[$func_name] Error: Directory \"$worktree_path\" does not exist.\"\n+\t\treturn 1\n+\tfi\n+\n+\t# Full path to the .git file in the worktree\n+\tlocal git_file=\"$worktree_path/.git\"\n+\n+\t# Check if the .git file exists\n+\tif [ ! -f \"$git_file\" ]; then\n+\t\techo \"[$func_name] Error: .git file not found in $worktree_path\"\n+\t\treturn 1\n+\tfi\n+\n+\t# Extract the path from the .git file\n+\tlocal gitdir_path=\"$(sed 's/^gitdir: //' \"$git_file\")\"\n+\n+\t# Check if the path is absolute or relative\n+\tif [ \"$use_relative_paths\" = \"true\" ]; then\n+\t\t# Ensure the path is relative\n+\t\tif is_absolute_path \"$gitdir_path\"; then\n+\t\t\techo \"[$func_name] Error: .git file contains an absolute path when a relative path was expected.\"\n+\t\t\techo \"[$func_name] Path read from .git file: $gitdir_path\"\n+\t\t\treturn 1\n+\t\tfi\n+\telse\n+\t\t# Ensure the path is absolute\n+\t\tif ! is_absolute_path \"$gitdir_path\"; then\n+\t\t\techo \"[$func_name] Error: .git file contains a relative path when an absolute path was expected.\"\n+\t\t\techo \"[$func_name] Path read from .git file: $gitdir_path\"\n+\t\t\treturn 1\n+\t\tfi\n+\tfi\n+\n+\t# Resolve the gitdir path relative to worktree if necessary\n+\tif ! is_absolute_path \"$gitdir_path\"; then\n+\t\tgitdir_path=\"$(cd \"$worktree_path\"/\"$gitdir_path\" && pwd -P)\"\n+\tfi\n+\n+\t# Verify if gitdir_path is correct and the gitdir file exists\n+\tlocal gitdir_file=\"$gitdir_path/gitdir\"\n+\tif [ ! -f \"$gitdir_file\" ]; then\n+\t\techo \"[$func_name] Error: $gitdir_file not found\"\n+\t\treturn 1\n+\tfi\n+\n+\t# Read the stored path from the gitdir file\n+\tlocal stored_path=\"$(cat \"$gitdir_file\")\"\n+\n+\tif [ \"$use_relative_paths\" = \"true\" ]; then\n+\t\t# Ensure the path is relative\n+\t\tif is_absolute_path \"$stored_path\"; then\n+\t\t\techo \"[$func_name] Error: $gitdir_file contains an absolute path when a relative path was expected.\"\n+\t\t\techo \"[$func_name] Path read from gitdir file: $stored_path\"\n+\t\t\treturn 1\n+\t\tfi\n+\telse\n+\t\t# Ensure the path is absolute\n+\t\tif ! is_absolute_path \"$stored_path\"; then\n+\t\t\techo \"[$func_name] Error: $gitdir_file contains a relative path when an absolute path was expected.\"\n+\t\t\techo \"[$func_name] Path read from gitdir file: $stored_path\"\n+\t\t\treturn 1\n+\t\tfi\n+\tfi\n+\n+\t# Resolve the stored_path path to an absolute path\n+\tif ! is_absolute_path \"$stored_path\"; then\n+\t\t# Determine the repo dir, by removing the /.git/worktrees/<worktree_dir> or /worktrees/<worktree_dir> part\n+\t\tlocal repo_dir=\"${gitdir_path%/*/*}\"\n+\t\trepo_dir=\"${repo_dir%/.git}\"\n+\n+\t\t# If repo_dir is a relative path, resolve it against worktree_path\n+\t\tif ! is_absolute_path \"$repo_dir\"; then\n+\t\t\trepo_dir=\"$(cd \"$worktree_path/$repo_dir\" && pwd -P)\"\n+\t\tfi\n+\n+\t\tstored_path=\"$(cd \"$(dirname \"$repo_dir/$stored_path\")\" && pwd -P)/.git\"\n+\t\tif [ ! -f \"$stored_path\" ]; then\n+\t\t\techo \"[$func_name] Error: File $stored_path does not exist.\"\n+\t\t\treturn 1\n+\t\tfi\n+\tfi\n+\n+\t# Verify that the stored_path points back to the original worktree .git file\n+\tif [ \"$stored_path\" != \"$git_file\" ]; then\n+\t\techo \"[$func_name]  Error: The gitdir file does not correctly reference the original .git file.\"\n+\t\techo \"Expected: $git_file\"\n+\t\techo \"Found: $stored_path\"\n+\t\treturn 1\n+\tfi\n+\n+\treturn 0\n+}\ndiff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh\nindex cfc4aeb1798..ca9bbf0aac5 100755\n--- a/t/t2400-worktree-add.sh\n+++ b/t/t2400-worktree-add.sh\n@@ -10,421 +10,21 @@ TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n . \"$TEST_DIRECTORY\"/lib-rebase.sh\n+. \"$TEST_DIRECTORY\"/lib-worktree.sh\n \n-test_expect_success 'setup' '\n-\ttest_commit init\n-'\n-\n-test_expect_success '\"add\" an existing worktree' '\n-\tmkdir -p existing/subtree &&\n-\ttest_must_fail git worktree add --detach existing main\n-'\n-\n-test_expect_success '\"add\" an existing empty worktree' '\n-\tmkdir existing_empty &&\n-\tgit worktree add --detach existing_empty main\n-'\n-\n-test_expect_success '\"add\" using shorthand - fails when no previous branch' '\n-\ttest_must_fail git worktree add existing_short -\n-'\n-\n-test_expect_success '\"add\" using - shorthand' '\n-\tgit checkout -b newbranch &&\n-\techo hello >myworld &&\n-\tgit add myworld &&\n-\tgit commit -m myworld &&\n-\tgit checkout main &&\n-\tgit worktree add short-hand - &&\n-\techo refs/heads/newbranch >expect &&\n-\tgit -C short-hand rev-parse --symbolic-full-name HEAD >actual &&\n-\ttest_cmp expect actual\n-'\n-\n-test_expect_success '\"add\" refuses to checkout locked branch' '\n-\ttest_must_fail git worktree add zere main &&\n-\t! test -d zere &&\n-\t! test -d .git/worktrees/zere\n-'\n-\n-test_expect_success 'checking out paths not complaining about linked checkouts' '\n-\t(\n-\tcd existing_empty &&\n-\techo dirty >>init.t &&\n-\tgit checkout main -- init.t\n-\t)\n-'\n-\n-test_expect_success '\"add\" worktree' '\n-\tgit rev-parse HEAD >expect &&\n-\tgit worktree add --detach here main &&\n-\t(\n-\t\tcd here &&\n-\t\ttest_cmp ../init.t init.t &&\n-\t\ttest_must_fail git symbolic-ref HEAD &&\n-\t\tgit rev-parse HEAD >actual &&\n-\t\ttest_cmp ../expect actual &&\n-\t\tgit fsck\n-\t)\n-'\n-\n-test_expect_success '\"add\" worktree with lock' '\n-\tgit worktree add --detach --lock here-with-lock main &&\n-\ttest_when_finished \"git worktree unlock here-with-lock || :\" &&\n-\ttest -f .git/worktrees/here-with-lock/locked\n-'\n-\n-test_expect_success '\"add\" worktree with lock and reason' '\n-\tlock_reason=\"why not\" &&\n-\tgit worktree add --detach --lock --reason \"$lock_reason\" here-with-lock-reason main &&\n-\ttest_when_finished \"git worktree unlock here-with-lock-reason || :\" &&\n-\ttest -f .git/worktrees/here-with-lock-reason/locked &&\n-\techo \"$lock_reason\" >expect &&\n-\ttest_cmp expect .git/worktrees/here-with-lock-reason/locked\n-'\n-\n-test_expect_success '\"add\" worktree with reason but no lock' '\n-\ttest_must_fail git worktree add --detach --reason \"why not\" here-with-reason-only main &&\n-\ttest_path_is_missing .git/worktrees/here-with-reason-only/locked\n-'\n-\n-test_expect_success '\"add\" worktree from a subdir' '\n-\t(\n-\t\tmkdir sub &&\n-\t\tcd sub &&\n-\t\tgit worktree add --detach here main &&\n-\t\tcd here &&\n-\t\ttest_cmp ../../init.t init.t\n-\t)\n-'\n-\n-test_expect_success '\"add\" from a linked checkout' '\n-\t(\n-\t\tcd here &&\n-\t\tgit worktree add --detach nested-here main &&\n-\t\tcd nested-here &&\n-\t\tgit fsck\n-\t)\n-'\n-\n-test_expect_success '\"add\" worktree creating new branch' '\n-\tgit worktree add -b newmain there main &&\n-\t(\n-\t\tcd there &&\n-\t\ttest_cmp ../init.t init.t &&\n-\t\tgit symbolic-ref HEAD >actual &&\n-\t\techo refs/heads/newmain >expect &&\n-\t\ttest_cmp expect actual &&\n-\t\tgit fsck\n-\t)\n-'\n-\n-test_expect_success 'die the same branch is already checked out' '\n-\t(\n-\t\tcd here &&\n-\t\ttest_must_fail git checkout newmain 2>actual &&\n-\t\tgrep \"already used by worktree at\" actual\n-\t)\n-'\n-\n-test_expect_success 'refuse to reset a branch in use elsewhere' '\n-\t(\n-\t\tcd here &&\n-\n-\t\t# we know we are on detached HEAD but just in case ...\n-\t\tgit checkout --detach HEAD &&\n-\t\tgit rev-parse --verify HEAD >old.head &&\n-\n-\t\tgit rev-parse --verify refs/heads/newmain >old.branch &&\n-\t\ttest_must_fail git checkout -B newmain 2>error &&\n-\t\tgit rev-parse --verify refs/heads/newmain >new.branch &&\n-\t\tgit rev-parse --verify HEAD >new.head &&\n-\n-\t\tgrep \"already used by worktree at\" error &&\n-\t\ttest_cmp old.branch new.branch &&\n-\t\ttest_cmp old.head new.head &&\n-\n-\t\t# and we must be still on the same detached HEAD state\n-\t\ttest_must_fail git symbolic-ref HEAD\n-\t)\n-'\n-\n-test_expect_success SYMLINKS 'die the same branch is already checked out (symlink)' '\n-\thead=$(git -C there rev-parse --git-path HEAD) &&\n-\tref=$(git -C there symbolic-ref HEAD) &&\n-\trm \"$head\" &&\n-\tln -s \"$ref\" \"$head\" &&\n-\ttest_must_fail git -C here checkout newmain\n-'\n-\n-test_expect_success 'not die the same branch is already checked out' '\n-\t(\n-\t\tcd here &&\n-\t\tgit worktree add --force anothernewmain newmain\n-\t)\n-'\n-\n-test_expect_success 'not die on re-checking out current branch' '\n-\t(\n-\t\tcd there &&\n-\t\tgit checkout newmain\n-\t)\n-'\n-\n-test_expect_success '\"add\" from a bare repo' '\n-\t(\n-\t\tgit clone --bare . bare &&\n-\t\tcd bare &&\n-\t\tgit worktree add -b bare-main ../there2 main\n-\t)\n-'\n-\n-test_expect_success 'checkout from a bare repo without \"add\"' '\n-\t(\n-\t\tcd bare &&\n-\t\ttest_must_fail git checkout main\n-\t)\n-'\n-\n-test_expect_success '\"add\" default branch of a bare repo' '\n-\t(\n-\t\tgit clone --bare . bare2 &&\n-\t\tcd bare2 &&\n-\t\tgit worktree add ../there3 main &&\n-\t\tcd ../there3 &&\n-\t\t# Simple check that a Git command does not\n-\t\t# immediately fail with the current setup\n-\t\tgit status\n-\t) &&\n-\tcat >expect <<-EOF &&\n-\tinit.t\n-\tEOF\n-\tls there3 >actual &&\n-\ttest_cmp expect actual\n-'\n-\n-test_expect_success '\"add\" to bare repo with worktree config' '\n-\t(\n-\t\tgit clone --bare . bare3 &&\n-\t\tcd bare3 &&\n-\t\tgit config extensions.worktreeconfig true &&\n-\n-\t\t# Add config values that are erroneous to have in\n-\t\t# a config.worktree file outside of the main\n-\t\t# working tree, to check that Git filters them out\n-\t\t# when copying config during \"git worktree add\".\n-\t\tgit config --worktree core.bare true &&\n-\t\tgit config --worktree core.worktree \"$(pwd)\" &&\n-\n-\t\t# We want to check that bogus.key is copied\n-\t\tgit config --worktree bogus.key value &&\n-\t\tgit config --unset core.bare &&\n-\t\tgit worktree add ../there4 main &&\n-\t\tcd ../there4 &&\n-\n-\t\t# Simple check that a Git command does not\n-\t\t# immediately fail with the current setup\n-\t\tgit status &&\n-\t\tgit worktree add --detach ../there5 &&\n-\t\tcd ../there5 &&\n-\t\tgit status\n-\t) &&\n-\n-\t# the worktree has the arbitrary value copied.\n-\ttest_cmp_config -C there4 value bogus.key &&\n-\ttest_cmp_config -C there5 value bogus.key &&\n-\n-\t# however, core.bare and core.worktree were removed.\n-\ttest_must_fail git -C there4 config core.bare &&\n-\ttest_must_fail git -C there4 config core.worktree &&\n-\n-\tcat >expect <<-EOF &&\n-\tinit.t\n-\tEOF\n-\n-\tls there4 >actual &&\n-\ttest_cmp expect actual &&\n-\tls there5 >actual &&\n-\ttest_cmp expect actual\n-'\n-\n-test_expect_success 'checkout with grafts' '\n-\ttest_when_finished rm .git/info/grafts &&\n-\ttest_commit abc &&\n-\tSHA1=$(git rev-parse HEAD) &&\n-\ttest_commit def &&\n-\ttest_commit xyz &&\n-\tmkdir .git/info &&\n-\techo \"$(git rev-parse HEAD) $SHA1\" >.git/info/grafts &&\n-\tcat >expected <<-\\EOF &&\n-\txyz\n-\tabc\n-\tEOF\n-\tgit log --format=%s -2 >actual &&\n-\ttest_cmp expected actual &&\n-\tgit worktree add --detach grafted main &&\n-\tgit --git-dir=grafted/.git log --format=%s -2 >actual &&\n-\ttest_cmp expected actual\n-'\n-\n-test_expect_success '\"add\" from relative HEAD' '\n-\ttest_commit a &&\n-\ttest_commit b &&\n-\ttest_commit c &&\n-\tgit rev-parse HEAD~1 >expected &&\n-\tgit worktree add relhead HEAD~1 &&\n-\tgit -C relhead rev-parse HEAD >actual &&\n-\ttest_cmp expected actual\n-'\n-\n-test_expect_success '\"add -b\" with <branch> omitted' '\n-\tgit worktree add -b burble flornk &&\n-\ttest_cmp_rev HEAD burble\n-'\n-\n-test_expect_success '\"add --detach\" with <branch> omitted' '\n-\tgit worktree add --detach fishhook &&\n-\tgit rev-parse HEAD >expected &&\n-\tgit -C fishhook rev-parse HEAD >actual &&\n-\ttest_cmp expected actual &&\n-\ttest_must_fail git -C fishhook symbolic-ref HEAD\n-'\n-\n-test_expect_success '\"add\" with <branch> omitted' '\n-\tgit worktree add wiffle/bat &&\n-\ttest_cmp_rev HEAD bat\n-'\n-\n-test_expect_success '\"add\" checks out existing branch of dwimd name' '\n-\tgit branch dwim HEAD~1 &&\n-\tgit worktree add dwim &&\n-\ttest_cmp_rev HEAD~1 dwim &&\n-\t(\n-\t\tcd dwim &&\n-\t\ttest_cmp_rev HEAD dwim\n-\t)\n-'\n-\n-test_expect_success '\"add <path>\" dwim fails with checked out branch' '\n-\tgit checkout -b test-branch &&\n-\ttest_must_fail git worktree add test-branch &&\n-\ttest_path_is_missing test-branch\n-'\n-\n-test_expect_success '\"add --force\" with existing dwimd name doesnt die' '\n-\tgit checkout test-branch &&\n-\tgit worktree add --force test-branch\n-'\n-\n-test_expect_success '\"add\" no auto-vivify with --detach and <branch> omitted' '\n-\tgit worktree add --detach mish/mash &&\n-\ttest_must_fail git rev-parse mash -- &&\n-\ttest_must_fail git -C mish/mash symbolic-ref HEAD\n-'\n \n # Helper function to test mutually exclusive options.\n #\n # Note: Quoted arguments containing spaces are not supported.\n test_wt_add_excl () {\n-\tlocal opts=\"$*\" &&\n+\tlocal opts=\"$*\"\n+\n \ttest_expect_success \"'worktree add' with '$opts' has mutually exclusive options\" '\n \t\ttest_must_fail git worktree add $opts 2>actual &&\n \t\tgrep -E \"fatal:( options)? .* cannot be used together\" actual\n \t'\n }\n \n-test_wt_add_excl -b poodle -B poodle bamboo main\n-test_wt_add_excl -b poodle --detach bamboo main\n-test_wt_add_excl -B poodle --detach bamboo main\n-test_wt_add_excl --orphan --detach bamboo\n-test_wt_add_excl --orphan --no-checkout bamboo\n-test_wt_add_excl --orphan bamboo main\n-test_wt_add_excl --orphan -b bamboo wtdir/ main\n-\n-test_expect_success '\"add -B\" fails if the branch is checked out' '\n-\tgit rev-parse newmain >before &&\n-\ttest_must_fail git worktree add -B newmain bamboo main &&\n-\tgit rev-parse newmain >after &&\n-\ttest_cmp before after\n-'\n-\n-test_expect_success 'add -B' '\n-\tgit worktree add -B poodle bamboo2 main^ &&\n-\tgit -C bamboo2 symbolic-ref HEAD >actual &&\n-\techo refs/heads/poodle >expected &&\n-\ttest_cmp expected actual &&\n-\ttest_cmp_rev main^ poodle\n-'\n-\n-test_expect_success 'add --quiet' '\n-\ttest_when_finished \"git worktree remove -f -f another-worktree\" &&\n-\tgit worktree add --quiet another-worktree main 2>actual &&\n-\ttest_must_be_empty actual\n-'\n-\n-test_expect_success 'add --quiet -b' '\n-\ttest_when_finished \"git branch -D quietnewbranch\" &&\n-\ttest_when_finished \"git worktree remove -f -f another-worktree\" &&\n-\tgit worktree add --quiet -b quietnewbranch another-worktree 2>actual &&\n-\ttest_must_be_empty actual\n-'\n-\n-test_expect_success '\"add --orphan\"' '\n-\ttest_when_finished \"git worktree remove -f -f orphandir\" &&\n-\tgit worktree add --orphan -b neworphan orphandir &&\n-\techo refs/heads/neworphan >expected &&\n-\tgit -C orphandir symbolic-ref HEAD >actual &&\n-\ttest_cmp expected actual\n-'\n-\n-test_expect_success '\"add --orphan (no -b)\"' '\n-\ttest_when_finished \"git worktree remove -f -f neworphan\" &&\n-\tgit worktree add --orphan neworphan &&\n-\techo refs/heads/neworphan >expected &&\n-\tgit -C neworphan symbolic-ref HEAD >actual &&\n-\ttest_cmp expected actual\n-'\n-\n-test_expect_success '\"add --orphan --quiet\"' '\n-\ttest_when_finished \"git worktree remove -f -f orphandir\" &&\n-\tgit worktree add --quiet --orphan -b neworphan orphandir 2>log.actual &&\n-\ttest_must_be_empty log.actual &&\n-\techo refs/heads/neworphan >expected &&\n-\tgit -C orphandir symbolic-ref HEAD >actual &&\n-\ttest_cmp expected actual\n-'\n-\n-test_expect_success '\"add --orphan\" fails if the branch already exists' '\n-\ttest_when_finished \"git branch -D existingbranch\" &&\n-\tgit worktree add -b existingbranch orphandir main &&\n-\tgit worktree remove orphandir &&\n-\ttest_must_fail git worktree add --orphan -b existingbranch orphandir\n-'\n-\n-test_expect_success '\"add --orphan\" with empty repository' '\n-\ttest_when_finished \"rm -rf empty_repo\" &&\n-\techo refs/heads/newbranch >expected &&\n-\tGIT_DIR=\"empty_repo\" git init --bare &&\n-\tgit -C empty_repo worktree add --orphan -b newbranch worktreedir &&\n-\tgit -C empty_repo/worktreedir symbolic-ref HEAD >actual &&\n-\ttest_cmp expected actual\n-'\n-\n-test_expect_success '\"add\" worktree with orphan branch and lock' '\n-\tgit worktree add --lock --orphan -b orphanbr orphan-with-lock &&\n-\ttest_when_finished \"git worktree unlock orphan-with-lock || :\" &&\n-\ttest -f .git/worktrees/orphan-with-lock/locked\n-'\n-\n-test_expect_success '\"add\" worktree with orphan branch, lock, and reason' '\n-\tlock_reason=\"why not\" &&\n-\tgit worktree add --detach --lock --reason \"$lock_reason\" orphan-with-lock-reason main &&\n-\ttest_when_finished \"git worktree unlock orphan-with-lock-reason || :\" &&\n-\ttest -f .git/worktrees/orphan-with-lock-reason/locked &&\n-\techo \"$lock_reason\" >expect &&\n-\ttest_cmp expect .git/worktrees/orphan-with-lock-reason/locked\n-'\n-\n # Note: Quoted arguments containing spaces are not supported.\n test_wt_add_orphan_hint () {\n \tlocal context=\"$1\" &&\n@@ -449,100 +49,6 @@ test_wt_add_orphan_hint () {\n \t'\n }\n \n-test_wt_add_orphan_hint 'no opts' 0\n-test_wt_add_orphan_hint '-b' 1 -b foobar_branch\n-test_wt_add_orphan_hint '-B' 1 -B foobar_branch\n-\n-test_expect_success \"'worktree add' doesn't show orphan hint in bad/orphan HEAD w/ --quiet\" '\n-\ttest_when_finished \"rm -rf repo\" &&\n-\tgit init repo &&\n-\t(cd repo && test_commit commit) &&\n-\ttest_must_fail git -C repo worktree add --quiet foobar_branch foobar/ 2>actual &&\n-\t! grep \"error: unknown switch\" actual &&\n-\t! grep \"hint: If you meant to create a worktree containing a new unborn branch\" actual\n-'\n-\n-test_expect_success 'local clone from linked checkout' '\n-\tgit clone --local here here-clone &&\n-\t( cd here-clone && git fsck )\n-'\n-\n-test_expect_success 'local clone --shared from linked checkout' '\n-\tgit -C bare worktree add --detach ../baretree &&\n-\tgit clone --local --shared baretree bare-clone &&\n-\tgrep /bare/ bare-clone/.git/objects/info/alternates\n-'\n-\n-test_expect_success '\"add\" worktree with --no-checkout' '\n-\tgit worktree add --no-checkout -b swamp swamp &&\n-\t! test -e swamp/init.t &&\n-\tgit -C swamp reset --hard &&\n-\ttest_cmp init.t swamp/init.t\n-'\n-\n-test_expect_success '\"add\" worktree with --checkout' '\n-\tgit worktree add --checkout -b swmap2 swamp2 &&\n-\ttest_cmp init.t swamp2/init.t\n-'\n-\n-test_expect_success 'put a worktree under rebase' '\n-\tgit worktree add under-rebase &&\n-\t(\n-\t\tcd under-rebase &&\n-\t\tset_fake_editor &&\n-\t\tFAKE_LINES=\"edit 1\" git rebase -i HEAD^ &&\n-\t\tgit worktree list >actual &&\n-\t\tgrep \"under-rebase.*detached HEAD\" actual\n-\t)\n-'\n-\n-test_expect_success 'add a worktree, checking out a rebased branch' '\n-\ttest_must_fail git worktree add new-rebase under-rebase &&\n-\t! test -d new-rebase\n-'\n-\n-test_expect_success 'checking out a rebased branch from another worktree' '\n-\tgit worktree add new-place &&\n-\ttest_must_fail git -C new-place checkout under-rebase\n-'\n-\n-test_expect_success 'not allow to delete a branch under rebase' '\n-\t(\n-\t\tcd under-rebase &&\n-\t\ttest_must_fail git branch -D under-rebase\n-\t)\n-'\n-\n-test_expect_success 'rename a branch under rebase not allowed' '\n-\ttest_must_fail git branch -M under-rebase rebase-with-new-name\n-'\n-\n-test_expect_success 'check out from current worktree branch ok' '\n-\t(\n-\t\tcd under-rebase &&\n-\t\tgit checkout under-rebase &&\n-\t\tgit checkout - &&\n-\t\tgit rebase --abort\n-\t)\n-'\n-\n-test_expect_success 'checkout a branch under bisect' '\n-\tgit worktree add under-bisect &&\n-\t(\n-\t\tcd under-bisect &&\n-\t\tgit bisect start &&\n-\t\tgit bisect bad &&\n-\t\tgit bisect good HEAD~2 &&\n-\t\tgit worktree list >actual &&\n-\t\tgrep \"under-bisect.*detached HEAD\" actual &&\n-\t\ttest_must_fail git worktree add new-bisect under-bisect &&\n-\t\t! test -d new-bisect\n-\t)\n-'\n-\n-test_expect_success 'rename a branch under bisect not allowed' '\n-\ttest_must_fail git branch -M under-bisect bisect-with-new-name\n-'\n # Is branch \"refs/heads/$1\" set to pull from \"$2/$3\"?\n test_branch_upstream () {\n \tprintf \"%s\\n\" \"$2\" \"refs/heads/$3\" >expect.upstream &&\n@@ -553,12 +59,6 @@ test_branch_upstream () {\n \ttest_cmp expect.upstream actual.upstream\n }\n \n-test_expect_success '--track sets up tracking' '\n-\ttest_when_finished rm -rf track &&\n-\tgit worktree add --track -b track track main &&\n-\ttest_branch_upstream track . main\n-'\n-\n # setup remote repository $1 and repository $2 with $1 set up as\n # remote.  The remote has two branches, main and foo.\n setup_remote_repo () {\n@@ -580,157 +80,6 @@ setup_remote_repo () {\n \t)\n }\n \n-test_expect_success '\"add\" <path> <remote/branch> w/ no HEAD' '\n-\ttest_when_finished rm -rf repo_upstream repo_local foo &&\n-\tsetup_remote_repo repo_upstream repo_local &&\n-\tgit -C repo_local config --bool core.bare true &&\n-\tgit -C repo_local branch -D main &&\n-\tgit -C repo_local worktree add ./foo repo_upstream/foo\n-'\n-\n-test_expect_success '--no-track avoids setting up tracking' '\n-\ttest_when_finished rm -rf repo_upstream repo_local foo &&\n-\tsetup_remote_repo repo_upstream repo_local &&\n-\t(\n-\t\tcd repo_local &&\n-\t\tgit worktree add --no-track -b foo ../foo repo_upstream/foo\n-\t) &&\n-\t(\n-\t\tcd foo &&\n-\t\ttest_must_fail git config \"branch.foo.remote\" &&\n-\t\ttest_must_fail git config \"branch.foo.merge\" &&\n-\t\ttest_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo\n-\t)\n-'\n-\n-test_expect_success '\"add\" <path> <non-existent-branch> fails' '\n-\ttest_must_fail git worktree add foo non-existent\n-'\n-\n-test_expect_success '\"add\" <path> <branch> dwims' '\n-\ttest_when_finished rm -rf repo_upstream repo_dwim foo &&\n-\tsetup_remote_repo repo_upstream repo_dwim &&\n-\tgit init repo_dwim &&\n-\t(\n-\t\tcd repo_dwim &&\n-\t\tgit worktree add ../foo foo\n-\t) &&\n-\t(\n-\t\tcd foo &&\n-\t\ttest_branch_upstream foo repo_upstream foo &&\n-\t\ttest_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo\n-\t)\n-'\n-\n-test_expect_success '\"add\" <path> <branch> dwims with checkout.defaultRemote' '\n-\ttest_when_finished rm -rf repo_upstream repo_dwim foo &&\n-\tsetup_remote_repo repo_upstream repo_dwim &&\n-\tgit init repo_dwim &&\n-\t(\n-\t\tcd repo_dwim &&\n-\t\tgit remote add repo_upstream2 ../repo_upstream &&\n-\t\tgit fetch repo_upstream2 &&\n-\t\ttest_must_fail git worktree add ../foo foo &&\n-\t\tgit -c checkout.defaultRemote=repo_upstream worktree add ../foo foo &&\n-\t\tgit status -uno --porcelain >status.actual &&\n-\t\ttest_must_be_empty status.actual\n-\t) &&\n-\t(\n-\t\tcd foo &&\n-\t\ttest_branch_upstream foo repo_upstream foo &&\n-\t\ttest_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo\n-\t)\n-'\n-\n-test_expect_success 'git worktree add does not match remote' '\n-\ttest_when_finished rm -rf repo_a repo_b foo &&\n-\tsetup_remote_repo repo_a repo_b &&\n-\t(\n-\t\tcd repo_b &&\n-\t\tgit worktree add ../foo\n-\t) &&\n-\t(\n-\t\tcd foo &&\n-\t\ttest_must_fail git config \"branch.foo.remote\" &&\n-\t\ttest_must_fail git config \"branch.foo.merge\" &&\n-\t\ttest_cmp_rev ! refs/remotes/repo_a/foo refs/heads/foo\n-\t)\n-'\n-\n-test_expect_success 'git worktree add --guess-remote sets up tracking' '\n-\ttest_when_finished rm -rf repo_a repo_b foo &&\n-\tsetup_remote_repo repo_a repo_b &&\n-\t(\n-\t\tcd repo_b &&\n-\t\tgit worktree add --guess-remote ../foo\n-\t) &&\n-\t(\n-\t\tcd foo &&\n-\t\ttest_branch_upstream foo repo_a foo &&\n-\t\ttest_cmp_rev refs/remotes/repo_a/foo refs/heads/foo\n-\t)\n-'\n-test_expect_success 'git worktree add --guess-remote sets up tracking (quiet)' '\n-\ttest_when_finished rm -rf repo_a repo_b foo &&\n-\tsetup_remote_repo repo_a repo_b &&\n-\t(\n-\t\tcd repo_b &&\n-\t\tgit worktree add --quiet --guess-remote ../foo 2>actual &&\n-\t\ttest_must_be_empty actual\n-\t) &&\n-\t(\n-\t\tcd foo &&\n-\t\ttest_branch_upstream foo repo_a foo &&\n-\t\ttest_cmp_rev refs/remotes/repo_a/foo refs/heads/foo\n-\t)\n-'\n-\n-test_expect_success 'git worktree --no-guess-remote (quiet)' '\n-\ttest_when_finished rm -rf repo_a repo_b foo &&\n-\tsetup_remote_repo repo_a repo_b &&\n-\t(\n-\t\tcd repo_b &&\n-\t\tgit worktree add --quiet --no-guess-remote ../foo\n-\t) &&\n-\t(\n-\t\tcd foo &&\n-\t\ttest_must_fail git config \"branch.foo.remote\" &&\n-\t\ttest_must_fail git config \"branch.foo.merge\" &&\n-\t\ttest_cmp_rev ! refs/remotes/repo_a/foo refs/heads/foo\n-\t)\n-'\n-\n-test_expect_success 'git worktree add with worktree.guessRemote sets up tracking' '\n-\ttest_when_finished rm -rf repo_a repo_b foo &&\n-\tsetup_remote_repo repo_a repo_b &&\n-\t(\n-\t\tcd repo_b &&\n-\t\tgit config worktree.guessRemote true &&\n-\t\tgit worktree add ../foo\n-\t) &&\n-\t(\n-\t\tcd foo &&\n-\t\ttest_branch_upstream foo repo_a foo &&\n-\t\ttest_cmp_rev refs/remotes/repo_a/foo refs/heads/foo\n-\t)\n-'\n-\n-test_expect_success 'git worktree --no-guess-remote option overrides config' '\n-\ttest_when_finished rm -rf repo_a repo_b foo &&\n-\tsetup_remote_repo repo_a repo_b &&\n-\t(\n-\t\tcd repo_b &&\n-\t\tgit config worktree.guessRemote true &&\n-\t\tgit worktree add --no-guess-remote ../foo\n-\t) &&\n-\t(\n-\t\tcd foo &&\n-\t\ttest_must_fail git config \"branch.foo.remote\" &&\n-\t\ttest_must_fail git config \"branch.foo.merge\" &&\n-\t\ttest_cmp_rev ! refs/remotes/repo_a/foo refs/heads/foo\n-\t)\n-'\n-\n test_dwim_orphan () {\n \tlocal info_text=\"No possible source branch, inferring '--orphan'\" &&\n \tlocal fetch_error_text=\"fatal: No local or remote refs exist despite at least one remote\" &&\n@@ -751,10 +100,11 @@ test_dwim_orphan () {\n \tlocal use_detach=0 &&\n \tlocal use_new_branch=0 &&\n \n-\tlocal outcome=\"$1\" &&\n+\tlocal use_relative_paths=\"$1\" &&\n+\tlocal outcome=\"$2\" &&\n \tlocal outcome_text &&\n \tlocal success &&\n-\tshift &&\n+\tshift 2 &&\n \tlocal args=\"\" &&\n \tlocal context=\"\" &&\n \tcase \"$outcome\" in\n@@ -791,25 +141,25 @@ test_dwim_orphan () {\n \t\t\tuse_cd=0 &&\n \t\t\tgit_ns=\"repo\" &&\n \t\t\tdashc_args=\"-C $git_ns\" &&\n-\t\t\tcontext=\"$context, 'git -C repo'\"\n+\t\t\tcontext=\"$context, 'git -C $git_ns'\"\n \t\t\t;;\n \t\t\"-C_wt\")\n \t\t\tuse_cd=0 &&\n \t\t\tgit_ns=\"wt\" &&\n \t\t\tdashc_args=\"-C $git_ns\" &&\n-\t\t\tcontext=\"$context, 'git -C wt'\"\n+\t\t\tcontext=\"$context, 'git -C $git_ns'\"\n \t\t\t;;\n \t\t\"cd_repo\")\n \t\t\tuse_cd=1 &&\n \t\t\tgit_ns=\"repo\" &&\n \t\t\tdashc_args=\"\" &&\n-\t\t\tcontext=\"$context, 'cd repo && git'\"\n+\t\t\tcontext=\"$context, 'cd $git_ns && git'\"\n \t\t\t;;\n \t\t\"cd_wt\")\n \t\t\tuse_cd=1 &&\n \t\t\tgit_ns=\"wt\" &&\n \t\t\tdashc_args=\"\" &&\n-\t\t\tcontext=\"$context, 'cd wt && git'\"\n+\t\t\tcontext=\"$context, 'cd $git_ns && git'\"\n \t\t\t;;\n \n \t\t# Bypass the \"pull first\" warning\n@@ -948,6 +298,7 @@ test_dwim_orphan () {\n \t\tthen\n \t\t\ttest_when_finished \"git -C repo worktree remove -f ../wt\" &&\n \t\t\tgit -C repo worktree add --orphan -b main ../wt &&\n+\t\t\tcheck_worktree_paths \"$use_relative_paths\" wt &&\n \t\t\t(cd wt && test_commit commit) &&\n \t\t\tif [ $bad_head -eq 1 ]\n \t\t\tthen\n@@ -956,7 +307,8 @@ test_dwim_orphan () {\n \t\telif [ $local_ref -eq 0 ] && [ \"$git_ns\" = \"wt\" ]\n \t\tthen\n \t\t\ttest_when_finished \"git -C repo worktree remove -f ../wt\" &&\n-\t\t\tgit -C repo worktree add --orphan -b orphanbranch ../wt\n+\t\t\tgit -C repo worktree add --orphan -b orphanbranch ../wt &&\n+\t\t\tcheck_worktree_paths \"$use_relative_paths\" wt\n \t\tfi &&\n \n \t\tif [ $remote -eq 1 ]\n@@ -977,13 +329,18 @@ test_dwim_orphan () {\n \t\t\ttest_when_finished git -C repo worktree remove ../foo\n \t\tfi &&\n \t\t(\n+\t\t\tlocal wt_prefix &&\n \t\t\tif [ $use_cd -eq 1 ]\n \t\t\tthen\n-\t\t\t\tcd $git_ns\n+\t\t\t\tcd $git_ns &&\n+\t\t\t\twt_prefix=\"..\"\n+\t\t\telse\n+\t\t\t\twt_prefix=\".\"\n \t\t\tfi &&\n \t\t\tif [ \"$outcome\" = \"infer\" ]\n \t\t\tthen\n \t\t\t\tgit $dashc_args worktree add $args 2>actual &&\n+\t\t\t\tcheck_worktree_paths \"$use_relative_paths\" \"$wt_prefix/foo\" &&\n \t\t\t\tif [ $use_quiet -eq 1 ]\n \t\t\t\tthen\n \t\t\t\t\ttest_must_be_empty actual\n@@ -993,6 +350,7 @@ test_dwim_orphan () {\n \t\t\telif [ \"$outcome\" = \"no_infer\" ]\n \t\t\tthen\n \t\t\t\tgit $dashc_args worktree add $args 2>actual &&\n+\t\t\t\tcheck_worktree_paths \"$use_relative_paths\" \"$wt_prefix/foo\" &&\n \t\t\t\tif [ $use_quiet -eq 1 ]\n \t\t\t\tthen\n \t\t\t\t\ttest_must_be_empty actual\n@@ -1042,169 +400,906 @@ test_dwim_orphan () {\n \t'\n }\n \n-for quiet_mode in \"no_quiet\" \"quiet\"\n-do\n-\tfor changedir_type in \"cd_repo\" \"cd_wt\" \"-C_repo\" \"-C_wt\"\n-\tdo\n-\t\tdwim_test_args=\"$quiet_mode $changedir_type\"\n-\t\ttest_dwim_orphan 'infer' $dwim_test_args no_-b\n-\t\ttest_dwim_orphan 'no_infer' $dwim_test_args no_-b local_ref good_head\n-\t\ttest_dwim_orphan 'infer' $dwim_test_args no_-b no_local_ref no_remote no_remote_ref no_guess_remote\n-\t\ttest_dwim_orphan 'infer' $dwim_test_args no_-b no_local_ref remote no_remote_ref no_guess_remote\n-\t\ttest_dwim_orphan 'fetch_error' $dwim_test_args no_-b no_local_ref remote no_remote_ref guess_remote\n-\t\ttest_dwim_orphan 'infer' $dwim_test_args no_-b no_local_ref remote no_remote_ref guess_remote force\n-\t\ttest_dwim_orphan 'no_infer' $dwim_test_args no_-b no_local_ref remote remote_ref guess_remote\n-\n-\t\ttest_dwim_orphan 'infer' $dwim_test_args -b\n-\t\ttest_dwim_orphan 'no_infer' $dwim_test_args -b local_ref good_head\n-\t\ttest_dwim_orphan 'infer' $dwim_test_args -b no_local_ref no_remote no_remote_ref no_guess_remote\n-\t\ttest_dwim_orphan 'infer' $dwim_test_args -b no_local_ref remote no_remote_ref no_guess_remote\n-\t\ttest_dwim_orphan 'infer' $dwim_test_args -b no_local_ref remote no_remote_ref guess_remote\n-\t\ttest_dwim_orphan 'infer' $dwim_test_args -b no_local_ref remote remote_ref guess_remote\n-\n-\t\ttest_dwim_orphan 'warn_bad_head' $dwim_test_args no_-b local_ref bad_head\n-\t\ttest_dwim_orphan 'warn_bad_head' $dwim_test_args -b local_ref bad_head\n-\t\ttest_dwim_orphan 'warn_bad_head' $dwim_test_args detach local_ref bad_head\n-\tdone\n-\n-\ttest_dwim_orphan 'fatal_orphan_bad_combo' $quiet_mode no_-b no_checkout\n-\ttest_dwim_orphan 'fatal_orphan_bad_combo' $quiet_mode no_-b track\n-\ttest_dwim_orphan 'fatal_orphan_bad_combo' $quiet_mode -b no_checkout\n-\ttest_dwim_orphan 'fatal_orphan_bad_combo' $quiet_mode -b track\n-done\n-\n post_checkout_hook () {\n \ttest_when_finished \"rm -rf .git/hooks\" &&\n \tmkdir .git/hooks &&\n-\ttest_hook -C \"$1\" post-checkout <<-\\EOF\n+\ttest_hook -C \"$1\" post-checkout <<-'EOF'\n \t{\n \t\techo $*\n \t\tgit rev-parse --git-dir --show-toplevel\n \t} >hook.actual\n-\tEOF\n+EOF\n }\n \n-test_expect_success '\"add\" invokes post-checkout hook (branch)' '\n-\tpost_checkout_hook &&\n-\t{\n-\t\techo $ZERO_OID $(git rev-parse HEAD) 1 &&\n-\t\techo $(pwd)/.git/worktrees/gumby &&\n-\t\techo $(pwd)/gumby\n-\t} >hook.expect &&\n-\tgit worktree add gumby &&\n-\ttest_cmp hook.expect gumby/hook.actual\n-'\n-\n-test_expect_success '\"add\" invokes post-checkout hook (detached)' '\n-\tpost_checkout_hook &&\n-\t{\n-\t\techo $ZERO_OID $(git rev-parse HEAD) 1 &&\n-\t\techo $(pwd)/.git/worktrees/grumpy &&\n-\t\techo $(pwd)/grumpy\n-\t} >hook.expect &&\n-\tgit worktree add --detach grumpy &&\n-\ttest_cmp hook.expect grumpy/hook.actual\n-'\n-\n-test_expect_success '\"add --no-checkout\" suppresses post-checkout hook' '\n-\tpost_checkout_hook &&\n-\trm -f hook.actual &&\n-\tgit worktree add --no-checkout gloopy &&\n-\ttest_path_is_missing gloopy/hook.actual\n-'\n-\n-test_expect_success '\"add\" in other worktree invokes post-checkout hook' '\n-\tpost_checkout_hook &&\n-\t{\n-\t\techo $ZERO_OID $(git rev-parse HEAD) 1 &&\n-\t\techo $(pwd)/.git/worktrees/guppy &&\n-\t\techo $(pwd)/guppy\n-\t} >hook.expect &&\n-\tgit -C gloopy worktree add --detach ../guppy &&\n-\ttest_cmp hook.expect guppy/hook.actual\n-'\n-\n-test_expect_success '\"add\" in bare repo invokes post-checkout hook' '\n-\trm -rf bare &&\n-\tgit clone --bare . bare &&\n-\t{\n-\t\techo $ZERO_OID $(git --git-dir=bare rev-parse HEAD) 1 &&\n-\t\techo $(pwd)/bare/worktrees/goozy &&\n-\t\techo $(pwd)/goozy\n-\t} >hook.expect &&\n-\tpost_checkout_hook bare &&\n-\tgit -C bare worktree add --detach ../goozy &&\n-\ttest_cmp hook.expect goozy/hook.actual\n-'\n-\n-test_expect_success '\"add\" an existing but missing worktree' '\n-\tgit worktree add --detach pneu &&\n-\ttest_must_fail git worktree add --detach pneu &&\n-\trm -fr pneu &&\n-\ttest_must_fail git worktree add --detach pneu &&\n-\tgit worktree add --force --detach pneu\n-'\n-\n-test_expect_success '\"add\" an existing locked but missing worktree' '\n-\tgit worktree add --detach gnoo &&\n-\tgit worktree lock gnoo &&\n-\ttest_when_finished \"git worktree unlock gnoo || :\" &&\n-\trm -fr gnoo &&\n-\ttest_must_fail git worktree add --detach gnoo &&\n-\ttest_must_fail git worktree add --force --detach gnoo &&\n-\tgit worktree add --force --force --detach gnoo\n-'\n-\n-test_expect_success '\"add\" not tripped up by magic worktree matching\"' '\n-\t# if worktree \"sub1/bar\" exists, \"git worktree add bar\" in distinct\n-\t# directory `sub2` should not mistakenly complain that `bar` is an\n-\t# already-registered worktree\n-\tmkdir sub1 sub2 &&\n-\tgit -C sub1 --git-dir=../.git worktree add --detach bozo &&\n-\tgit -C sub2 --git-dir=../.git worktree add --detach bozo\n-'\n-\n-test_expect_success FUNNYNAMES 'sanitize generated worktree name' '\n-\tgit worktree add --detach \".  weird*..?.lock.lock\" &&\n-\ttest -d .git/worktrees/---weird-.-\n-'\n-\n-test_expect_success '\"add\" should not fail because of another bad worktree' '\n-\tgit init add-fail &&\n-\t(\n-\t\tcd add-fail &&\n-\t\ttest_commit first &&\n-\t\tmkdir sub &&\n-\t\tgit worktree add sub/to-be-deleted &&\n-\t\trm -rf sub &&\n-\t\tgit worktree add second\n-\t)\n-'\n-\n-test_expect_success '\"add\" with uninitialized submodule, with submodule.recurse unset' '\n-\ttest_config_global protocol.file.allow always &&\n-\ttest_create_repo submodule &&\n-\ttest_commit -C submodule first &&\n-\ttest_create_repo project &&\n-\tgit -C project submodule add ../submodule &&\n-\tgit -C project add submodule &&\n-\ttest_tick &&\n-\tgit -C project commit -m add_sub &&\n-\tgit clone project project-clone &&\n-\tgit -C project-clone worktree add ../project-2\n-'\n-test_expect_success '\"add\" with uninitialized submodule, with submodule.recurse set' '\n-\tgit -C project-clone -c submodule.recurse worktree add ../project-3\n-'\n-\n-test_expect_success '\"add\" with initialized submodule, with submodule.recurse unset' '\n-\ttest_config_global protocol.file.allow always &&\n-\tgit -C project-clone submodule update --init &&\n-\tgit -C project-clone worktree add ../project-4\n-'\n-\n-test_expect_success '\"add\" with initialized submodule, with submodule.recurse set' '\n-\tgit -C project-clone -c submodule.recurse worktree add ../project-5\n-'\n+run_worktree_add_tests() {\n+\tlocal use_relative_paths=\"$1\"\n+\n+\t# Set the Git config variable based on the parameter\n+\ttest_config_global worktree.userelativepaths \"$use_relative_paths\"\n+\n+\ttest_expect_success 'setup' '\n+\t\ttest_commit init\n+\t'\n+\n+\ttest_expect_success '\"add\" an existing worktree' '\n+\t\tmkdir -p existing/subtree &&\n+\t\ttest_must_fail git worktree add --detach existing main\n+\t'\n+\n+\ttest_expect_success '\"add\" an existing empty worktree' '\n+\t\tmkdir existing_empty &&\n+\t\tgit worktree add --detach existing_empty main &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" existing_empty\n+\t'\n+\n+\ttest_expect_success '\"add\" using shorthand - fails when no previous branch' '\n+\t\ttest_must_fail git worktree add existing_short -\n+\t'\n+\n+\ttest_expect_success '\"add\" using - shorthand' '\n+\t\tgit checkout -b newbranch &&\n+\t\techo hello >myworld &&\n+\t\tgit add myworld &&\n+\t\tgit commit -m myworld &&\n+\t\tgit checkout main &&\n+\t\tgit worktree add short-hand - &&\n+\t\techo refs/heads/newbranch >expect &&\n+\t\tgit -C short-hand rev-parse --symbolic-full-name HEAD >actual &&\n+\t\ttest_cmp expect actual &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" short-hand\n+\t'\n+\n+\ttest_expect_success '\"add\" refuses to checkout locked branch' '\n+\t\ttest_must_fail git worktree add zere main &&\n+\t\t! test -d zere &&\n+\t\t! test -d .git/worktrees/zere\n+\t'\n+\n+\ttest_expect_success 'checking out paths not complaining about linked checkouts' '\n+\t\t(\n+\t\tcd existing_empty &&\n+\t\techo dirty >>init.t &&\n+\t\tgit checkout main -- init.t\n+\t\t)\n+\t'\n+\n+\ttest_expect_success '\"add\" worktree' '\n+\t\tgit rev-parse HEAD >expect &&\n+\t\tgit worktree add --detach here main &&\n+\t\t(\n+\t\t\tcd here &&\n+\t\t\ttest_cmp ../init.t init.t &&\n+\t\t\ttest_must_fail git symbolic-ref HEAD &&\n+\t\t\tgit rev-parse HEAD >actual &&\n+\t\t\ttest_cmp ../expect actual &&\n+\t\t\tgit fsck\n+\t\t) &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" here\n+\t'\n+\n+\ttest_expect_success '\"add\" worktree with lock' '\n+\t\tgit worktree add --detach --lock here-with-lock main &&\n+\t\ttest_when_finished \"git worktree unlock here-with-lock || :\" &&\n+\t\ttest -f .git/worktrees/here-with-lock/locked &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" here-with-lock\n+\t'\n+\n+\ttest_expect_success '\"add\" worktree with lock and reason' '\n+\t\tlock_reason=\"why not\" &&\n+\t\tgit worktree add --detach --lock --reason \"$lock_reason\" here-with-lock-reason main &&\n+\t\ttest_when_finished \"git worktree unlock here-with-lock-reason || :\" &&\n+\t\ttest -f .git/worktrees/here-with-lock-reason/locked &&\n+\t\techo \"$lock_reason\" >expect &&\n+\t\ttest_cmp expect .git/worktrees/here-with-lock-reason/locked &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" here-with-lock-reason\n+\t'\n+\n+\ttest_expect_success '\"add\" worktree with reason but no lock' '\n+\t\ttest_must_fail git worktree add --detach --reason \"why not\" here-with-reason-only main &&\n+\t\ttest_path_is_missing .git/worktrees/here-with-reason-only/locked\n+\t'\n+\n+\ttest_expect_success '\"add\" worktree from a subdir' '\n+\t\t(\n+\t\t\tmkdir sub &&\n+\t\t\tcd sub &&\n+\t\t\tgit worktree add --detach here main &&\n+\t\t\tcd here &&\n+\t\t\ttest_cmp ../../init.t init.t &&\n+\t\t\tcheck_worktree_paths \"$use_relative_paths\" .\n+\t\t)\n+\t'\n+\n+\ttest_expect_success '\"add\" from a linked checkout' '\n+\t\t(\n+\t\t\tcd here &&\n+\t\t\tgit worktree add --detach nested-here main &&\n+\t\t\tcd nested-here &&\n+\t\t\tgit fsck &&\n+\t\t\tcheck_worktree_paths \"$use_relative_paths\" .\n+\t\t)\n+\t'\n+\n+\ttest_expect_success '\"add\" worktree creating new branch' '\n+\t\tgit worktree add -b newmain there main &&\n+\t\t(\n+\t\t\tcd there &&\n+\t\t\ttest_cmp ../init.t init.t &&\n+\t\t\tgit symbolic-ref HEAD >actual &&\n+\t\t\techo refs/heads/newmain >expect &&\n+\t\t\ttest_cmp expect actual &&\n+\t\t\tgit fsck &&\n+\t\t\tcheck_worktree_paths \"$use_relative_paths\" .\n+\t\t)\n+\t'\n+\n+\ttest_expect_success 'die the same branch is already checked out' '\n+\t\t(\n+\t\t\tcd here &&\n+\t\t\ttest_must_fail git checkout newmain 2>actual &&\n+\t\t\tgrep \"already used by worktree at\" actual\n+\t\t)\n+\t'\n+\n+\ttest_expect_success 'refuse to reset a branch in use elsewhere' '\n+\t\t(\n+\t\t\tcd here &&\n+\n+\t\t\t# we know we are on detached HEAD but just in case ...\n+\t\t\tgit checkout --detach HEAD &&\n+\t\t\tgit rev-parse --verify HEAD >old.head &&\n+\n+\t\t\tgit rev-parse --verify refs/heads/newmain >old.branch &&\n+\t\t\ttest_must_fail git checkout -B newmain 2>error &&\n+\t\t\tgit rev-parse --verify refs/heads/newmain >new.branch &&\n+\t\t\tgit rev-parse --verify HEAD >new.head &&\n+\n+\t\t\tgrep \"already used by worktree at\" error &&\n+\t\t\ttest_cmp old.branch new.branch &&\n+\t\t\ttest_cmp old.head new.head &&\n+\n+\t\t\t# and we must be still on the same detached HEAD state\n+\t\t\ttest_must_fail git symbolic-ref HEAD\n+\t\t)\n+\t'\n+\n+\ttest_expect_success SYMLINKS 'die the same branch is already checked out (symlink)' '\n+\t\thead=$(git -C there rev-parse --git-path HEAD) &&\n+\t\tref=$(git -C there symbolic-ref HEAD) &&\n+\t\trm \"$head\" &&\n+\t\tln -s \"$ref\" \"$head\" &&\n+\t\ttest_must_fail git -C here checkout newmain\n+\t'\n+\n+\ttest_expect_success 'not die the same branch is already checked out' '\n+\t\t(\n+\t\t\tcd here &&\n+\t\t\tgit worktree add --force anothernewmain newmain &&\n+\t\t\tcheck_worktree_paths \"$use_relative_paths\" anothernewmain\n+\t\t)\n+\t'\n+\n+\ttest_expect_success 'not die on re-checking out current branch' '\n+\t\t(\n+\t\t\tcd there &&\n+\t\t\tgit checkout newmain\n+\t\t)\n+\t'\n+\n+\ttest_expect_success '\"add\" from a bare repo' '\n+\t\t(\n+\t\t\tgit clone --bare . bare &&\n+\t\t\tcd bare &&\n+\t\t\tgit worktree add -b bare-main ../there2 main &&\n+\t\t\tcheck_worktree_paths \"$use_relative_paths\" ../there2\n+\t\t)\n+\t'\n+\n+\ttest_expect_success 'checkout from a bare repo without \"add\"' '\n+\t\t(\n+\t\t\tcd bare &&\n+\t\t\ttest_must_fail git checkout main\n+\t\t)\n+\t'\n+\n+\ttest_expect_success '\"add\" default branch of a bare repo' '\n+\t\t(\n+\t\t\tgit clone --bare . bare2 &&\n+\t\t\tcd bare2 &&\n+\t\t\tgit worktree add ../there3 main &&\n+\t\t\tcheck_worktree_paths \"$use_relative_paths\" ../there3 &&\n+\t\t\tcd ../there3 &&\n+\t\t\t# Simple check that a Git command does not\n+\t\t\t# immediately fail with the current setup\n+\t\t\tgit status\n+\t\t) &&\n+\t\tcat >expect <<-EOF &&\n+\t\tinit.t\n+\t\tEOF\n+\t\tls there3 >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success '\"add\" to bare repo with worktree config' '\n+\t\t(\n+\t\t\tgit clone --bare . bare3 &&\n+\t\t\tcd bare3 &&\n+\t\t\tgit config extensions.worktreeconfig true &&\n+\n+\t\t\t# Add config values that are erroneous to have in\n+\t\t\t# a config.worktree file outside of the main\n+\t\t\t# working tree, to check that Git filters them out\n+\t\t\t# when copying config during \"git worktree add\".\n+\t\t\tgit config --worktree core.bare true &&\n+\t\t\tgit config --worktree core.worktree \"$(pwd)\" &&\n+\n+\t\t\t# We want to check that bogus.key is copied\n+\t\t\tgit config --worktree bogus.key value &&\n+\t\t\tgit config --unset core.bare &&\n+\t\t\tgit worktree add ../there4 main &&\n+\t\t\tcheck_worktree_paths \"$use_relative_paths\" ../there4 &&\n+\t\t\tcd ../there4 &&\n+\n+\t\t\t# Simple check that a Git command does not\n+\t\t\t# immediately fail with the current setup\n+\t\t\tgit status &&\n+\t\t\tgit worktree add --detach ../there5 &&\n+\t\t\tcheck_worktree_paths \"$use_relative_paths\" ../there5 &&\n+\t\t\tcd ../there5 &&\n+\t\t\tgit status\n+\t\t) &&\n+\n+\t\t# the worktree has the arbitrary value copied.\n+\t\ttest_cmp_config -C there4 value bogus.key &&\n+\t\ttest_cmp_config -C there5 value bogus.key &&\n+\n+\t\t# however, core.bare and core.worktree were removed.\n+\t\ttest_must_fail git -C there4 config core.bare &&\n+\t\ttest_must_fail git -C there4 config core.worktree &&\n+\n+\t\tcat >expect <<-EOF &&\n+\t\tinit.t\n+\t\tEOF\n+\n+\t\tls there4 >actual &&\n+\t\ttest_cmp expect actual &&\n+\t\tls there5 >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success 'checkout with grafts' '\n+\t\ttest_when_finished rm .git/info/grafts &&\n+\t\ttest_commit abc &&\n+\t\tSHA1=$(git rev-parse HEAD) &&\n+\t\ttest_commit def &&\n+\t\ttest_commit xyz &&\n+\t\tmkdir .git/info &&\n+\t\techo \"$(git rev-parse HEAD) $SHA1\" >.git/info/grafts &&\n+\t\tcat >expected <<-\\EOF &&\n+\t\txyz\n+\t\tabc\n+\t\tEOF\n+\t\tgit log --format=%s -2 >actual &&\n+\t\ttest_cmp expected actual &&\n+\t\tgit worktree add --detach grafted main &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" grafted &&\n+\t\tgit --git-dir=grafted/.git log --format=%s -2 >actual &&\n+\t\ttest_cmp expected actual\n+\t'\n+\n+\ttest_expect_success '\"add\" from relative HEAD' '\n+\t\ttest_commit a &&\n+\t\ttest_commit b &&\n+\t\ttest_commit c &&\n+\t\tgit rev-parse HEAD~1 >expected &&\n+\t\tgit worktree add relhead HEAD~1 &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" relhead &&\n+\t\tgit -C relhead rev-parse HEAD >actual &&\n+\t\ttest_cmp expected actual\n+\t'\n+\n+\ttest_expect_success '\"add -b\" with <branch> omitted' '\n+\t\tgit worktree add -b burble flornk &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" flornk &&\n+\t\ttest_cmp_rev HEAD burble\n+\t'\n+\n+\ttest_expect_success '\"add --detach\" with <branch> omitted' '\n+\t\tgit worktree add --detach fishhook &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" fishhook &&\n+\t\tgit rev-parse HEAD >expected &&\n+\t\tgit -C fishhook rev-parse HEAD >actual &&\n+\t\ttest_cmp expected actual &&\n+\t\ttest_must_fail git -C fishhook symbolic-ref HEAD\n+\t'\n+\n+\ttest_expect_success '\"add\" with <branch> omitted' '\n+\t\tgit worktree add wiffle/bat &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" wiffle/bat &&\n+\t\ttest_cmp_rev HEAD bat\n+\t'\n+\n+\ttest_expect_success '\"add\" checks out existing branch of dwimd name' '\n+\t\tgit branch dwim HEAD~1 &&\n+\t\tgit worktree add dwim &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" dwim &&\n+\t\ttest_cmp_rev HEAD~1 dwim &&\n+\t\t(\n+\t\t\tcd dwim &&\n+\t\t\ttest_cmp_rev HEAD dwim\n+\t\t)\n+\t'\n+\n+\ttest_expect_success '\"add <path>\" dwim fails with checked out branch' '\n+\t\tgit checkout -b test-branch &&\n+\t\ttest_must_fail git worktree add test-branch &&\n+\t\ttest_path_is_missing test-branch\n+\t'\n+\n+\ttest_expect_success '\"add --force\" with existing dwimd name doesnt die' '\n+\t\tgit checkout test-branch &&\n+\t\tgit worktree add --force test-branch\n+\t'\n+\n+\ttest_expect_success '\"add\" no auto-vivify with --detach and <branch> omitted' '\n+\t\tgit worktree add --detach mish/mash &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" mish/mash &&\n+\t\ttest_must_fail git rev-parse mash -- &&\n+\t\ttest_must_fail git -C mish/mash symbolic-ref HEAD\n+\t'\n+\n+\ttest_expect_success '\"add -B\" fails if the branch is checked out' '\n+\t\tgit rev-parse newmain >before &&\n+\t\ttest_must_fail git worktree add -B newmain bamboo main &&\n+\t\tgit rev-parse newmain >after &&\n+\t\ttest_cmp before after\n+\t'\n+\n+\ttest_expect_success 'add -B' '\n+\t\tgit worktree add -B poodle bamboo2 main^ &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" bamboo2 &&\n+\t\tgit -C bamboo2 symbolic-ref HEAD >actual &&\n+\t\techo refs/heads/poodle >expected &&\n+\t\ttest_cmp expected actual &&\n+\t\ttest_cmp_rev main^ poodle\n+\t'\n+\n+\ttest_expect_success 'add --quiet' '\n+\t\ttest_when_finished \"git worktree remove -f -f another-worktree\" &&\n+\t\tgit worktree add --quiet another-worktree main 2>actual &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" another-worktree &&\n+\t\ttest_must_be_empty actual\n+\t'\n+\n+\ttest_expect_success 'add --quiet -b' '\n+\t\ttest_when_finished \"git branch -D quietnewbranch\" &&\n+\t\ttest_when_finished \"git worktree remove -f -f another-worktree\" &&\n+\t\tgit worktree add --quiet -b quietnewbranch another-worktree 2>actual &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" another-worktree &&\n+\t\ttest_must_be_empty actual\n+\t'\n+\n+\ttest_expect_success '\"add --orphan\"' '\n+\t\ttest_when_finished \"git worktree remove -f -f orphandir\" &&\n+\t\tgit worktree add --orphan -b neworphan orphandir &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" orphandir &&\n+\t\techo refs/heads/neworphan >expected &&\n+\t\tgit -C orphandir symbolic-ref HEAD >actual &&\n+\t\ttest_cmp expected actual\n+\t'\n+\n+\ttest_expect_success '\"add --orphan (no -b)\"' '\n+\t\ttest_when_finished \"git worktree remove -f -f neworphan\" &&\n+\t\tgit worktree add --orphan neworphan &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" neworphan &&\n+\t\techo refs/heads/neworphan >expected &&\n+\t\tgit -C neworphan symbolic-ref HEAD >actual &&\n+\t\ttest_cmp expected actual\n+\t'\n+\n+\ttest_expect_success '\"add --orphan --quiet\"' '\n+\t\ttest_when_finished \"git worktree remove -f -f orphandir\" &&\n+\t\tgit worktree add --quiet --orphan -b neworphan orphandir 2>log.actual &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" orphandir &&\n+\t\ttest_must_be_empty log.actual &&\n+\t\techo refs/heads/neworphan >expected &&\n+\t\tgit -C orphandir symbolic-ref HEAD >actual &&\n+\t\ttest_cmp expected actual\n+\t'\n+\n+\ttest_expect_success '\"add --orphan\" fails if the branch already exists' '\n+\t\ttest_when_finished \"git branch -D existingbranch\" &&\n+\t\tgit worktree add -b existingbranch orphandir main &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" orphandir &&\n+\t\tgit worktree remove orphandir &&\n+\t\ttest_must_fail git worktree add --orphan -b existingbranch orphandir\n+\t'\n+\n+\ttest_expect_success '\"add --orphan\" with empty repository' '\n+\t\ttest_when_finished \"rm -rf empty_repo\" &&\n+\t\techo refs/heads/newbranch >expected &&\n+\t\tGIT_DIR=\"empty_repo\" git init --bare &&\n+\t\tgit -C empty_repo worktree add --orphan -b newbranch worktreedir &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" empty_repo/worktreedir &&\n+\t\tgit -C empty_repo/worktreedir symbolic-ref HEAD >actual &&\n+\t\ttest_cmp expected actual\n+\t'\n+\n+\ttest_expect_success '\"add\" worktree with orphan branch and lock' '\n+\t\tgit worktree add --lock --orphan -b orphanbr orphan-with-lock &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" orphan-with-lock &&\n+\t\ttest_when_finished \"git worktree unlock orphan-with-lock || :\" &&\n+\t\ttest -f .git/worktrees/orphan-with-lock/locked\n+\t'\n+\n+\ttest_expect_success '\"add\" worktree with orphan branch, lock, and reason' '\n+\t\tlock_reason=\"why not\" &&\n+\t\tgit worktree add --detach --lock --reason \"$lock_reason\" orphan-with-lock-reason main &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" orphan-with-lock-reason &&\n+\t\ttest_when_finished \"git worktree unlock orphan-with-lock-reason || :\" &&\n+\t\ttest -f .git/worktrees/orphan-with-lock-reason/locked &&\n+\t\techo \"$lock_reason\" >expect &&\n+\t\ttest_cmp expect .git/worktrees/orphan-with-lock-reason/locked\n+\t'\n+\n+\ttest_expect_success \"'worktree add' doesn't show orphan hint in bad/orphan HEAD w/ --quiet\" '\n+\t\ttest_when_finished \"rm -rf repo\" &&\n+\t\tgit init repo &&\n+\t\t(cd repo && test_commit commit) &&\n+\t\ttest_must_fail git -C repo worktree add --quiet foobar_branch foobar/ 2>actual &&\n+\t\t! grep \"error: unknown switch\" actual &&\n+\t\t! grep \"hint: If you meant to create a worktree containing a new unborn branch\" actual\n+\t'\n+\n+\ttest_expect_success 'local clone from linked checkout' '\n+\t\tgit clone --local here here-clone &&\n+\t\t( cd here-clone && git fsck )\n+\t'\n+\n+\ttest_expect_success 'local clone --shared from linked checkout' '\n+\t\tgit -C bare worktree add --detach ../baretree &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" baretree &&\n+\t\tgit clone --local --shared baretree bare-clone &&\n+\t\tgrep /bare/ bare-clone/.git/objects/info/alternates\n+\t'\n+\n+\ttest_expect_success '\"add\" worktree with --no-checkout' '\n+\t\tgit worktree add --no-checkout -b swamp swamp &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" swamp &&\n+\t\t! test -e swamp/init.t &&\n+\t\tgit -C swamp reset --hard &&\n+\t\ttest_cmp init.t swamp/init.t\n+\t'\n+\n+\ttest_expect_success '\"add\" worktree with --checkout' '\n+\t\tgit worktree add --checkout -b swmap2 swamp2 &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" swamp2 &&\n+\t\ttest_cmp init.t swamp2/init.t\n+\t'\n+\n+\ttest_expect_success 'put a worktree under rebase' '\n+\t\tgit worktree add under-rebase &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" under-rebase &&\n+\t\t(\n+\t\t\tcd under-rebase &&\n+\t\t\tset_fake_editor &&\n+\t\t\tFAKE_LINES=\"edit 1\" git rebase -i HEAD^ &&\n+\t\t\tgit worktree list >actual &&\n+\t\t\tgrep \"under-rebase.*detached HEAD\" actual\n+\t\t)\n+\t'\n+\n+\ttest_expect_success 'add a worktree, checking out a rebased branch' '\n+\t\ttest_must_fail git worktree add new-rebase under-rebase &&\n+\t\t! test -d new-rebase\n+\t'\n+\n+\ttest_expect_success 'checking out a rebased branch from another worktree' '\n+\t\tgit worktree add new-place &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" new-place &&\n+\t\ttest_must_fail git -C new-place checkout under-rebase\n+\t'\n+\n+\ttest_expect_success 'not allow to delete a branch under rebase' '\n+\t\t(\n+\t\t\tcd under-rebase &&\n+\t\t\ttest_must_fail git branch -D under-rebase\n+\t\t)\n+\t'\n+\n+\ttest_expect_success 'rename a branch under rebase not allowed' '\n+\t\ttest_must_fail git branch -M under-rebase rebase-with-new-name\n+\t'\n+\n+\ttest_expect_success 'check out from current worktree branch ok' '\n+\t\t(\n+\t\t\tcd under-rebase &&\n+\t\t\tgit checkout under-rebase &&\n+\t\t\tgit checkout - &&\n+\t\t\tgit rebase --abort\n+\t\t)\n+\t'\n+\n+\ttest_expect_success 'checkout a branch under bisect' '\n+\t\tgit worktree add under-bisect &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" under-bisect &&\n+\t\t(\n+\t\t\tcd under-bisect &&\n+\t\t\tgit bisect start &&\n+\t\t\tgit bisect bad &&\n+\t\t\tgit bisect good HEAD~2 &&\n+\t\t\tgit worktree list >actual &&\n+\t\t\tgrep \"under-bisect.*detached HEAD\" actual &&\n+\t\t\ttest_must_fail git worktree add new-bisect under-bisect &&\n+\t\t\t! test -d new-bisect\n+\t\t)\n+\t'\n+\n+\ttest_expect_success 'rename a branch under bisect not allowed' '\n+\t\ttest_must_fail git branch -M under-bisect bisect-with-new-name\n+\t'\n+\n+\ttest_expect_success '--track sets up tracking' '\n+\t\ttest_when_finished rm -rf track &&\n+\t\tgit worktree add --track -b track track main &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" track &&\n+\t\ttest_branch_upstream track . main\n+\t'\n+\n+\ttest_expect_success '\"add\" <path> <remote/branch> w/ no HEAD' '\n+\t\ttest_when_finished rm -rf repo_upstream repo_local foo &&\n+\t\tsetup_remote_repo repo_upstream repo_local &&\n+\t\tgit -C repo_local config --bool core.bare true &&\n+\t\tgit -C repo_local branch -D main &&\n+\t\tgit -C repo_local worktree add ./foo repo_upstream/foo &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" repo_local/foo\n+\t'\n+\n+\ttest_expect_success '--no-track avoids setting up tracking' '\n+\t\ttest_when_finished rm -rf repo_upstream repo_local foo &&\n+\t\tsetup_remote_repo repo_upstream repo_local &&\n+\t\t(\n+\t\t\tcd repo_local &&\n+\t\t\tgit worktree add --no-track -b foo ../foo repo_upstream/foo &&\n+\t\t\tcheck_worktree_paths \"$use_relative_paths\" ../foo\n+\t\t) &&\n+\t\t(\n+\t\t\tcd foo &&\n+\t\t\ttest_must_fail git config \"branch.foo.remote\" &&\n+\t\t\ttest_must_fail git config \"branch.foo.merge\" &&\n+\t\t\ttest_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo\n+\t\t)\n+\t'\n+\n+\ttest_expect_success '\"add\" <path> <non-existent-branch> fails' '\n+\t\ttest_must_fail git worktree add foo non-existent\n+\t'\n+\n+\ttest_expect_success '\"add\" <path> <branch> dwims' '\n+\t\ttest_when_finished rm -rf repo_upstream repo_dwim foo &&\n+\t\tsetup_remote_repo repo_upstream repo_dwim &&\n+\t\tgit init repo_dwim &&\n+\t\t(\n+\t\t\tcd repo_dwim &&\n+\t\t\tgit worktree add ../foo foo &&\n+\t\t\tcheck_worktree_paths \"$use_relative_paths\" ../foo\n+\t\t) &&\n+\t\t(\n+\t\t\tcd foo &&\n+\t\t\ttest_branch_upstream foo repo_upstream foo &&\n+\t\t\ttest_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo\n+\t\t)\n+\t'\n+\n+\ttest_expect_success '\"add\" <path> <branch> dwims with checkout.defaultRemote' '\n+\t\ttest_when_finished rm -rf repo_upstream repo_dwim foo &&\n+\t\tsetup_remote_repo repo_upstream repo_dwim &&\n+\t\tgit init repo_dwim &&\n+\t\t(\n+\t\t\tcd repo_dwim &&\n+\t\t\tgit remote add repo_upstream2 ../repo_upstream &&\n+\t\t\tgit fetch repo_upstream2 &&\n+\t\t\ttest_must_fail git worktree add ../foo foo &&\n+\t\t\tgit -c checkout.defaultRemote=repo_upstream worktree add ../foo foo &&\n+\t\t\tcheck_worktree_paths \"$use_relative_paths\" ../foo &&\n+\t\t\tgit status -uno --porcelain >status.actual &&\n+\t\t\ttest_must_be_empty status.actual\n+\t\t) &&\n+\t\t(\n+\t\t\tcd foo &&\n+\t\t\ttest_branch_upstream foo repo_upstream foo &&\n+\t\t\ttest_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo\n+\t\t)\n+\t'\n+\n+\ttest_expect_success 'git worktree add does not match remote' '\n+\t\ttest_when_finished rm -rf repo_a repo_b foo &&\n+\t\tsetup_remote_repo repo_a repo_b &&\n+\t\t(\n+\t\t\tcd repo_b &&\n+\t\t\tgit worktree add ../foo &&\n+\t\t\tcheck_worktree_paths \"$use_relative_paths\" ../foo\n+\t\t) &&\n+\t\t(\n+\t\t\tcd foo &&\n+\t\t\ttest_must_fail git config \"branch.foo.remote\" &&\n+\t\t\ttest_must_fail git config \"branch.foo.merge\" &&\n+\t\t\ttest_cmp_rev ! refs/remotes/repo_a/foo refs/heads/foo\n+\t\t)\n+\t'\n+\n+\ttest_expect_success 'git worktree add --guess-remote sets up tracking' '\n+\t\ttest_when_finished rm -rf repo_a repo_b foo &&\n+\t\tsetup_remote_repo repo_a repo_b &&\n+\t\t(\n+\t\t\tcd repo_b &&\n+\t\t\tgit worktree add --guess-remote ../foo &&\n+\t\t\tcheck_worktree_paths \"$use_relative_paths\" ../foo\n+\t\t) &&\n+\t\t(\n+\t\t\tcd foo &&\n+\t\t\ttest_branch_upstream foo repo_a foo &&\n+\t\t\ttest_cmp_rev refs/remotes/repo_a/foo refs/heads/foo\n+\t\t)\n+\t'\n+\ttest_expect_success 'git worktree add --guess-remote sets up tracking (quiet)' '\n+\t\ttest_when_finished rm -rf repo_a repo_b foo &&\n+\t\tsetup_remote_repo repo_a repo_b &&\n+\t\t(\n+\t\t\tcd repo_b &&\n+\t\t\tgit worktree add --quiet --guess-remote ../foo 2>actual &&\n+\t\t\tcheck_worktree_paths \"$use_relative_paths\" ../foo &&\n+\t\t\ttest_must_be_empty actual\n+\t\t) &&\n+\t\t(\n+\t\t\tcd foo &&\n+\t\t\ttest_branch_upstream foo repo_a foo &&\n+\t\t\ttest_cmp_rev refs/remotes/repo_a/foo refs/heads/foo\n+\t\t)\n+\t'\n+\n+\ttest_expect_success 'git worktree --no-guess-remote (quiet)' '\n+\t\ttest_when_finished rm -rf repo_a repo_b foo &&\n+\t\tsetup_remote_repo repo_a repo_b &&\n+\t\t(\n+\t\t\tcd repo_b &&\n+\t\t\tgit worktree add --quiet --no-guess-remote ../foo &&\n+\t\t\tcheck_worktree_paths \"$use_relative_paths\" ../foo\n+\t\t) &&\n+\t\t(\n+\t\t\tcd foo &&\n+\t\t\ttest_must_fail git config \"branch.foo.remote\" &&\n+\t\t\ttest_must_fail git config \"branch.foo.merge\" &&\n+\t\t\ttest_cmp_rev ! refs/remotes/repo_a/foo refs/heads/foo\n+\t\t)\n+\t'\n+\n+\ttest_expect_success 'git worktree add with worktree.guessRemote sets up tracking' '\n+\t\ttest_when_finished rm -rf repo_a repo_b foo &&\n+\t\tsetup_remote_repo repo_a repo_b &&\n+\t\t(\n+\t\t\tcd repo_b &&\n+\t\t\tgit config worktree.guessRemote true &&\n+\t\t\tgit worktree add ../foo &&\n+\t\t\tcheck_worktree_paths \"$use_relative_paths\" ../foo\n+\t\t) &&\n+\t\t(\n+\t\t\tcd foo &&\n+\t\t\ttest_branch_upstream foo repo_a foo &&\n+\t\t\ttest_cmp_rev refs/remotes/repo_a/foo refs/heads/foo\n+\t\t)\n+\t'\n+\n+\ttest_expect_success 'git worktree --no-guess-remote option overrides config' '\n+\t\ttest_when_finished rm -rf repo_a repo_b foo &&\n+\t\tsetup_remote_repo repo_a repo_b &&\n+\t\t(\n+\t\t\tcd repo_b &&\n+\t\t\tgit config worktree.guessRemote true &&\n+\t\t\tgit worktree add --no-guess-remote ../foo &&\n+\t\t\tcheck_worktree_paths \"$use_relative_paths\" ../foo\n+\t\t) &&\n+\t\t(\n+\t\t\tcd foo &&\n+\t\t\ttest_must_fail git config \"branch.foo.remote\" &&\n+\t\t\ttest_must_fail git config \"branch.foo.merge\" &&\n+\t\t\ttest_cmp_rev ! refs/remotes/repo_a/foo refs/heads/foo\n+\t\t)\n+\t'\n+\n+\tfor quiet_mode in \"no_quiet\" \"quiet\"\n+\tdo\n+\t\tfor changedir_type in \"cd_repo\" \"cd_wt\" \"-C_repo\" \"-C_wt\"\n+\t\tdo\n+\t\t\tdwim_test_args=\"$quiet_mode $changedir_type\"\n+\t\t\ttest_dwim_orphan $use_relative_paths 'infer' $dwim_test_args no_-b\n+\t\t\ttest_dwim_orphan $use_relative_paths 'no_infer' $dwim_test_args no_-b local_ref good_head\n+\t\t\ttest_dwim_orphan $use_relative_paths 'infer' $dwim_test_args no_-b no_local_ref no_remote no_remote_ref no_guess_remote\n+\t\t\ttest_dwim_orphan $use_relative_paths 'infer' $dwim_test_args no_-b no_local_ref remote no_remote_ref no_guess_remote\n+\t\t\ttest_dwim_orphan $use_relative_paths 'fetch_error' $dwim_test_args no_-b no_local_ref remote no_remote_ref guess_remote\n+\t\t\ttest_dwim_orphan $use_relative_paths 'infer' $dwim_test_args no_-b no_local_ref remote no_remote_ref guess_remote force\n+\t\t\ttest_dwim_orphan $use_relative_paths 'no_infer' $dwim_test_args no_-b no_local_ref remote remote_ref guess_remote\n+\n+\t\t\ttest_dwim_orphan $use_relative_paths 'infer' $dwim_test_args -b\n+\t\t\ttest_dwim_orphan $use_relative_paths 'no_infer' $dwim_test_args -b local_ref good_head\n+\t\t\ttest_dwim_orphan $use_relative_paths 'infer' $dwim_test_args -b no_local_ref no_remote no_remote_ref no_guess_remote\n+\t\t\ttest_dwim_orphan $use_relative_paths 'infer' $dwim_test_args -b no_local_ref remote no_remote_ref no_guess_remote\n+\t\t\ttest_dwim_orphan $use_relative_paths 'infer' $dwim_test_args -b no_local_ref remote no_remote_ref guess_remote\n+\t\t\ttest_dwim_orphan $use_relative_paths 'infer' $dwim_test_args -b no_local_ref remote remote_ref guess_remote\n+\n+\t\t\ttest_dwim_orphan $use_relative_paths 'warn_bad_head' $dwim_test_args no_-b local_ref bad_head\n+\t\t\ttest_dwim_orphan $use_relative_paths 'warn_bad_head' $dwim_test_args -b local_ref bad_head\n+\t\t\ttest_dwim_orphan $use_relative_paths 'warn_bad_head' $dwim_test_args detach local_ref bad_head\n+\t\tdone\n+\n+\t\ttest_dwim_orphan $use_relative_paths 'fatal_orphan_bad_combo' $quiet_mode no_-b no_checkout\n+\t\ttest_dwim_orphan $use_relative_paths 'fatal_orphan_bad_combo' $quiet_mode no_-b track\n+\t\ttest_dwim_orphan $use_relative_paths 'fatal_orphan_bad_combo' $quiet_mode -b no_checkout\n+\t\ttest_dwim_orphan $use_relative_paths 'fatal_orphan_bad_combo' $quiet_mode -b track\n+\tdone\n+\n+\ttest_expect_success '\"add\" invokes post-checkout hook (branch)' '\n+\t\tpost_checkout_hook &&\n+\t\t{\n+\t\t\techo $ZERO_OID $(git rev-parse HEAD) 1 &&\n+\t\t\techo $(pwd)/.git/worktrees/gumby &&\n+\t\t\techo $(pwd)/gumby\n+\t\t} >hook.expect &&\n+\t\tgit worktree add gumby &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" gumby &&\n+\t\ttest_cmp hook.expect gumby/hook.actual\n+\t'\n+\n+\ttest_expect_success '\"add\" invokes post-checkout hook (detached)' '\n+\t\tpost_checkout_hook &&\n+\t\t{\n+\t\t\techo $ZERO_OID $(git rev-parse HEAD) 1 &&\n+\t\t\techo $(pwd)/.git/worktrees/grumpy &&\n+\t\t\techo $(pwd)/grumpy\n+\t\t} >hook.expect &&\n+\t\tgit worktree add --detach grumpy &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" grumpy &&\n+\t\ttest_cmp hook.expect grumpy/hook.actual\n+\t'\n+\n+\ttest_expect_success '\"add --no-checkout\" suppresses post-checkout hook' '\n+\t\tpost_checkout_hook &&\n+\t\trm -f hook.actual &&\n+\t\tgit worktree add --no-checkout gloopy &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" gloopy &&\n+\t\ttest_path_is_missing gloopy/hook.actual\n+\t'\n+\n+\ttest_expect_success '\"add\" in other worktree invokes post-checkout hook' '\n+\t\tpost_checkout_hook &&\n+\t\t{\n+\t\t\techo $ZERO_OID $(git rev-parse HEAD) 1 &&\n+\t\t\techo $(pwd)/.git/worktrees/guppy &&\n+\t\t\techo $(pwd)/guppy\n+\t\t} >hook.expect &&\n+\t\tgit -C gloopy worktree add --detach ../guppy &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" guppy &&\n+\t\ttest_cmp hook.expect guppy/hook.actual\n+\t'\n+\n+\ttest_expect_success '\"add\" in bare repo invokes post-checkout hook' '\n+\t\trm -rf bare &&\n+\t\tgit clone --bare . bare &&\n+\t\t{\n+\t\t\techo $ZERO_OID $(git --git-dir=bare rev-parse HEAD) 1 &&\n+\t\t\techo $(pwd)/bare/worktrees/goozy &&\n+\t\t\techo $(pwd)/goozy\n+\t\t} >hook.expect &&\n+\t\tpost_checkout_hook bare &&\n+\t\tgit -C bare worktree add --detach ../goozy &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" goozy &&\n+\t\ttest_cmp hook.expect goozy/hook.actual\n+\t'\n+\n+\ttest_expect_success '\"add\" an existing but missing worktree' '\n+\t\tgit worktree add --detach pneu &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" pneu &&\n+\t\ttest_must_fail git worktree add --detach pneu &&\n+\t\trm -fr pneu &&\n+\t\ttest_must_fail git worktree add --detach pneu &&\n+\t\tgit worktree add --force --detach pneu &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" pneu\n+\t'\n+\n+\ttest_expect_success '\"add\" an existing locked but missing worktree' '\n+\t\tgit worktree add --detach gnoo &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" gnoo &&\n+\t\tgit worktree lock gnoo &&\n+\t\ttest_when_finished \"git worktree unlock gnoo || :\" &&\n+\t\trm -fr gnoo &&\n+\t\ttest_must_fail git worktree add --detach gnoo &&\n+\t\ttest_must_fail git worktree add --force --detach gnoo &&\n+\t\tgit worktree add --force --force --detach gnoo &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" gnoo\n+\t'\n+\n+\ttest_expect_success '\"add\" not tripped up by magic worktree matching\"' '\n+\t\t# if worktree \"sub1/bar\" exists, \"git worktree add bar\" in distinct\n+\t\t# directory `sub2` should not mistakenly complain that `bar` is an\n+\t\t# already-registered worktree\n+\t\tmkdir sub1 sub2 &&\n+\t\tgit -C sub1 --git-dir=../.git worktree add --detach bozo &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" sub1/bozo &&\n+\t\tgit -C sub2 --git-dir=../.git worktree add --detach bozo &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" sub2/bozo\n+\t'\n+\n+\ttest_expect_success FUNNYNAMES 'sanitize generated worktree name' '\n+\t\tgit worktree add --detach \".  weird*..?.lock.lock\" &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" \".  weird*..?.lock.lock\" &&\n+\t\ttest -d .git/worktrees/---weird-.-\n+\t'\n+\n+\ttest_expect_success '\"add\" should not fail because of another bad worktree' '\n+\t\tgit init add-fail &&\n+\t\t(\n+\t\t\tcd add-fail &&\n+\t\t\ttest_commit first &&\n+\t\t\tmkdir sub &&\n+\t\t\tgit worktree add sub/to-be-deleted &&\n+\t\t\tcheck_worktree_paths \"$use_relative_paths\" sub/to-be-deleted &&\n+\t\t\trm -rf sub &&\n+\t\t\tgit worktree add second &&\n+\t\t\tcheck_worktree_paths \"$use_relative_paths\" second\n+\t\t)\n+\t'\n+\n+\ttest_expect_success '\"add\" with uninitialized submodule, with submodule.recurse unset' '\n+\t\ttest_config_global protocol.file.allow always &&\n+\t\ttest_create_repo submodule &&\n+\t\ttest_commit -C submodule first &&\n+\t\ttest_create_repo project &&\n+\t\tgit -C project submodule add ../submodule &&\n+\t\tgit -C project add submodule &&\n+\t\ttest_tick &&\n+\t\tgit -C project commit -m add_sub &&\n+\t\tgit clone project project-clone &&\n+\t\tgit -C project-clone worktree add ../project-2 &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" project-2\n+\t'\n+\ttest_expect_success '\"add\" with uninitialized submodule, with submodule.recurse set' '\n+\t\tgit -C project-clone -c submodule.recurse worktree add ../project-3 &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" project-3\n+\t'\n+\n+\ttest_expect_success '\"add\" with initialized submodule, with submodule.recurse unset' '\n+\t\ttest_config_global protocol.file.allow always &&\n+\t\tgit -C project-clone submodule update --init &&\n+\t\tgit -C project-clone worktree add ../project-4 &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" project-4\n+\t'\n+\n+\ttest_expect_success '\"add\" with initialized submodule, with submodule.recurse set' '\n+\t\tgit -C project-clone -c submodule.recurse worktree add ../project-5 &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" project-5\n+\t'\n+}\n+\n+say \"Run tests with worktree.userelativepaths set to false\"\n+run_worktree_add_tests false\n+\n+cd ..\n+setup_test_repo\n+\n+say \"Run tests with worktree.userelativepaths set to true\"\n+run_worktree_add_tests true\n+\n+say \"All other tests\"\n+test_wt_add_excl -b poodle -B poodle bamboo main\n+test_wt_add_excl -b poodle --detach bamboo main\n+test_wt_add_excl -B poodle --detach bamboo main\n+test_wt_add_excl --orphan --detach bamboo\n+test_wt_add_excl --orphan --no-checkout bamboo\n+test_wt_add_excl --orphan bamboo main\n+test_wt_add_excl --orphan -b bamboo wtdir/ main\n+\n+test_wt_add_orphan_hint 'no opts' 0\n+test_wt_add_orphan_hint '-b' 1 -b foobar_branch\n+test_wt_add_orphan_hint '-B' 1 -B foobar_branch\n \n test_done\ndiff --git a/t/t2401-worktree-prune.sh b/t/t2401-worktree-prune.sh\nindex 71aa9bcd620..7ff6e794027 100755\n--- a/t/t2401-worktree-prune.sh\n+++ b/t/t2401-worktree-prune.sh\n@@ -80,44 +80,66 @@ test_expect_success 'not prune locked checkout' '\n \ttest -d .git/worktrees/ghi\n '\n \n-test_expect_success 'not prune recent checkouts' '\n-\ttest_when_finished rm -r .git/worktrees &&\n-\tgit worktree add jlm HEAD &&\n-\ttest -d .git/worktrees/jlm &&\n-\trm -rf jlm &&\n-\tgit worktree prune --verbose --expire=2.days.ago &&\n-\ttest -d .git/worktrees/jlm\n-'\n-\n-test_expect_success 'not prune proper checkouts' '\n-\ttest_when_finished rm -r .git/worktrees &&\n-\tgit worktree add --detach \"$PWD/nop\" main &&\n-\tgit worktree prune &&\n-\ttest -d .git/worktrees/nop\n-'\n-\n-test_expect_success 'prune duplicate (linked/linked)' '\n-\ttest_when_finished rm -fr .git/worktrees w1 w2 &&\n-\tgit worktree add --detach w1 &&\n-\tgit worktree add --detach w2 &&\n-\tsed \"s/w2/w1/\" .git/worktrees/w2/gitdir >.git/worktrees/w2/gitdir.new &&\n-\tmv .git/worktrees/w2/gitdir.new .git/worktrees/w2/gitdir &&\n-\tgit worktree prune --verbose 2>actual &&\n-\ttest_grep \"duplicate entry\" actual &&\n-\ttest -d .git/worktrees/w1 &&\n-\t! test -d .git/worktrees/w2\n+run_worktree_prune_tests() {\n+\tlocal use_relative_paths=\"$1\"\n+\n+\t# Set the Git config variable based on the parameter\n+\ttest_config_global worktree.userelativepaths \"$use_relative_paths\"\n+\n+\ttest_expect_success 'not prune recent checkouts' '\n+\t\ttest_when_finished rm -r .git/worktrees &&\n+\t\tgit worktree add jlm HEAD &&\n+\t\ttest -d .git/worktrees/jlm &&\n+\t\trm -rf jlm &&\n+\t\tgit worktree prune --verbose --expire=2.days.ago &&\n+\t\ttest -d .git/worktrees/jlm\n+\t'\n+\n+\ttest_expect_success 'not prune proper checkouts' '\n+\t\ttest_when_finished rm -r .git/worktrees &&\n+\t\tgit worktree add --detach \"$PWD/nop\" main &&\n+\t\tgit worktree prune &&\n+\t\ttest -d .git/worktrees/nop\n+\t'\n+\n+\ttest_expect_success 'prune duplicate (linked/linked)' '\n+\t\ttest_when_finished rm -fr .git/worktrees w1 w2 &&\n+\t\tgit worktree add --detach w1 &&\n+\t\tgit worktree add --detach w2 &&\n+\t\tsed \"s/w2/w1/\" .git/worktrees/w2/gitdir >.git/worktrees/w2/gitdir.new &&\n+\t\tmv .git/worktrees/w2/gitdir.new .git/worktrees/w2/gitdir &&\n+\t\tgit worktree prune --verbose 2>actual &&\n+\t\ttest_grep \"duplicate entry\" actual &&\n+\t\ttest -d .git/worktrees/w1 &&\n+\t\t! test -d .git/worktrees/w2\n+\t'\n+\n+\ttest_expect_success 'prune duplicate (main/linked)' '\n+\t\ttest_when_finished rm -fr repo wt &&\n+\t\ttest_create_repo repo &&\n+\t\ttest_commit -C repo x &&\n+\t\tgit -C repo worktree add --detach ../wt &&\n+\t\trm -fr wt &&\n+\t\tmv repo wt &&\n+\t\tgit -C wt worktree prune --verbose 2>actual &&\n+\t\techo \"FOO\" &&\n+\t\tcat actual &&\n+\t\techo \"BAR\" &&\n+\t\ttest_grep \"duplicate entry\" actual &&\n+\t\t! test -d .git/worktrees/wt\n+\t'\n+}\n+\n+say \"Run tests with worktree.userelativepaths set to false\"\n+run_worktree_prune_tests false\n+\n+cd ..\n+setup_test_repo\n+test_expect_success initialize '\n+\tgit commit --allow-empty -m init\n '\n \n-test_expect_success 'prune duplicate (main/linked)' '\n-\ttest_when_finished rm -fr repo wt &&\n-\ttest_create_repo repo &&\n-\ttest_commit -C repo x &&\n-\tgit -C repo worktree add --detach ../wt &&\n-\trm -fr wt &&\n-\tmv repo wt &&\n-\tgit -C wt worktree prune --verbose 2>actual &&\n-\ttest_grep \"duplicate entry\" actual &&\n-\t! test -d .git/worktrees/wt\n-'\n+say \"Run tests with worktree.userelativepaths set to true\"\n+run_worktree_prune_tests true\n \n test_done\ndiff --git a/t/t2403-worktree-move.sh b/t/t2403-worktree-move.sh\nindex 901342ea09b..90fab3800bc 100755\n--- a/t/t2403-worktree-move.sh\n+++ b/t/t2403-worktree-move.sh\n@@ -4,6 +4,7 @@ test_description='test git worktree move, remove, lock and unlock'\n \n TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n+. \"$TEST_DIRECTORY\"/lib-worktree.sh\n \n test_expect_success 'setup' '\n \ttest_commit init &&\n@@ -247,4 +248,13 @@ test_expect_success 'not remove a repo with initialized submodule' '\n \t)\n '\n \n+test_expect_success 'move worktree and verify path format' '\n+\ttest_config worktree.userelativepaths false &&\n+\tgit worktree add fred &&\n+\tcheck_worktree_paths false fred &&\n+\ttest_config worktree.userelativepaths true &&\n+\tgit worktree move fred barney &&\n+\tcheck_worktree_paths true barney\n+'\n+\n test_done\ndiff --git a/t/t2406-worktree-repair.sh b/t/t2406-worktree-repair.sh\nindex edbf502ec57..ae9cdd2f031 100755\n--- a/t/t2406-worktree-repair.sh\n+++ b/t/t2406-worktree-repair.sh\n@@ -5,6 +5,145 @@ test_description='test git worktree repair'\n TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n+. \"$TEST_DIRECTORY\"/lib-worktree.sh\n+\n+test_corrupt_gitfile () {\n+\tlocal use_relative_paths=\"$1\" &&\n+\tlocal butcher=\"$2\" &&\n+\tlocal problem=\"$3\" &&\n+\tlocal repairdir=\"${4:-.}\" &&\n+\ttest_when_finished 'rm -rf corrupt && git worktree prune' &&\n+\tgit worktree add --detach corrupt &&\n+\tgit -C corrupt rev-parse --absolute-git-dir >expect &&\n+\teval \"$butcher\" &&\n+\tgit -C \"$repairdir\" worktree repair 2>err &&\n+\tcheck_worktree_paths \"$use_relative_paths\" corrupt &&\n+\ttest_grep \"$problem\" err &&\n+\tgit -C corrupt rev-parse --absolute-git-dir >actual &&\n+\ttest_cmp expect actual\n+}\n+\n+run_worktree_repair_tests() {\n+\tlocal use_relative_paths=\"$1\"\n+\n+\t# Set the Git config variable based on the parameter\n+\ttest_config_global worktree.userelativepaths \"$use_relative_paths\"\n+\n+\ttest_expect_success setup '\n+\t\ttest_commit init\n+\t'\n+\n+\ttest_expect_success 'repair missing .git file' '\n+\t\ttest_corrupt_gitfile \"$use_relative_paths\" \"rm -f corrupt/.git\" \".git file broken\"\n+\t'\n+\n+\ttest_expect_success 'repair bogus .git file' '\n+\t\ttest_corrupt_gitfile \"$use_relative_paths\" \"echo \\\"gitdir: /nowhere\\\" >corrupt/.git\" \\\n+\t\t\t\".git file broken\"\n+\t'\n+\n+\ttest_expect_success 'repair incorrect .git file' '\n+\t\ttest_when_finished \"rm -rf other && git worktree prune\" &&\n+\t\ttest_create_repo other &&\n+\t\tother=$(git -C other rev-parse --absolute-git-dir) &&\n+\t\ttest_corrupt_gitfile \"$use_relative_paths\" \"echo \\\"gitdir: $other\\\" >corrupt/.git\" \\\n+\t\t\t\".git file incorrect\"\n+\t'\n+\n+\ttest_expect_success 'repair .git file from main/.git' '\n+\t\ttest_corrupt_gitfile \"$use_relative_paths\" \"rm -f corrupt/.git\" \".git file broken\" .git\n+\t'\n+\n+\ttest_expect_success 'repair .git file from linked worktree' '\n+\t\ttest_when_finished \"rm -rf other && git worktree prune\" &&\n+\t\tgit worktree add --detach other &&\n+\t\ttest_corrupt_gitfile \"$use_relative_paths\" \"rm -f corrupt/.git\" \".git file broken\" other\n+\t'\n+\n+\ttest_expect_success 'repair .git file from bare.git' '\n+\t\ttest_when_finished \"rm -rf bare.git corrupt && git worktree prune\" &&\n+\t\tgit clone --bare . bare.git &&\n+\t\tgit -C bare.git worktree add --detach ../corrupt &&\n+\t\tgit -C corrupt rev-parse --absolute-git-dir >expect &&\n+\t\trm -f corrupt/.git &&\n+\t\tgit -C bare.git worktree repair &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" corrupt &&\n+\t\tgit -C corrupt rev-parse --absolute-git-dir >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success 'repair broken gitdir' '\n+\t\ttest_when_finished \"rm -rf orig moved && git worktree prune\" &&\n+\t\tgit worktree add --detach orig &&\n+\t\tsed s,orig/\\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&\n+\t\trm .git/worktrees/orig/gitdir &&\n+\t\tmv orig moved &&\n+\t\tgit worktree repair moved 2>err &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" moved &&\n+\t\ttest_cmp expect .git/worktrees/orig/gitdir &&\n+\t\ttest_grep \"gitdir unreadable\" err\n+\t'\n+\n+\ttest_expect_success 'repair incorrect gitdir' '\n+\t\ttest_when_finished \"rm -rf orig moved && git worktree prune\" &&\n+\t\tgit worktree add --detach orig &&\n+\t\tsed s,orig/\\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&\n+\t\tmv orig moved &&\n+\t\tgit worktree repair moved 2>err &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" moved &&\n+\t\ttest_cmp expect .git/worktrees/orig/gitdir &&\n+\t\ttest_grep \"gitdir incorrect\" err\n+\t'\n+\n+\ttest_expect_success 'repair gitdir (implicit) from linked worktree' '\n+\t\ttest_when_finished \"rm -rf orig moved && git worktree prune\" &&\n+\t\tgit worktree add --detach orig &&\n+\t\tsed s,orig/\\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&\n+\t\tmv orig moved &&\n+\t\tgit -C moved worktree repair 2>err &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" moved &&\n+\t\ttest_cmp expect .git/worktrees/orig/gitdir &&\n+\t\ttest_grep \"gitdir incorrect\" err\n+\t'\n+\n+\ttest_expect_success 'repair multiple gitdir files' '\n+\t\ttest_when_finished \"rm -rf orig1 orig2 moved1 moved2 &&\n+\t\t\tgit worktree prune\" &&\n+\t\tgit worktree add --detach orig1 &&\n+\t\tgit worktree add --detach orig2 &&\n+\t\tsed s,orig1/\\.git$,moved1/.git, .git/worktrees/orig1/gitdir >expect1 &&\n+\t\tsed s,orig2/\\.git$,moved2/.git, .git/worktrees/orig2/gitdir >expect2 &&\n+\t\tmv orig1 moved1 &&\n+\t\tmv orig2 moved2 &&\n+\t\tgit worktree repair moved1 moved2 2>err &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" moved1 &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" moved2 &&\n+\t\ttest_cmp expect1 .git/worktrees/orig1/gitdir &&\n+\t\ttest_cmp expect2 .git/worktrees/orig2/gitdir &&\n+\t\ttest_grep \"gitdir incorrect:.*orig1/gitdir$\" err &&\n+\t\ttest_grep \"gitdir incorrect:.*orig2/gitdir$\" err\n+\t'\n+\n+\ttest_expect_success 'repair moved main and linked worktrees' '\n+\t\ttest_when_finished \"rm -rf main side mainmoved sidemoved\" &&\n+\t\ttest_create_repo main &&\n+\t\ttest_commit -C main init &&\n+\t\tgit -C main worktree add --detach ../side &&\n+\t\tsed \"s,side/\\.git$,sidemoved/.git,\" \\\n+\t\t\tmain/.git/worktrees/side/gitdir >expect-gitdir &&\n+\t\tsed \"s,main/.git/worktrees/side$,mainmoved/.git/worktrees/side,\" \\\n+\t\t\tside/.git >expect-gitfile &&\n+\t\tmv main mainmoved &&\n+\t\tmv side sidemoved &&\n+\t\tgit -C mainmoved worktree repair ../sidemoved &&\n+\t\tcheck_worktree_paths \"$use_relative_paths\" sidemoved &&\n+\t\ttest_cmp expect-gitdir mainmoved/.git/worktrees/side/gitdir &&\n+\t\ttest_cmp expect-gitfile sidemoved/.git\n+\t'\n+}\n+\n+say \"Expected failures due to inability to repair the broken worktree.\"\n+\n test_expect_success setup '\n \ttest_commit init\n '\n@@ -38,58 +177,6 @@ test_expect_success \"don't clobber .git repo\" '\n \ttest_grep \".git is not a file\" err\n '\n \n-test_corrupt_gitfile () {\n-\tbutcher=$1 &&\n-\tproblem=$2 &&\n-\trepairdir=${3:-.} &&\n-\ttest_when_finished 'rm -rf corrupt && git worktree prune' &&\n-\tgit worktree add --detach corrupt &&\n-\tgit -C corrupt rev-parse --absolute-git-dir >expect &&\n-\teval \"$butcher\" &&\n-\tgit -C \"$repairdir\" worktree repair 2>err &&\n-\ttest_grep \"$problem\" err &&\n-\tgit -C corrupt rev-parse --absolute-git-dir >actual &&\n-\ttest_cmp expect actual\n-}\n-\n-test_expect_success 'repair missing .git file' '\n-\ttest_corrupt_gitfile \"rm -f corrupt/.git\" \".git file broken\"\n-'\n-\n-test_expect_success 'repair bogus .git file' '\n-\ttest_corrupt_gitfile \"echo \\\"gitdir: /nowhere\\\" >corrupt/.git\" \\\n-\t\t\".git file broken\"\n-'\n-\n-test_expect_success 'repair incorrect .git file' '\n-\ttest_when_finished \"rm -rf other && git worktree prune\" &&\n-\ttest_create_repo other &&\n-\tother=$(git -C other rev-parse --absolute-git-dir) &&\n-\ttest_corrupt_gitfile \"echo \\\"gitdir: $other\\\" >corrupt/.git\" \\\n-\t\t\".git file incorrect\"\n-'\n-\n-test_expect_success 'repair .git file from main/.git' '\n-\ttest_corrupt_gitfile \"rm -f corrupt/.git\" \".git file broken\" .git\n-'\n-\n-test_expect_success 'repair .git file from linked worktree' '\n-\ttest_when_finished \"rm -rf other && git worktree prune\" &&\n-\tgit worktree add --detach other &&\n-\ttest_corrupt_gitfile \"rm -f corrupt/.git\" \".git file broken\" other\n-'\n-\n-test_expect_success 'repair .git file from bare.git' '\n-\ttest_when_finished \"rm -rf bare.git corrupt && git worktree prune\" &&\n-\tgit clone --bare . bare.git &&\n-\tgit -C bare.git worktree add --detach ../corrupt &&\n-\tgit -C corrupt rev-parse --absolute-git-dir >expect &&\n-\trm -f corrupt/.git &&\n-\tgit -C bare.git worktree repair &&\n-\tgit -C corrupt rev-parse --absolute-git-dir >actual &&\n-\ttest_cmp expect actual\n-'\n-\n test_expect_success 'invalid worktree path' '\n \ttest_must_fail git worktree repair /notvalid >out 2>err &&\n \ttest_must_be_empty out &&\n@@ -124,37 +211,6 @@ test_expect_success 'repo not found; .git file broken' '\n \ttest_grep \".git file broken\" err\n '\n \n-test_expect_success 'repair broken gitdir' '\n-\ttest_when_finished \"rm -rf orig moved && git worktree prune\" &&\n-\tgit worktree add --detach orig &&\n-\tsed s,orig/\\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&\n-\trm .git/worktrees/orig/gitdir &&\n-\tmv orig moved &&\n-\tgit worktree repair moved 2>err &&\n-\ttest_cmp expect .git/worktrees/orig/gitdir &&\n-\ttest_grep \"gitdir unreadable\" err\n-'\n-\n-test_expect_success 'repair incorrect gitdir' '\n-\ttest_when_finished \"rm -rf orig moved && git worktree prune\" &&\n-\tgit worktree add --detach orig &&\n-\tsed s,orig/\\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&\n-\tmv orig moved &&\n-\tgit worktree repair moved 2>err &&\n-\ttest_cmp expect .git/worktrees/orig/gitdir &&\n-\ttest_grep \"gitdir incorrect\" err\n-'\n-\n-test_expect_success 'repair gitdir (implicit) from linked worktree' '\n-\ttest_when_finished \"rm -rf orig moved && git worktree prune\" &&\n-\tgit worktree add --detach orig &&\n-\tsed s,orig/\\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&\n-\tmv orig moved &&\n-\tgit -C moved worktree repair 2>err &&\n-\ttest_cmp expect .git/worktrees/orig/gitdir &&\n-\ttest_grep \"gitdir incorrect\" err\n-'\n-\n test_expect_success 'unable to repair gitdir (implicit) from main worktree' '\n \ttest_when_finished \"rm -rf orig moved && git worktree prune\" &&\n \tgit worktree add --detach orig &&\n@@ -165,36 +221,16 @@ test_expect_success 'unable to repair gitdir (implicit) from main worktree' '\n \ttest_must_be_empty err\n '\n \n-test_expect_success 'repair multiple gitdir files' '\n-\ttest_when_finished \"rm -rf orig1 orig2 moved1 moved2 &&\n-\t\tgit worktree prune\" &&\n-\tgit worktree add --detach orig1 &&\n-\tgit worktree add --detach orig2 &&\n-\tsed s,orig1/\\.git$,moved1/.git, .git/worktrees/orig1/gitdir >expect1 &&\n-\tsed s,orig2/\\.git$,moved2/.git, .git/worktrees/orig2/gitdir >expect2 &&\n-\tmv orig1 moved1 &&\n-\tmv orig2 moved2 &&\n-\tgit worktree repair moved1 moved2 2>err &&\n-\ttest_cmp expect1 .git/worktrees/orig1/gitdir &&\n-\ttest_cmp expect2 .git/worktrees/orig2/gitdir &&\n-\ttest_grep \"gitdir incorrect:.*orig1/gitdir$\" err &&\n-\ttest_grep \"gitdir incorrect:.*orig2/gitdir$\" err\n-'\n+cd ..\n+setup_test_repo\n \n-test_expect_success 'repair moved main and linked worktrees' '\n-\ttest_when_finished \"rm -rf main side mainmoved sidemoved\" &&\n-\ttest_create_repo main &&\n-\ttest_commit -C main init &&\n-\tgit -C main worktree add --detach ../side &&\n-\tsed \"s,side/\\.git$,sidemoved/.git,\" \\\n-\t\tmain/.git/worktrees/side/gitdir >expect-gitdir &&\n-\tsed \"s,main/.git/worktrees/side$,mainmoved/.git/worktrees/side,\" \\\n-\t\tside/.git >expect-gitfile &&\n-\tmv main mainmoved &&\n-\tmv side sidemoved &&\n-\tgit -C mainmoved worktree repair ../sidemoved &&\n-\ttest_cmp expect-gitdir mainmoved/.git/worktrees/side/gitdir &&\n-\ttest_cmp expect-gitfile sidemoved/.git\n-'\n+say \"Run tests with worktree.userelativepaths set to false\"\n+run_worktree_repair_tests false\n+\n+cd ..\n+setup_test_repo\n+\n+say \"Run tests with worktree.userelativepaths set to true\"\n+run_worktree_repair_tests true\n \n test_done\ndiff --git a/t/test-lib.sh b/t/test-lib.sh\nindex 278d1215f15..c1fe8df2c39 100644\n--- a/t/test-lib.sh\n+++ b/t/test-lib.sh\n@@ -1627,26 +1627,30 @@ remove_trash_directory () {\n \t! test -d \"$dir\"\n }\n \n-# Test repository\n-remove_trash_directory \"$TRASH_DIRECTORY\" || {\n-\tBAIL_OUT 'cannot prepare test area'\n-}\n+setup_test_repo() {\n+\t# Remove the trash directory if it exists\n+\tremove_trash_directory \"$TRASH_DIRECTORY\" || {\n+\t\tBAIL_OUT 'cannot prepare test area'\n+\t}\n \n-remove_trash=t\n-if test -z \"$TEST_NO_CREATE_REPO\"\n-then\n-\tgit init \\\n-\t    ${TEST_CREATE_REPO_NO_TEMPLATE:+--template=} \\\n-\t    \"$TRASH_DIRECTORY\" >&3 2>&4 ||\n-\terror \"cannot run git init\"\n-else\n-\tmkdir -p \"$TRASH_DIRECTORY\"\n-fi\n+\tremove_trash=t\n \n-# Use -P to resolve symlinks in our working directory so that the cwd\n-# in subprocesses like git equals our $PWD (for pathname comparisons).\n-cd -P \"$TRASH_DIRECTORY\" || BAIL_OUT \"cannot cd -P to \\\"$TRASH_DIRECTORY\\\"\"\n+\t# Initialize a new Git repository or create the directory\n+\tif [ -z \"$TEST_NO_CREATE_REPO\" ]; then\n+\t\tgit init \\\n+\t\t\t${TEST_CREATE_REPO_NO_TEMPLATE:+--template=} \\\n+\t\t\t\"$TRASH_DIRECTORY\" >&3 2>&4 ||\n+\t\terror \"cannot run git init\"\n+\telse\n+\t\tmkdir -p \"$TRASH_DIRECTORY\"\n+\tfi\n+\n+\t# Use -P to resolve symlinks in our working directory so that the cwd\n+\t# in subprocesses like git equals our $PWD (for pathname comparisons).\n+\tcd -P \"$TRASH_DIRECTORY\" || BAIL_OUT \"cannot cd -P to \\\"$TRASH_DIRECTORY\\\"\"\n+}\n \n+setup_test_repo\n start_test_output \"$0\"\n \n # Convenience\ndiff --git a/worktree.c b/worktree.c\nindex 30a947426ee..a3a8c580508 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -328,17 +328,6 @@ int validate_worktree(const struct worktree *wt, struct strbuf *errmsg,\n \t\tgoto done;\n \t}\n \n-\t/*\n-\t * Make sure \"gitdir\" file points to a real .git file and that\n-\t * file points back here.\n-\t */\n-\tif (!is_absolute_path(wt->path)) {\n-\t\tstrbuf_addf_gently(errmsg,\n-\t\t\t\t   _(\"'%s' file does not contain absolute path to the working tree location\"),\n-\t\t\t\t   git_common_path(\"worktrees/%s/gitdir\", wt->id));\n-\t\tgoto done;\n-\t}\n-\n \tif (flags & WT_VALIDATE_WORKTREE_MISSING_OK &&\n \t    !file_exists(wt->path)) {\n \t\tret = 0;\n@@ -372,19 +361,30 @@ done:\n \n void update_worktree_location(struct worktree *wt, const char *path_)\n {\n-\tstruct strbuf path = STRBUF_INIT;\n+\tstruct strbuf worktree_new_path = STRBUF_INIT;\n+\tint use_relative_paths = 0;\n \n \tif (is_main_worktree(wt))\n \t\tBUG(\"can't relocate main worktree\");\n \n-\tstrbuf_realpath(&path, path_, 1);\n-\tif (fspathcmp(wt->path, path.buf)) {\n-\t\twrite_file(git_common_path(\"worktrees/%s/gitdir\", wt->id),\n-\t\t\t   \"%s/.git\", path.buf);\n-\t\tfree(wt->path);\n-\t\twt->path = strbuf_detach(&path, NULL);\n-\t}\n-\tstrbuf_release(&path);\n+\tstrbuf_realpath(&worktree_new_path, path_, 1);\n+\n+\tuse_relative_paths = repo_config_get_worktree_use_relative_paths(the_repository);\n+\tconnect_work_tree_and_git_dir(worktree_new_path.buf,\n+\t\t\t\t      git_common_path(\"worktrees/%s\", wt->id),\n+\t\t\t\t      use_relative_paths);\n+\tconnect_gitdir_file_and_work_tree(worktree_new_path.buf, wt->id,\n+\t\t\t\t\t  use_relative_paths);\n+\n+\t/* Update worktree's path */\n+\tfree(wt->path);\n+\t/*\n+\tTODO: This is setting the worktree's path to an absolute path, not\n+\tsure if always correct, especially when using relative paths.\n+\t*/\n+\twt->path = strbuf_detach(&worktree_new_path, NULL);\n+\n+\tstrbuf_release(&worktree_new_path);\n }\n \n int is_worktree_being_rebased(const struct worktree *wt,\n@@ -564,21 +564,25 @@ static void repair_gitfile(struct worktree *wt,\n {\n \tstruct strbuf dotgit = STRBUF_INIT;\n \tstruct strbuf repo = STRBUF_INIT;\n-\tchar *backlink;\n+\tchar *backlink = NULL;\n \tconst char *repair = NULL;\n+\tchar *abs_wt_path = NULL;\n \tint err;\n+\tint use_relative_paths = 0;\n+\n+\tabs_wt_path = worktree_real_pathdup_for_wt(wt);\n \n \t/* missing worktree can't be repaired */\n-\tif (!file_exists(wt->path))\n-\t\treturn;\n+\tif (!file_exists(abs_wt_path))\n+\t\tgoto done;\n \n-\tif (!is_directory(wt->path)) {\n+\tif (!is_directory(abs_wt_path)) {\n \t\tfn(1, wt->path, _(\"not a directory\"), cb_data);\n-\t\treturn;\n+\t\tgoto done;\n \t}\n \n \tstrbuf_realpath(&repo, git_common_path(\"worktrees/%s\", wt->id), 1);\n-\tstrbuf_addf(&dotgit, \"%s/.git\", wt->path);\n+\tstrbuf_addf(&dotgit, \"%s/.git\", abs_wt_path);\n \tbacklink = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err));\n \n \tif (err == READ_GITFILE_ERR_NOT_A_FILE)\n@@ -590,10 +594,16 @@ static void repair_gitfile(struct worktree *wt,\n \n \tif (repair) {\n \t\tfn(0, wt->path, repair, cb_data);\n-\t\twrite_file(dotgit.buf, \"gitdir: %s\", repo.buf);\n+\n+\t\tuse_relative_paths =\n+\t\t\trepo_config_get_worktree_use_relative_paths(the_repository);\n+\t\tconnect_work_tree_and_git_dir(abs_wt_path, repo.buf,\n+\t\t\t\t\t      use_relative_paths);\n \t}\n \n+done:\n \tfree(backlink);\n+\tfree(abs_wt_path);\n \tstrbuf_release(&repo);\n \tstrbuf_release(&dotgit);\n }\n@@ -682,9 +692,11 @@ void repair_worktree_at_path(const char *path,\n \tstruct strbuf realdotgit = STRBUF_INIT;\n \tstruct strbuf gitdir = STRBUF_INIT;\n \tstruct strbuf olddotgit = STRBUF_INIT;\n+\tstruct strbuf abs_wt_path_sb = STRBUF_INIT;\n \tchar *backlink = NULL;\n \tconst char *repair = NULL;\n \tint err;\n+\tint use_relative_paths = 0;\n \n \tif (!fn)\n \t\tfn = repair_noop;\n@@ -717,16 +729,26 @@ void repair_worktree_at_path(const char *path,\n \t\trepair = _(\"gitdir unreadable\");\n \telse {\n \t\tstrbuf_rtrim(&olddotgit);\n+\t\tif (!is_absolute_path(olddotgit.buf))\n+\t\t\tstrbuf_realpath_forgiving(&olddotgit, olddotgit.buf, 1);\n+\n \t\tif (fspathcmp(olddotgit.buf, realdotgit.buf))\n \t\t\trepair = _(\"gitdir incorrect\");\n \t}\n \n \tif (repair) {\n \t\tfn(0, gitdir.buf, repair, cb_data);\n-\t\twrite_file(gitdir.buf, \"%s\", realdotgit.buf);\n+\n+\t\tuse_relative_paths =\n+\t\t\trepo_config_get_worktree_use_relative_paths(the_repository);\n+\n+\t\tstrbuf_add_real_path(&abs_wt_path_sb, path);\n+\t\tconnect_gitdir_file_and_work_tree(abs_wt_path_sb.buf, basename(backlink),\n+\t\t\t\t\t\t  use_relative_paths);\n \t}\n done:\n \tfree(backlink);\n+\tstrbuf_release(&abs_wt_path_sb);\n \tstrbuf_release(&olddotgit);\n \tstrbuf_release(&gitdir);\n \tstrbuf_release(&realdotgit);\n\nbase-commit: 6531f31ef3bead57a3255fa08efa6e7553c5a9a7\n-- \ngitgitgadget\n"},{"id":"503177","messageId":"xmqqikupbxh5.fsf@gitster.g","threadId":"62161","inReplyTo":"pull.1783.git.git.1726880551891.gitgitgadget@gmail.com","subject":"Re: [PATCH] builtin/worktree: support relative paths for worktrees","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-09-21T01:35:02Z","receivedAt":"2024-09-21T01:35:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Francesco Guastella via GitGitGadget\" <gitgitgadget@gmail.com>\nwrites:\n\n> From: Francesco Guastella <guastella.francesco@gmail.com>\n>\n> Add a new configuration option, `worktree.useRelativePaths`, which allows\n> users to choose between storing relative or absolute paths in the files\n> Git uses to maintain links between the main worktree and any additional\n> ones.\n\nI may be misremembering things, but wasn't the use of absolute paths\na concious design decision?  \n\nWhen the source repository and an attached worktree know each other\nwith relative location, moving merely one of them would make it\nimpossible to locate the other.\n\nOn the other hand, if they know the other peer's absolute location,\nat least the one that was moved would still be able to locate the\none that did not move, which means that it is possible to find from\nthe one that moved the other one that did not move, and teach the\nlatter where the new location of the moved one is.\n\nThe only case where it may be an improvement to have them refer to\neach other with relative locations is when both of them move in\nunison without breaking their relative location.\n\nThere may be problems other than the design choice I mentioned above\nin the resulting code after applying this huge patch, but as it\nstands, the patch does a bit too many things at once to be sensibly\nreviewable.  I cannot comment much on the implementation (at least\nin this message) in its current form.\n\nFor example, the refactoring of t/t2400 into t/lib-worktree might be\nan excellent thing to do, but it looks like that the change to the\ntests alone deserves to be split into at least a few patches (one to\nrefactor the test script without changing any functionality, and one\nor more patches to add or improve test helpers, and another that\ncomes with code and behaviour change that add tests for the new\nbehaviour when configured, or something like that).\n\nI might be tempted to come back to it later, but style violations in\nthe t/lib-worktree.sh were annoying enough to discourage me from\nreviewing it (if it followed Documentation/CodingGuidelines, it\nwouldn't have repelled my eyes).\n\nThanks.\n"},{"id":"503193","messageId":"20240921145858.1987-1-guastella.francesco@gmail.com","threadId":"62161","inReplyTo":"xmqqikupbxh5.fsf@gitster.g","subject":"Re: Re: [PATCH] builtin/worktree: support relative paths for worktrees","fromName":"Francesco Guastella","fromEmail":"guastella.francesco@gmail.com","sentAt":"2024-09-21T14:58:57Z","receivedAt":"2024-09-21T14:59:04Z","isPatch":true,"sender":{"key":"guastella.francesco@gmail.com","avatar":null},"body":"Thank you for your feedback on my patch.\n\n> I may be misremembering things, but wasn't the use of absolute paths\n> a concious design decision?  \n> \n> When the source repository and an attached worktree know each other\n> with relative location, moving merely one of them would make it\n> impossible to locate the other.\n> \n> On the other hand, if they know the other peer's absolute location,\n> at least the one that was moved would still be able to locate the\n> one that did not move, which means that it is possible to find from\n> the one that moved the other one that did not move, and teach the\n> latter where the new location of the moved one is.\n> \n> The only case where it may be an improvement to have them refer to\n> each other with relative locations is when both of them move in\n> unison without breaking their relative location.\n\nRegarding the design choice between absolute and relative paths,\nI’m not certain whether using absolute paths was an intentional decision.\nYour example highlights a valid advantage of absolute paths;\nhowever, this approach is not without its limitations.\nIn my cover letter, I mentioned just a few issues related to absolute paths,\nand my online research revealed many other problems that could be mitigated\nby supporting relative paths.\n\nTo support this, many users have requested this functionality or have created\nscripts as workarounds to convert paths in Git-generated files.\nAn example from my own experience is that worktrees created on Windows are\nconsidered prunable by Git because paths starting with, for instance, ‘C:’\nhave no meaning in GNU/Linux environments.\n\nTo avoid breaking changes and maintain backward compatibility, I chose\nto extend Git by adding support for relative paths rather than replacing\nthe original behavior.\nThis approach gives users the flexibility to choose what best suits their needs.\n\nIn my experience, having the option to use relative paths is essential for\neffective worktree management.\nFor instance, not having this option has led me to avoid using worktrees\naltogether (up to now, at least :-P).\n\nIf it’s helpful, I would be glad to enhance the documentation to clearly\noutline the pros and cons of using relative paths, including\nthe valid example you provided.\n\n> There may be problems other than the design choice I mentioned above\n> in the resulting code after applying this huge patch, but as it\n> stands, the patch does a bit too many things at once to be sensibly\n> reviewable.  I cannot comment much on the implementation (at least\n> in this message) in its current form.\n> \n> For example, the refactoring of t/t2400 into t/lib-worktree might be\n> an excellent thing to do, but it looks like that the change to the\n> tests alone deserves to be split into at least a few patches (one to\n> refactor the test script without changing any functionality, and one\n> or more patches to add or improve test helpers, and another that\n> comes with code and behaviour change that add tests for the new\n> behaviour when configured, or something like that).\n\nRegarding the extensive changes in the worktree add test suite,\nI understand that this makes the review process challenging.\nI consolidated many tests into a reusable function to ensure that\nGit behaves correctly regardless of whether absolute or relative paths are chosen.\nEach test there now also includes a call to the check_worktree_paths function\nto verify the correctness of the generated paths.\n\n> I might be tempted to come back to it later, but style violations in\n> the t/lib-worktree.sh were annoying enough to discourage me from\n> reviewing it (if it followed Documentation/CodingGuidelines, it\n> wouldn't have repelled my eyes).\n\nLastly, I sincerely apologize for any coding guideline violations. I reviewed\nthe guidelines carefully and made every effort to adhere to them, so I regret any oversights.\nI will make the necessary corrections as soon as I have the opportunity.\n\nThank you again for your time and consideration of my patch.\n"},{"id":"503340","messageId":"CAPig+cQXFy=xPVpoSq6Wq0pxMRCjS=WbkgdO+3LySPX=q0nPCw@mail.gmail.com","threadId":"62161","inReplyTo":"pull.1783.git.git.1726880551891.gitgitgadget@gmail.com","subject":"Re: [PATCH] builtin/worktree: support relative paths for worktrees","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2024-09-24T08:06:25Z","receivedAt":"2024-09-24T08:06:37Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Fri, Sep 20, 2024 at 9:02 PM Francesco Guastella via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>     builtin/worktree: support relative paths for worktrees\n>\n>     As of now, when creating or managing worktrees, paths are always stored\n>     in absolute form. While this behavior works well in many scenarios, it\n>     presents significant challenges when worktrees are accessed across\n>     different operating systems or when the repository is moved to a\n>     different location in the filesystem. Absolute paths hard-coded in the\n>     .git and gitdir files can break these links, causing issues that may\n>     require manual intervention to resolve.\n>\n>     To address this, I have introduced a new configuration option:\n>     worktree.useRelativePaths. This option allows users to specify whether\n>     they prefer Git to store worktree paths in relative form rather than\n>     absolute. The new feature enhances Git’s flexibility, particularly in\n>     environments where repositories need to be portable across different\n>     systems or where directories are frequently relocated.\n>\n>     Key Changes: The new worktree.useRelativePaths option can be enabled by\n>     the user to store paths in relative form. When enabled, any new\n>     worktrees added using the git worktree add command will have their paths\n>     stored as relative paths in the necessary git files.\n>\n>     The git worktree move command has been updated to respect the current\n>     value of worktree.useRelativePaths. When a worktree is moved, Git will\n>     now automatically adjust the path format (relative or absolute) to match\n>     the user's configuration setting.\n>\n>     The git worktree repair command has been similarly enhanced. It will now\n>     automatically convert paths between relative and absolute forms based on\n>     the worktree.useRelativePaths setting, making it easier to maintain\n>     consistent links across different environments.\n\nUsing relative rather than absolute paths has been discussed\npreviously[1], and the general feeling was that relative paths would\nprobably be beneficial (despite some obvious downsides mentioned by\nJunio in his response[2]). That earlier discussion petered out without\nany changes being made partly because some stated and some implied\nquestions lacked answers.\n\nA significant concern was whether any such change could be made\nwithout breaking existing third-party tooling, non-canonical Git\nimplementations, and even older versions of Git itself. Your choice of\ncontrolling the behavior via a configuration option somewhat sidesteps\nthe issue by giving the user the choice of tolerating or not\ntolerating such potential breakage.\n\nSidestepping like that may be the best we can hope for, though it\nwould also be nice if the choice could be automated and hidden from\nthe user as an implementation detail. One idea which was floated was\nfor Git to store both the absolute and relative paths, thus leveraging\nthe benefits of both path types. But that, too, has the potential\ndownside of breaking existing tooling and other Git implementations,\nso it requires careful consideration. At any rate, it would be good to\nhear your thoughts on the idea since it might somehow fit into the\noverall scheme laid out by your work, or your work may gain additional\ndirection by taking that idea into consideration.\n\nAnyhow, below is a set of questions and observations that I jotted\ndown as I lightly scanned the patch and played around with \"git\nworktree\" after applying the patch. Many of the items may seem to\nsound negative, but do not interpret that as rejection of the idea of\nusing relative paths; as stated above earlier discussion looked upon\nrelative paths as potentially beneficial.\n\nIt's possible that the actual code changes in the patch answer some of\nthe questions I ask below, but I simply don't have the mental\nbandwidth to digest and reason about a 3,500 line patch in order to\nfigure out the answers myself. As such, any answers and responses you\ncan provide directly will be appreciated.\n\nHere are my notes...\n\nSince it is likely that third-party tools and non-canonical Git\nimplementations (and even older versions of Git itself) will break\nwith relative paths, people have created tools which let them switch\nbetween the two on-demand. This way, they can benefit from relative\npaths but still interoperate (albeit in a painfully manual fashion)\nwith those tools or implementations which would otherwise break. Does\nthis implementation provide such a tool? At first glance, based upon\nyour above commentary, it sounded as if \"git worktree repair\" would\nserve a similar purpose after a user manually changes the\nworktree.useRelativePaths setting, but that doesn't seem to be the\ncase in practice.\n\nThat leads to the next question: Should there be a dedicated\ngit-worktree subcommand which both changes the\nworktree.useRelativePaths setting and converts all the stored paths to\nreflect the new setting?\n\nYour above commentary says that \"git worktree repair\" converts between\nabsolute and relative, but it's not clear whether it does this for\n*all* worktrees or only worktrees which it fixes. Moreover, does it\nconvert both <repo>/worktrees/<id>/gitdir and <worktree>/.git in\nunison or does it only convert the path in a file it actually repairs?\nBased upon testing, I'm guessing it only does the absolute/relative\nconversion *if* it actually repairs a file, and *only* converts the\nrepaired file. As such, the links between <repo> and <worktree> can\nend up a mix of absolute and relative.\n\nAccording to your above commentary, \"git worktree repair\" converts\nbetween absolute and relative but the documentation for \"repair\" in\nDocumentation/git-worktree.txt has not been updated to reflect this\n(unlike the documentation for \"git worktree move\" which now does\nconversion and is documented as doing so).\n\nThe new worktree.useRelativePaths configuration should be documented\nin Documentation/config/worktree.txt.\n\nThe \"worktrees/<id>/gitdir::\" entry in\nDocumentation/gitrepository-layout.txt says that the contained path is\nabsolute. This needs to be updated to mention relative paths and\nworktree.useRelativePaths.\n\nThe documentation should discuss and stress the potential dangers of\nusing relative paths so that people know what they are getting\nthemselves into. In particular, it should mention the possibility of\nrelative paths breaking third-party tooling, non-canonical Git\nimplementations, and older versions of Git itself.\n\nSometimes patch authors don't bother documenting newly-added \"public\"\nAPI functions (possibly because a lot of existing API functions lack\ndocumentation), so it was nice to see this patch being thorough about\ndocumenting new API functions.\n\nIn my testing, several \"git worktree\" subcommands failed or misbehaved\nbadly when invoked from within a linked worktree. This surprised me\nsince my light scan over the patch seemed to indicate that you had\ntaken such cases into consideration, so perhaps I'm doing something\nwrong(?). For my tests, I had set up a repository and worktrees like\nthis:\n\n  % git init a\n  % cd a\n  % git config set worktree.useRelativePaths true\n  % echo content >file\n  % git add file\n  % git commit file -m msg\n  % git worktree add ../b\n  % git worktree add c\n\n\"git worktree\" subcommands are supposed to be agnostic in the sense\nthat you should be able to invoke them from within any worktree\nsuccessfully, however, with your patch applied, this seems to no\nlonger be the case. For instance:\n\n  % cd c\n  % git worktree lock --reason because ../../b\n  fatal: '../../b' is not a working tree\n\nSimilarly, when run from within a linked worktree, \"git worktree\nprune\" now thinks that *all* worktrees should be pruned:\n\n  # still in \"c\"\n  % git worktree prune -n -v\n  Removing worktrees/c: gitdir file points to non-existent location\n  Removing worktrees/b: gitdir file points to non-existent location\n\nWhen run from within a linked worktree, \"git worktree list\" shows\npaths relative to the main worktree (or bare repository):\n\n  # still in \"c\"\n  % git worktree list\n  /.../a  935238d [main]\n  ../b  935238d [b] prunable\n  c  935238d [c] prunable\n\nThis makes the direct output useless for any sort of navigation and\nespecially so in --porcelain mode for scripting purposes. To fix this,\n\"list\" should either show the absolute paths (even if they are\nmaintained as relative internally) or the paths need to be recomputed\nand shown as relative from the *current* worktree, not from the main\nworktree.\n\nAssuming I'm not doing something wrong in my testing, then do the\nabove breakages indicate some gaping holes in the test suite? If so,\nthen we probably need a bunch of new tests to ensure that the above\nbehaviors don't break when relative paths are in use.\n\nYou clearly put a lot of work and care into this patch, and (as noted\nabove) the idea of using relative paths has previously been considered\nin a reasonably positive light, so it is unlikely that reviewers want\nto lightly dismiss your patch, but at 3,500 lines, the patch is\nunreviewable. No reviewer is going to have the mental bandwidth to be\nable to remember or reason about *every* change made by this\nmonolithic patch. As the author of the patch, having developed it\nincrementally over the course of days or weeks, you have a good\noverall understanding of all the changes in the patch and the\nevolution of the code from its present state in the project to the\nstate in your fork, but reviewer time is a limited resource on this\nproject, and it is almost certainly impossible for any reviewer to be\nable to properly digest this all in a single patch.\n\nAs such, aside from answering the above questions, the way to move\nforward is to split these changes out into many small, independent,\nwell-isolated pieces, each in its own patch, and to arrange the order\nof patches so that they hand-hold and lead reviewers through the\nevolution from the current implementation to the end-state. Each patch\nshould be one step in the journey toward the end-goal, and each patch\nshould build upon the previous patch, requiring reviewers to remember\nonly one or two important items from the current patch in order to\nunderstand the subsequent patch. For a topic with so many changes,\nthis will likely require quite a few patches; it may even make sense\nto split it up into several series of patches, with each series\nsubmitted separately after reviewers have had time to digest the\npreceding series.\n\nOrganizing a patch series like this places a lot of extra work on your\nshoulders (beyond the work you already invested making the changes to\nthe code itself), but providing such hand-holding and baby-steps is\nthe only way reviewers will be able to grasp all the changes. A\nwell-organized patch series is also important for future readers of\nthe project history when they need to understand why the current\nimplementation is the way it is.\n\n[1]: The discussion begins at the \"Also Eric\" paragraph of this email\nand continues in emails following it:\nhttps://lore.kernel.org/git/CACsJy8CXEKG+WNdSPOWF7JDzPXidSRWZZ5zkdMW3N3Dg8SGW_Q@mail.gmail.com/\n\n[2]: https://lore.kernel.org/git/xmqqikupbxh5.fsf@gitster.g/\n"}]}