Prepending prefix to pathspec is a trick to workaround the fact that
commands can be executed in a subdirectory, but all git commands run
at worktree's root. The prefix part should always be treated as
literal string. Make it so.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclo...@gmail.com>
---
 cache.h    |  2 ++
 path.c     | 15 ++++++++++++++-
 pathspec.c | 21 +++++++++++++++++----
 pathspec.h |  2 +-
 setup.c    | 24 ++++++++++++++++++++----
 5 files changed, 54 insertions(+), 10 deletions(-)

diff --git a/cache.h b/cache.h
index b0ed117..13e3c94 100644
--- a/cache.h
+++ b/cache.h
@@ -414,6 +414,7 @@ extern void setup_work_tree(void);
 extern const char *setup_git_directory_gently(int *);
 extern const char *setup_git_directory(void);
 extern char *prefix_path(const char *prefix, int len, const char *path);
+extern char *prefix_path_gently(const char *prefix, int len, int *remaining, 
const char *path);
 extern const char *prefix_filename(const char *prefix, int len, const char 
*path);
 extern int check_filename(const char *prefix, const char *name);
 extern void verify_filename(const char *prefix,
@@ -741,6 +742,7 @@ const char *real_path(const char *path);
 const char *real_path_if_valid(const char *path);
 const char *absolute_path(const char *path);
 const char *relative_path(const char *abs, const char *base);
+int normalize_path_copy_len(char *dst, const char *src, int *prefix_len);
 int normalize_path_copy(char *dst, const char *src);
 int longest_ancestor_length(const char *path, struct string_list *prefixes);
 char *strip_path_suffix(const char *path, const char *suffix);
diff --git a/path.c b/path.c
index 04ff148..f4b49d6 100644
--- a/path.c
+++ b/path.c
@@ -492,8 +492,14 @@ const char *relative_path(const char *abs, const char 
*base)
  *
  * Note that this function is purely textual.  It does not follow symlinks,
  * verify the existence of the path, or make any system calls.
+ *
+ * prefix_len != NULL is for a specific case of prefix_pathspec():
+ * assume that src == dst and src[0..prefix_len-1] is already
+ * normalized, any time "../" eats up to the prefix_len part,
+ * prefix_len is reduced. In the end prefix_len is the remaining
+ * prefix that has not been overridden by user pathspec.
  */
-int normalize_path_copy(char *dst, const char *src)
+int normalize_path_copy_len(char *dst, const char *src, int *prefix_len)
 {
        char *dst0;
 
@@ -568,11 +574,18 @@ int normalize_path_copy(char *dst, const char *src)
                /* Windows: dst[-1] cannot be backslash anymore */
                while (dst0 < dst && dst[-1] != '/')
                        dst--;
+               if (prefix_len && *prefix_len > dst - dst0)
+                       *prefix_len = dst - dst0;
        }
        *dst = '\0';
        return 0;
 }
 
+int normalize_path_copy(char *dst, const char *src)
+{
+       return normalize_path_copy_len(dst, src, NULL);
+}
+
 /*
  * path = Canonical absolute path
  * prefixes = string_list containing normalized, absolute paths without
diff --git a/pathspec.c b/pathspec.c
index da802e2..71e5eaf 100644
--- a/pathspec.c
+++ b/pathspec.c
@@ -150,10 +150,14 @@ static unsigned prefix_pathspec(struct pathspec_item 
*item,
        magic |= short_magic;
        *p_short_magic = short_magic;
 
-       if (magic & PATHSPEC_FROMTOP)
+       if (magic & PATHSPEC_FROMTOP) {
                match = xstrdup(copyfrom);
-       else
-               match = prefix_path(prefix, prefixlen, copyfrom);
+               prefixlen = 0;
+       } else {
+               match = prefix_path_gently(prefix, prefixlen, &prefixlen, 
copyfrom);
+               if (!match)
+                       die(_("%s: '%s' is outside repository"), elt, copyfrom);
+       }
        *raw = item->match = match;
        /*
         * Prefix the pathspec (keep all magic) and assign to
@@ -167,6 +171,7 @@ static unsigned prefix_pathspec(struct pathspec_item *item,
        } else
                item->original = elt;
        item->len = strlen(item->match);
+       item->prefix = prefixlen;
 
        if ((flags & PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP) &&
            (item->len >= 1 && item->match[item->len - 1] == '/') &&
@@ -198,13 +203,20 @@ static unsigned prefix_pathspec(struct pathspec_item 
*item,
 
        if (limit_pathspec_to_literal())
                item->nowildcard_len = item->len;
-       else
+       else {
                item->nowildcard_len = simple_length(item->match);
+               if (item->nowildcard_len < prefixlen)
+                       item->nowildcard_len = prefixlen;
+       }
        item->flags = 0;
        if (item->nowildcard_len < item->len &&
            item->match[item->nowildcard_len] == '*' &&
            no_wildcard(item->match + item->nowildcard_len + 1))
                item->flags |= PATHSPEC_ONESTAR;
+
+       /* sanity checks, pathspec matchers assume these are sane */
+       assert(item->nowildcard_len <= item->len &&
+              item->prefix         <= item->len);
        return magic;
 }
 
@@ -284,6 +296,7 @@ void parse_pathspec(struct pathspec *pathspec,
                item->match = prefix;
                item->original = prefix;
                item->nowildcard_len = item->len = strlen(prefix);
+               item->prefix = item->len;
                raw[0] = prefix;
                raw[1] = NULL;
                pathspec->nr = 1;
diff --git a/pathspec.h b/pathspec.h
index 0f6739d..2f3532e 100644
--- a/pathspec.h
+++ b/pathspec.h
@@ -21,7 +21,7 @@ struct pathspec {
                const char *match;
                const char *original;
                unsigned magic;
-               int len;
+               int len, prefix;
                int nowildcard_len;
                int flags;
        } *items;
diff --git a/setup.c b/setup.c
index d1ece5d..ff4499e 100644
--- a/setup.c
+++ b/setup.c
@@ -5,7 +5,19 @@
 static int inside_git_dir = -1;
 static int inside_work_tree = -1;
 
-static char *prefix_path_gently(const char *prefix, int len, const char *path)
+/*
+ * Normalize "path", prepending the "prefix" for relative paths. If
+ * remaining_prefix is not NULL, return the actual prefix still
+ * remains in the path. For example, prefix = sub1/sub2/ and path is
+ *
+ *  foo          -> sub1/sub2/foo  (full prefix)
+ *  ../foo       -> sub1/foo       (remaining prefix is sub1/)
+ *  ../../bar    -> bar            (no remaining prefix)
+ *  ../../sub1/sub2/foo -> sub1/sub2/foo (but no remaining prefix)
+ *  `pwd`/../bar -> sub1/bar       (no remaining prefix)
+ */
+char *prefix_path_gently(const char *prefix, int len,
+                        int *remaining_prefix, const char *path)
 {
        const char *orig = path;
        char *sanitized;
@@ -13,13 +25,17 @@ static char *prefix_path_gently(const char *prefix, int 
len, const char *path)
                const char *temp = real_path(path);
                sanitized = xmalloc(len + strlen(temp) + 1);
                strcpy(sanitized, temp);
+               if (remaining_prefix)
+                       *remaining_prefix = 0;
        } else {
                sanitized = xmalloc(len + strlen(path) + 1);
                if (len)
                        memcpy(sanitized, prefix, len);
                strcpy(sanitized + len, path);
+               if (remaining_prefix)
+                       *remaining_prefix = len;
        }
-       if (normalize_path_copy(sanitized, sanitized))
+       if (normalize_path_copy_len(sanitized, sanitized, remaining_prefix))
                goto error_out;
        if (is_absolute_path(orig)) {
                size_t root_len, len, total;
@@ -44,7 +60,7 @@ static char *prefix_path_gently(const char *prefix, int len, 
const char *path)
 
 char *prefix_path(const char *prefix, int len, const char *path)
 {
-       char *r = prefix_path_gently(prefix, len, path);
+       char *r = prefix_path_gently(prefix, len, NULL, path);
        if (!r)
                die("'%s' is outside repository", path);
        return r;
@@ -53,7 +69,7 @@ char *prefix_path(const char *prefix, int len, const char 
*path)
 int path_inside_repo(const char *prefix, const char *path)
 {
        int len = prefix ? strlen(prefix) : 0;
-       char *r = prefix_path_gently(prefix, len, path);
+       char *r = prefix_path_gently(prefix, len, NULL, path);
        if (r) {
                free(r);
                return 1;
-- 
1.8.2.83.gc99314b

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

Reply via email to