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