Skip to content

Commit 971e829

Browse files
committed
Merge branch 'jk/pathspec-literal'
Allow scripts to feed literal paths to commands that take pathspecs, by disabling wildcard globbing. * jk/pathspec-literal: add global --literal-pathspecs option Conflicts: dir.c
2 parents 29fb151 + 823ab40 commit 971e829

5 files changed

Lines changed: 115 additions & 11 deletions

File tree

Documentation/git.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,11 @@ help ...`.
428428
Do not use replacement refs to replace git objects. See
429429
linkgit:git-replace[1] for more information.
430430

431+
--literal-pathspecs::
432+
Treat pathspecs literally, rather than as glob patterns. This is
433+
equivalent to setting the `GIT_LITERAL_PATHSPECS` environment
434+
variable to `1`.
435+
431436

432437
GIT COMMANDS
433438
------------
@@ -796,6 +801,16 @@ for further details.
796801
as a file path and will try to write the trace messages
797802
into it.
798803

804+
GIT_LITERAL_PATHSPECS::
805+
Setting this variable to `1` will cause git to treat all
806+
pathspecs literally, rather than as glob patterns. For example,
807+
running `GIT_LITERAL_PATHSPECS=1 git log -- '*.c'` will search
808+
for commits that touch the path `*.c`, not any paths that the
809+
glob `*.c` matches. You might want this if you are feeding
810+
literal paths to git (e.g., paths previously given to you by
811+
`git ls-tree`, `--raw` diff output, etc).
812+
813+
799814
Discussion[[Discussion]]
800815
------------------------
801816

cache.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ static inline enum object_type object_type(unsigned int mode)
362362
#define GIT_NOTES_DISPLAY_REF_ENVIRONMENT "GIT_NOTES_DISPLAY_REF"
363363
#define GIT_NOTES_REWRITE_REF_ENVIRONMENT "GIT_NOTES_REWRITE_REF"
364364
#define GIT_NOTES_REWRITE_MODE_ENVIRONMENT "GIT_NOTES_REWRITE_MODE"
365+
#define GIT_LITERAL_PATHSPECS_ENVIRONMENT "GIT_LITERAL_PATHSPECS"
365366

366367
/*
367368
* Repository-local GIT_* environment variables
@@ -493,6 +494,8 @@ extern int init_pathspec(struct pathspec *, const char **);
493494
extern void free_pathspec(struct pathspec *);
494495
extern int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec);
495496

497+
extern int limit_pathspec_to_literal(void);
498+
496499
#define HASH_WRITE_OBJECT 1
497500
#define HASH_FORMAT_CHECK 2
498501
extern int index_fd(unsigned char *sha1, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags);

dir.c

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ static size_t common_prefix_len(const char **pathspec)
6060
{
6161
const char *n, *first;
6262
size_t max = 0;
63+
int literal = limit_pathspec_to_literal();
6364

6465
if (!pathspec)
6566
return max;
@@ -69,7 +70,7 @@ static size_t common_prefix_len(const char **pathspec)
6970
size_t i, len = 0;
7071
for (i = 0; first == n || i < max; i++) {
7172
char c = n[i];
72-
if (!c || c != first[i] || is_glob_special(c))
73+
if (!c || c != first[i] || (!literal && is_glob_special(c)))
7374
break;
7475
if (c == '/')
7576
len = i + 1;
@@ -139,6 +140,7 @@ int within_depth(const char *name, int namelen,
139140
static int match_one(const char *match, const char *name, int namelen)
140141
{
141142
int matchlen;
143+
int literal = limit_pathspec_to_literal();
142144

143145
/* If the match was just the prefix, we matched */
144146
if (!*match)
@@ -148,7 +150,7 @@ static int match_one(const char *match, const char *name, int namelen)
148150
for (;;) {
149151
unsigned char c1 = tolower(*match);
150152
unsigned char c2 = tolower(*name);
151-
if (c1 == '\0' || is_glob_special(c1))
153+
if (c1 == '\0' || (!literal && is_glob_special(c1)))
152154
break;
153155
if (c1 != c2)
154156
return 0;
@@ -160,7 +162,7 @@ static int match_one(const char *match, const char *name, int namelen)
160162
for (;;) {
161163
unsigned char c1 = *match;
162164
unsigned char c2 = *name;
163-
if (c1 == '\0' || is_glob_special(c1))
165+
if (c1 == '\0' || (!literal && is_glob_special(c1)))
164166
break;
165167
if (c1 != c2)
166168
return 0;
@@ -170,14 +172,16 @@ static int match_one(const char *match, const char *name, int namelen)
170172
}
171173
}
172174

173-
174175
/*
175176
* If we don't match the matchstring exactly,
176177
* we need to match by fnmatch
177178
*/
178179
matchlen = strlen(match);
179-
if (strncmp_icase(match, name, matchlen))
180+
if (strncmp_icase(match, name, matchlen)) {
181+
if (literal)
182+
return 0;
180183
return !fnmatch_icase(match, name, 0) ? MATCHED_FNMATCH : 0;
184+
}
181185

182186
if (namelen == matchlen)
183187
return MATCHED_EXACTLY;
@@ -1454,13 +1458,17 @@ int init_pathspec(struct pathspec *pathspec, const char **paths)
14541458

14551459
item->match = path;
14561460
item->len = strlen(path);
1457-
item->nowildcard_len = simple_length(path);
14581461
item->flags = 0;
1459-
if (item->nowildcard_len < item->len) {
1460-
pathspec->has_wildcard = 1;
1461-
if (path[item->nowildcard_len] == '*' &&
1462-
no_wildcard(path + item->nowildcard_len + 1))
1463-
item->flags |= PATHSPEC_ONESTAR;
1462+
if (limit_pathspec_to_literal()) {
1463+
item->nowildcard_len = item->len;
1464+
} else {
1465+
item->nowildcard_len = simple_length(path);
1466+
if (item->nowildcard_len < item->len) {
1467+
pathspec->has_wildcard = 1;
1468+
if (path[item->nowildcard_len] == '*' &&
1469+
no_wildcard(path + item->nowildcard_len + 1))
1470+
item->flags |= PATHSPEC_ONESTAR;
1471+
}
14641472
}
14651473
}
14661474

@@ -1475,3 +1483,11 @@ void free_pathspec(struct pathspec *pathspec)
14751483
free(pathspec->items);
14761484
pathspec->items = NULL;
14771485
}
1486+
1487+
int limit_pathspec_to_literal(void)
1488+
{
1489+
static int flag = -1;
1490+
if (flag < 0)
1491+
flag = git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT, 0);
1492+
return flag;
1493+
}

git.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,14 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
135135
git_config_push_parameter((*argv)[1]);
136136
(*argv)++;
137137
(*argc)--;
138+
} else if (!strcmp(cmd, "--literal-pathspecs")) {
139+
setenv(GIT_LITERAL_PATHSPECS_ENVIRONMENT, "1", 1);
140+
if (envchanged)
141+
*envchanged = 1;
142+
} else if (!strcmp(cmd, "--no-literal-pathspecs")) {
143+
setenv(GIT_LITERAL_PATHSPECS_ENVIRONMENT, "0", 1);
144+
if (envchanged)
145+
*envchanged = 1;
138146
} else {
139147
fprintf(stderr, "Unknown option: %s\n", cmd);
140148
usage(git_usage_string);

t/t6130-pathspec-noglob.sh

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#!/bin/sh
2+
3+
test_description='test globbing (and noglob) of pathspec limiting'
4+
. ./test-lib.sh
5+
6+
test_expect_success 'create commits with glob characters' '
7+
test_commit unrelated bar &&
8+
test_commit vanilla foo &&
9+
test_commit star "f*" &&
10+
test_commit bracket "f[o][o]"
11+
'
12+
13+
test_expect_success 'vanilla pathspec matches literally' '
14+
echo vanilla >expect &&
15+
git log --format=%s -- foo >actual &&
16+
test_cmp expect actual
17+
'
18+
19+
test_expect_success 'star pathspec globs' '
20+
cat >expect <<-\EOF &&
21+
bracket
22+
star
23+
vanilla
24+
EOF
25+
git log --format=%s -- "f*" >actual &&
26+
test_cmp expect actual
27+
'
28+
29+
test_expect_success 'bracket pathspec globs and matches literal brackets' '
30+
cat >expect <<-\EOF &&
31+
bracket
32+
vanilla
33+
EOF
34+
git log --format=%s -- "f[o][o]" >actual &&
35+
test_cmp expect actual
36+
'
37+
38+
test_expect_success 'no-glob option matches literally (vanilla)' '
39+
echo vanilla >expect &&
40+
git --literal-pathspecs log --format=%s -- foo >actual &&
41+
test_cmp expect actual
42+
'
43+
44+
test_expect_success 'no-glob option matches literally (star)' '
45+
echo star >expect &&
46+
git --literal-pathspecs log --format=%s -- "f*" >actual &&
47+
test_cmp expect actual
48+
'
49+
50+
test_expect_success 'no-glob option matches literally (bracket)' '
51+
echo bracket >expect &&
52+
git --literal-pathspecs log --format=%s -- "f[o][o]" >actual &&
53+
test_cmp expect actual
54+
'
55+
56+
test_expect_success 'no-glob environment variable works' '
57+
echo star >expect &&
58+
GIT_LITERAL_PATHSPECS=1 git log --format=%s -- "f*" >actual &&
59+
test_cmp expect actual
60+
'
61+
62+
test_done

0 commit comments

Comments
 (0)