[PATCH 2/2] worktree: provide better prefix to go back to original cwd

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Nguyán ThÃi Ngác Duy <pclouds@xxxxxxxxx>

When both GIT_DIR and GIT_WORK_TREE are set, if cwd is outside
worktree, prefix (the one passed to every builtin commands) will be
set to NULL, which means "user stays at worktree topdir", but cwd is
not moved to worktree topdir.

As a consequence, command line arguments will be interpreted
differently. Assume there is a file named "bar" in foo/.git. A command
can expect command line arguments to be "path in repository":

$ GIT_DIR=foo/.git GIT_WORK_TREE=foo git blah bar

or "path in file system":

$ GIT_DIR=foo/.git GIT_WORK_TREE=foo git blah foo/bar

Some commands may want "path in repository" and "path in file system"
to be identical. Moreover, output from commands in such situations are
relative to worktree topdir (because prefix is NULL), not what users
expect. It's just confusing.

This patch does not address that. Instead it prepares some variables
to ease access to original cwd in this case. Commands that work well
outside worktree should:

 - detect this case, via is_inside_work_tree()

 - enter_work_tree() (not strictly needed, but I believe it would
   less work this way)

 - prepend startup_info->cwd_to_worktree to output so it becomes
   relative to original cwd

 - prepend startup_info->worktree_to_cwd to command line arguments
   if they are meant to access file system

Signed-off-by: Nguyán ThÃi Ngác Duy <pclouds@xxxxxxxxx>
---
 I don't care Windows UNC path. And I did not test the case
 where cwd is on C:\ while worktree is on another drive.

 As stated elsewhere, I neglect other cases where cwd may be
 outside worktree. The goal of these patches is to help
 Chris' grep series. If it works out, I'll fix the rest.

 Sorry Chris, I think I have dragged you into worktree setup
 mess.

 builtin/rev-parse.c        |   10 ++++
 cache.h                    |    2 +
 setup.c                    |  119 ++++++++++++++++++++++++++++++++++++++++++--
 t/t1510-worktree-prefix.sh |   52 +++++++++++++++++++
 4 files changed, 179 insertions(+), 4 deletions(-)
 create mode 100755 t/t1510-worktree-prefix.sh

diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index a5a1c86..525610e 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -623,6 +623,16 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
 					puts(prefix);
 				continue;
 			}
+			if (!strcmp(arg, "--cwd-to-worktree")) {
+				if (startup_info->cwd_to_worktree)
+					puts(startup_info->cwd_to_worktree);
+				continue;
+			}
+			if (!strcmp(arg, "--worktree-to-cwd")) {
+				if (startup_info->worktree_to_cwd)
+					puts(startup_info->worktree_to_cwd);
+				continue;
+			}
 			if (!strcmp(arg, "--show-cdup")) {
 				const char *pfx = prefix;
 				if (!is_inside_work_tree()) {
diff --git a/cache.h b/cache.h
index e71db23..be35b8f 100644
--- a/cache.h
+++ b/cache.h
@@ -1111,6 +1111,8 @@ const char *split_cmdline_strerror(int cmdline_errno);
 /* git.c */
 struct startup_info {
 	int have_repository;
+	char *cwd_to_worktree; /* path from original cwd to worktree */
+	char *worktree_to_cwd; /* path from worktree to original cwd */
 };
 extern struct startup_info *startup_info;
 
diff --git a/setup.c b/setup.c
index 3fbe928..1f5117a 100644
--- a/setup.c
+++ b/setup.c
@@ -320,10 +320,120 @@ const char *read_gitfile_gently(const char *path)
 	return path;
 }
 
+/*
+ * Given "foo/bar" and "hey/hello/world", return "../../hey/hello/world/"
+ * Either path1 or path2 can be NULL. All must be canonical.
+ */
+static char *make_path_to_path(const char *path1, const char *path2)
+{
+	int nr_back = 0;
+	int i, pathlen = path2 ? strlen(path2) : 0;
+	char *buf, *p;
+
+	if (path1 && *path1) {
+		nr_back = 1;
+		while (*path1) {
+			if (is_dir_sep(*path1))
+				nr_back++;
+			path1++;
+		}
+	}
+
+	if (!nr_back && !pathlen)
+		return NULL;
+
+	p = buf = xmalloc(3*nr_back + pathlen + 2); /* "../"^nr_back + path2 + '/' + NULL */
+	for (i = 0; i < nr_back; i++) {
+		memcpy(p, "../", 3);
+		p += 3;
+	}
+	if (pathlen) {
+		memcpy(p, path2, pathlen);
+		p += pathlen;
+		if (p[-1] != '/') /* path2 may be c:/, don't put another slash  */
+			*p++ = '/';
+	}
+	*p = '\0';
+	return buf;
+}
+
+/*
+ * Return a prefix if cwd inside worktree, or NULL otherwise.
+ * Also fill startup_info struct.
+ */
+static const char *setup_prefix(const char *cwd)
+{
+	const char *worktree = get_git_work_tree();
+	int len = 0, cwd_len = strlen(cwd), worktree_len = strlen(worktree);
+
+	while (worktree[len] && worktree[len] == cwd[len])
+		len++;
+
+	if (!worktree[len] && !cwd[len]) {
+		if (startup_info) {
+			startup_info->cwd_to_worktree = NULL;
+			startup_info->worktree_to_cwd = NULL;
+		}
+		return NULL;
+	}
+	/* get /foo/, not /foo/baa if /foo/baa1 and /foo/baa2 are given */
+	else if (worktree[len] && cwd[len]) {
+		while (len && !is_dir_sep(worktree[len]))
+			len--;
+		len++;
+
+		/* Worktree and cwd are on different drives? */
+		if (len == 3 && has_dos_drive_prefix(cwd)) {
+			if (startup_info) {
+				/* make_path_to_path will add the trailing slash */
+				startup_info->cwd_to_worktree = make_path_to_path(NULL, worktree);
+				startup_info->worktree_to_cwd = make_path_to_path(NULL, cwd);
+			}
+			return NULL;
+		}
+	}
+	else {
+		if (worktree[len]) {
+			if (!is_dir_sep(worktree[len])) {
+				while (len && !is_dir_sep(worktree[len]))
+					len--;
+			}
+		}
+		else {
+			if (!is_dir_sep(cwd[len])) {
+				while (len && !is_dir_sep(cwd[len]))
+					len--;
+			}
+		}
+		len++;		/* must be a slash here, skip it */
+	}
+
+	if (len < cwd_len && len < worktree_len) {
+		if (startup_info) {
+			startup_info->cwd_to_worktree = make_path_to_path(cwd+len, worktree+len);
+			startup_info->worktree_to_cwd = make_path_to_path(worktree+len, cwd+len);
+		}
+		return NULL;
+	}
+
+	if (startup_info) {
+		if (len < cwd_len) { /* cwd inside worktree */
+			startup_info->cwd_to_worktree = make_path_to_path(cwd+len, NULL);
+			startup_info->worktree_to_cwd = make_path_to_path(NULL, cwd+len);
+		}
+		else {
+			startup_info->cwd_to_worktree = make_path_to_path(NULL, worktree+len);
+			startup_info->worktree_to_cwd = make_path_to_path(worktree+len, NULL);
+		}
+	}
+
+	return len < cwd_len ? cwd+len : NULL;
+}
+
 static const char *setup_explicit_git_dir(const char *gitdirenv,
 				const char *work_tree_env, int *nongit_ok)
 {
-	static char buffer[1024 + 1];
+	static char buffer[PATH_MAX];
 	const char *retval;
 
 	if (PATH_MAX - 40 < strlen(gitdirenv))
@@ -344,9 +454,10 @@ static const char *setup_explicit_git_dir(const char *gitdirenv,
 	}
 	if (check_repository_format_gently(nongit_ok))
 		return NULL;
-	retval = get_relative_cwd(buffer, sizeof(buffer) - 1,
-			get_git_work_tree());
-	if (!retval || !*retval)
+	if (!getcwd(buffer, sizeof(buffer)))
+		die_errno("can't find the current directory");
+	retval = setup_prefix(buffer);
+	if (!retval)
 		return NULL;
 	set_git_dir(make_absolute_path(gitdirenv));
 	if (chdir(work_tree_env) < 0)
diff --git a/t/t1510-worktree-prefix.sh b/t/t1510-worktree-prefix.sh
new file mode 100755
index 0000000..3839493
--- /dev/null
+++ b/t/t1510-worktree-prefix.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+test_description='test rev-parse --cwd-to-worktree and --worktree-to-cwd'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	mkdir foo bar &&
+	mv .git foo &&
+	mkdir foo/bar &&
+	GIT_DIR=`pwd`/foo/.git &&
+	GIT_WORK_TREE=`pwd`/foo &&
+	export GIT_DIR GIT_WORK_TREE
+'
+
+test_expect_success 'at root' '
+	(
+	cd foo &&
+	git rev-parse --cwd-to-worktree --worktree-to-cwd >result &&
+	: >expected &&
+	test_cmp expected result
+	)
+'
+
+test_expect_success 'cwd inside worktree' '
+	(
+	cd foo/bar &&
+	git rev-parse --cwd-to-worktree --worktree-to-cwd >result &&
+	echo ../ >expected &&
+	echo bar/ >>expected &&
+	test_cmp expected result
+	)
+'
+
+test_expect_success 'cwd outside worktree' '
+	git rev-parse --cwd-to-worktree --worktree-to-cwd >result &&
+	echo foo/ >expected &&
+	echo ../ >>expected &&
+	test_cmp expected result
+'
+
+test_expect_success 'cwd outside worktree (2)' '
+	(
+	cd bar &&
+	git rev-parse --cwd-to-worktree --worktree-to-cwd >result &&
+	echo ../foo/ >expected &&
+	echo ../bar/ >>expected &&
+	test_cmp expected result
+	)
+'
+
+test_done
-- 
1.7.1.rc1.70.g788ca

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]