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

Re: [PATCH v7 2/3] repository: keep a symlink-preserving copy of the worktree path

From
Patrick Steinhardt <ps@pks.im>
Date
Jul 9, 2026, 10:09 UTC
Message-ID
<ak9zWopOWpRVHmmS@pks.im>
In-Reply-To
<20260709-includeif-worktree-v7-2-e87e705e8df6@black-desk.cn>
On Thu, Jul 09, 2026 at 10:41:42AM +0800, Chen Linxuan via B4 Relay wrote:
Show 12 quoted lines
> diff --git a/repository.c b/repository.c
> index 73d80bcffdf5..a29d55a6fcd3 100644
> --- a/repository.c
> +++ b/repository.c
> @@ -149,6 +149,11 @@ const char *repo_get_work_tree(struct repository *repo)
>  	return repo->worktree;
>  }
>  
> +const char *repo_get_work_tree_original(struct repository *repo)
> +{
> +	return repo->worktree_original;
> +}

Feels a bit heavy-handed to have such an accessor, as we could've just as well accessed the member directly via the structure.

Show 51 quoted lines
> diff --git a/setup.c b/setup.c
> index 0de56a074f7c..fbbeb95f99db 100644
> --- a/setup.c
> +++ b/setup.c
> @@ -1213,12 +1213,94 @@ static const char *setup_explicit_git_dir(struct repository *repo,
>  	return NULL;
>  }
>  
> +/*
> + * Do "a" and "b" refer to the same filesystem entry? Both must report a
> + * nonzero (dev,ino): some filesystems return (0,0) for unrelated paths,
> + * which would otherwise look identical.
> + */
> +static int same_entry(const char *a, const char *b)
> +{
> +	struct stat sa, sb;
> +
> +	if (stat(a, &sa) || stat(b, &sb))
> +		return 0;
> +	return (sa.st_dev || sa.st_ino) &&
> +	       sa.st_dev == sb.st_dev && sa.st_ino == sb.st_ino;
> +}
> +
> +/*
> + * Recover the symlink-preserving spelling of the worktree root.
> + *
> + * strbuf_add_absolute_path() already consults $PWD to keep symlinks when
> + * resolving a relative path, so set_git_work_tree()'s other callers get a
> + * symlink-preserving worktree path for free.  This function exists for the
> + * discovered-repository case: setup_git_directory_gently() chdir()s to the
> + * worktree root *before* set_git_work_tree(repo, ".") runs, so by the time
> + * "." is resolved $PWD still names the caller's original directory and no
> + * longer agrees with the physical cwd, and strbuf_add_absolute_path()
> + * falls back to the realpath.  We close that gap by deriving the logical
> + * root here, from $PWD, while we still have the original physical cwd and
> + * the root offset in hand.
> + *
> + * "cwd" is the physical current directory (getcwd), and "root_len" is the
> + * length of the worktree root within it; cwd->buf[root_len..] is therefore
> + * the part of the path below the root (empty when git ran at the root).
> + *
> + * $PWD, maintained by the shell, may spell that same directory through
> + * symlinks.  If we can confirm $PWD really names cwd's directory (same
> + * device/inode) and that the below-root suffix matches, we swap the
> + * physical root prefix for $PWD's prefix and keep the user's symlinks.
> + * Only symlinks in the root prefix itself are preserved: the below-root
> + * suffix is matched byte-for-byte, so a symlink below the root is not.
> + *
> + * Returns the allocated logical path, or NULL when $PWD is missing, already
> + * physical, or untrustworthy.
> + */
Oof.
Show 36 quoted lines
> +static char *logical_path_from_cwd(struct strbuf *cwd, int root_len)
> +{
> +	const char *pwd = getenv("PWD");
> +	size_t suffix_len, pwd_len;
> +	struct strbuf path = STRBUF_INIT;
> +
> +	if (!pwd || !is_absolute_path(pwd) || !strcmp(pwd, cwd->buf))
> +		return NULL;
> +	/*
> +	 * $PWD is a plain environment variable: it can be set to anything,
> +	 * or left stale after a chdir.  Only borrow its symlink-preserving
> +	 * spelling once we prove it still points at the same directory as
> +	 * the physical cwd; otherwise give up and return NULL.
> +	 */
> +	if (!same_entry(cwd->buf, pwd))
> +		return NULL;
> +
> +	/*
> +	 * Drop the below-root suffix from $PWD.  It must match the physical
> +	 * suffix exactly; the only spelling difference we accept is in the
> +	 * root prefix -- i.e. the symlinks we want to preserve.
> +	 */
> +	suffix_len = cwd->len - root_len;
> +	pwd_len = strlen(pwd);
> +	if (suffix_len) {
> +		const char *suffix = cwd->buf + root_len;
> +
> +		if (suffix_len > pwd_len ||
> +		    fspathcmp(pwd + pwd_len - suffix_len, suffix))
> +			return NULL;
> +		pwd_len -= suffix_len;
> +	}
> +
> +	strbuf_add(&path, pwd, pwd_len);
> +	return strbuf_detach(&path, NULL);
> +}

This feels quite awkward to me, and I assume that these changes will lead to conflicts with ps/setup-split-discovery-and-setup.

I wonder whether we can maybe avoid this whole mess by removing the call to chdir(3p) when discovering Git directories in the first place. Instead, we'd only chdir(3p) after we have fully discovered the Git repository's paths, and that may allow us to not have to worry about reconstructing the logical path?

It's something that I wanted to explore after the mentioned patch series has landed, but maybe it's something we should try to do as part of this patch series here.

Alternatively, I'm less certain that this complexity is ultimately really worth it now... so another alternative could be to document the issue and fix it at a later point in time.

Patrick
Previous: Chen Linxuan via B4 RelayNext: Chen Linxuan via B4 Relay
Message 3 of 7 in “includeIf: add "worktree" condition for matching working tree path”
  1. 0/3 includeIf: add "worktree" condition for matching working tree pathChen Linxuan via B4 Relay, Jul 9, 2026
  2. 2/3 repository: keep a symlink-preserving copy of the worktree pathChen Linxuan via B4 Relay, Jul 9, 2026
  3. Patrick SteinhardtJul 9, 2026
  4. 1/3 config: refactor include_by_gitdir() into include_by_path()Chen Linxuan via B4 Relay, Jul 9, 2026
  5. 3/3 config: add "worktree" and "worktree/i" includeIf conditionsChen Linxuan via B4 Relay, Jul 9, 2026
  6. Patrick SteinhardtJul 9, 2026
  7. Junio C HamanoJul 9, 2026

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

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