Re: [PATCH] add core.pathspecGlob config option

3 messages, 2 authors, 2016-06-15 · open the first message on its own page

Re: [PATCH] add core.pathspecGlob config option

From: Junio C Hamano <hidden>
Date: 2016-06-15 22:55:33

Jeff King [off-list ref] writes:
... doing ":(noglob)" right would mean converting
the whole codebase to use "struct pathspec", as the usual
"const char **pathspec" cannot represent extra per-item
flags.
As that is the longer-term direction we would want to go, I'd rather
not to take the approach in this patch for introducing user-facing
support of literal pathspecs.

Having said that, even when we have the ':(noglob)' magic pathspec
support, it would make sense to introduce a command line option to
make it easier for scripted Porcelains that call plumbing commands
to pass literal pathspecs (i.e. they know exactly what paths they
want to operate, not what patterns the paths they want to operate
would match).  I do not think configuration variable makes much
sense (unless you are thinking "git -c core.literalpathspec=true"
as that command line option).

Thanks

Re: [PATCH] add core.pathspecGlob config option

From: Jeff King <hidden>
Date: 2016-06-15 22:55:33

On Wed, Dec 19, 2012 at 12:54:04PM -0800, Junio C Hamano wrote:
Jeff King [off-list ref] writes:
quoted
... doing ":(noglob)" right would mean converting
the whole codebase to use "struct pathspec", as the usual
"const char **pathspec" cannot represent extra per-item
flags.
As that is the longer-term direction we would want to go, I'd rather
not to take the approach in this patch for introducing user-facing
support of literal pathspecs.

Having said that, even when we have the ':(noglob)' magic pathspec
support, it would make sense to introduce a command line option to
make it easier for scripted Porcelains that call plumbing commands
to pass literal pathspecs (i.e. they know exactly what paths they
want to operate, not what patterns the paths they want to operate
would match).
Right, that is my use case. Even once ":(noglob)" exists, it will still
be much simpler for me to use a single option, since I would never have
mixed patterns and literal paths (and I suspect most use cases that
would care about the distinction would be in the same boat). And that is
what led me to submit this at all, as it is not just "well, it is a hack
until :(noglob) works", but "this is not as fine a granularity as
:(noglob), but it better matches what callers want".
I do not think configuration variable makes much sense (unless you are
thinking "git -c core.literalpathspec=true" as that command line
option).
The configuration variable is much more convenient for me, because
otherwise I have to instrument every call to git with a "--no-glob"
option. Because I have a very restricted environment, and happen to know
that globs will _never_ be useful on my repos (or, say, on commands run
by a particular user who has this in their ~/.gitconfig), it makes sense
to just turn off the feature with one switch.

And my thinking was that for people who are not in such a restricted
environment, "git -c core.pathspecglob=false" could serve as that
command-line option, as you mentioned.

It's perhaps a better match to make it an environment variable. Then it
is tied to a particular flow of execution, rather than having it be a
property of a system, user, or repo (which is what config does). So for
my restricted environment, it would be sufficient for me to set the
environment variable for the user who runs the scripts. And people who
want a command-line option can set it via the shell, or we can provide
"git --no-pathspec-glob" to set it.

-Peff

[PATCH] add GIT_PATHSPEC_GLOB environment variable

From: Jeff King <hidden>
Date: 2016-06-15 22:55:33

On Wed, Dec 19, 2012 at 04:09:19PM -0500, Jeff King wrote:
It's perhaps a better match to make it an environment variable. Then it
is tied to a particular flow of execution, rather than having it be a
property of a system, user, or repo (which is what config does). So for
my restricted environment, it would be sufficient for me to set the
environment variable for the user who runs the scripts. And people who
want a command-line option can set it via the shell, or we can provide
"git --no-pathspec-glob" to set it.
Here it is as an environment variable. I think this probably makes more
sense in the general case (it's a little more work for my use case, but
I think the intended use is much more obvious).

I included the common_prefix fix you mentioned (I do not think it
produced incorrect results as it was, but it did not take full advantage
of an optimization).

-- >8 --
Subject: add GIT_PATHSPEC_GLOB environment variable

Git takes pathspec arguments in many places to limit the
scope of an operation. These pathspecs are treated not as
literal paths, but as glob patterns that can be fed to
fnmatch. When a user is giving a specific pattern, this is a
nice feature.

However, when programatically providing pathspecs, it can be
a nuisance. For example, to find the latest revision which
modified "$foo", one can use "git rev-list -- $foo". But if
"$foo" contains glob characters (e.g., "f*"), it will
erroneously match more entries than desired. The caller
needs to quote the characters in $foo, and even then, the
results may not be exactly the same as with a literal
pathspec. For instance, the depth checks in
match_pathspec_depth do not kick in if we match via fnmatch.

This patch introduces an environment variable to turn all
pathspecs into literal strings. This makes it easy to turn
off the globbing behavior for a whole environment (e.g., if
you are serving repos via a web interface that is only going
to use literal programmatic pathspecs), or for a particular
run.

It cannot turn off globbing for particular pathspecs. That
could eventually be done with a ":(noglob)" magic pathspec
prefix. However, that level of granularity is not often
needed, and doing ":(noglob)" right would mean converting
the whole codebase to use "struct pathspec", as the usual
"const char **pathspec" cannot represent extra per-item
flags.

Signed-off-by: Jeff King <redacted>
---
 Documentation/git.txt      | 15 +++++++++++
 cache.h                    |  3 +++
 dir.c                      | 24 +++++++++++++-----
 git.c                      |  8 ++++++
 t/t6130-pathspec-noglob.sh | 62 ++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 106 insertions(+), 6 deletions(-)
 create mode 100755 t/t6130-pathspec-noglob.sh
diff --git a/Documentation/git.txt b/Documentation/git.txt
index 1d797f2..7008b4d 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -422,6 +422,10 @@ help ...`.
 	Do not use replacement refs to replace git objects. See
 	linkgit:git-replace[1] for more information.
 
+--no-pathspec-glob::
+	Do not treat pathspecs as glob patterns. See the section on
+	the `GIT_PATHSPEC_GLOB` environment variable below.
+
 
 GIT COMMANDS
 ------------
@@ -790,6 +794,17 @@ for further details.
 	as a file path and will try to write the trace messages
 	into it.
 
+GIT_PATHSPEC_GLOB::
+	Setting this variable to `0` will turn off the globbing features
+	of git's pathspecs. For example, running `GIT_PATHSPEC_GLOB=0 git
+	log -- '*.c'` will search for commits literally touching the
+	path `*.c`, not any paths that the glob `*.c` matches. You might
+	want this if you are feeding literal paths to git (e.g., paths
+	previously given to you by `git ls-tree`, `--raw` diff output,
+	etc). Setting it to `1` enables pathspec globbing (which is also
+	the default).
+
+
 Discussion[[Discussion]]
 ------------------------
 
diff --git a/cache.h b/cache.h
index 18fdd18..98af77c 100644
--- a/cache.h
+++ b/cache.h
@@ -362,6 +362,7 @@ static inline enum object_type object_type(unsigned int mode)
 #define GIT_NOTES_DISPLAY_REF_ENVIRONMENT "GIT_NOTES_DISPLAY_REF"
 #define GIT_NOTES_REWRITE_REF_ENVIRONMENT "GIT_NOTES_REWRITE_REF"
 #define GIT_NOTES_REWRITE_MODE_ENVIRONMENT "GIT_NOTES_REWRITE_MODE"
+#define GIT_PATHSPEC_GLOB_ENVIRONMENT "GIT_PATHSPEC_GLOB"
 
 /*
  * Repository-local GIT_* environment variables
@@ -490,6 +491,8 @@ extern int ce_path_match(const struct cache_entry *ce, const struct pathspec *pa
 extern void free_pathspec(struct pathspec *);
 extern int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec);
 
+extern int allow_pathspec_glob(void);
+
 #define HASH_WRITE_OBJECT 1
 #define HASH_FORMAT_CHECK 2
 extern int index_fd(unsigned char *sha1, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags);
diff --git a/dir.c b/dir.c
index 5a83aa7..a4d4321 100644
--- a/dir.c
+++ b/dir.c
@@ -38,6 +38,7 @@ static size_t common_prefix_len(const char **pathspec)
 {
 	const char *n, *first;
 	size_t max = 0;
+	int glob = allow_pathspec_glob();
 
 	if (!pathspec)
 		return max;
@@ -47,7 +48,7 @@ static size_t common_prefix_len(const char **pathspec)
 		size_t i, len = 0;
 		for (i = 0; first == n || i < max; i++) {
 			char c = n[i];
-			if (!c || c != first[i] || is_glob_special(c))
+			if (!c || c != first[i] || (glob && is_glob_special(c)))
 				break;
 			if (c == '/')
 				len = i + 1;
@@ -117,6 +118,7 @@ static int match_one(const char *match, const char *name, int namelen)
 static int match_one(const char *match, const char *name, int namelen)
 {
 	int matchlen;
+	int glob = allow_pathspec_glob();
 
 	/* If the match was just the prefix, we matched */
 	if (!*match)
@@ -126,7 +128,7 @@ static int match_one(const char *match, const char *name, int namelen)
 		for (;;) {
 			unsigned char c1 = tolower(*match);
 			unsigned char c2 = tolower(*name);
-			if (c1 == '\0' || is_glob_special(c1))
+			if (c1 == '\0' || (glob && is_glob_special(c1)))
 				break;
 			if (c1 != c2)
 				return 0;
@@ -138,7 +140,7 @@ static int match_one(const char *match, const char *name, int namelen)
 		for (;;) {
 			unsigned char c1 = *match;
 			unsigned char c2 = *name;
-			if (c1 == '\0' || is_glob_special(c1))
+			if (c1 == '\0' || (glob && is_glob_special(c1)))
 				break;
 			if (c1 != c2)
 				return 0;
@@ -148,14 +150,16 @@ static int match_one(const char *match, const char *name, int namelen)
 		}
 	}
 
-
 	/*
 	 * If we don't match the matchstring exactly,
 	 * we need to match by fnmatch
 	 */
 	matchlen = strlen(match);
-	if (strncmp_icase(match, name, matchlen))
+	if (strncmp_icase(match, name, matchlen)) {
+		if (!glob)
+			return 0;
 		return !fnmatch_icase(match, name, 0) ? MATCHED_FNMATCH : 0;
+	}
 
 	if (namelen == matchlen)
 		return MATCHED_EXACTLY;
@@ -1429,7 +1433,7 @@ int init_pathspec(struct pathspec *pathspec, const char **paths)
 
 		item->match = path;
 		item->len = strlen(path);
-		item->use_wildcard = !no_wildcard(path);
+		item->use_wildcard = allow_pathspec_glob() && !no_wildcard(path);
 		if (item->use_wildcard)
 			pathspec->has_wildcard = 1;
 	}
@@ -1445,3 +1449,11 @@ void free_pathspec(struct pathspec *pathspec)
 	free(pathspec->items);
 	pathspec->items = NULL;
 }
+
+int allow_pathspec_glob(void)
+{
+	static int flag = -1;
+	if (flag < 0)
+		flag = git_env_bool(GIT_PATHSPEC_GLOB_ENVIRONMENT, 1);
+	return flag;
+}
diff --git a/git.c b/git.c
index d33f9b3..db0496f 100644
--- a/git.c
+++ b/git.c
@@ -135,6 +135,14 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
 			git_config_push_parameter((*argv)[1]);
 			(*argv)++;
 			(*argc)--;
+		} else if (!strcmp(cmd, "--pathspec-glob")) {
+			setenv(GIT_PATHSPEC_GLOB_ENVIRONMENT, "1", 1);
+			if (envchanged)
+				*envchanged = 1;
+		} else if (!strcmp(cmd, "--no-pathspec-glob")) {
+			setenv(GIT_PATHSPEC_GLOB_ENVIRONMENT, "0", 1);
+			if (envchanged)
+				*envchanged = 1;
 		} else {
 			fprintf(stderr, "Unknown option: %s\n", cmd);
 			usage(git_usage_string);
diff --git a/t/t6130-pathspec-noglob.sh b/t/t6130-pathspec-noglob.sh
new file mode 100755
index 0000000..835e730
--- /dev/null
+++ b/t/t6130-pathspec-noglob.sh
@@ -0,0 +1,62 @@
+#!/bin/sh
+
+test_description='test globbing (and noglob) of pathspec limiting'
+. ./test-lib.sh
+
+test_expect_success 'create commits with glob characters' '
+	test_commit unrelated bar &&
+	test_commit vanilla foo &&
+	test_commit star "f*" &&
+	test_commit bracket "f[o][o]"
+'
+
+test_expect_success 'vanilla pathspec matches literally' '
+	echo vanilla >expect &&
+	git log --format=%s -- foo >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'star pathspec globs' '
+	cat >expect <<-\EOF &&
+	bracket
+	star
+	vanilla
+	EOF
+	git log --format=%s -- "f*" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'bracket pathspec globs and matches literal brackets' '
+	cat >expect <<-\EOF &&
+	bracket
+	vanilla
+	EOF
+	git log --format=%s -- "f[o][o]" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'no-glob option matches literally (vanilla)' '
+	echo vanilla >expect &&
+	git --no-pathspec-glob log --format=%s -- foo >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'no-glob option matches literally (star)' '
+	echo star >expect &&
+	git --no-pathspec-glob log --format=%s -- "f*" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'no-glob option matches literally (bracket)' '
+	echo bracket >expect &&
+	git --no-pathspec-glob log --format=%s -- "f[o][o]" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'no-glob environment variable works' '
+	echo star >expect &&
+	GIT_PATHSPEC_GLOB=0 git log --format=%s -- "f*" >actual &&
+	test_cmp expect actual
+'
+
+test_done
-- 
1.8.1.rc2.24.gf3bad77
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help