Skip to content

Commit 1164c72

Browse files
jowang4105gitster
authored andcommitted
attr: enable attr pathspec magic for git-add and git-stash
Allow users to limit or exclude files based on file attributes during git-add and git-stash. For example, the chromium project would like to use $ git add . ':(exclude,attr:submodule)' as submodules are managed by an external tool, forbidding end users to record changes with "git add". Allowing "git add" to often records changes that users do not want in their commits. This commit does not change any attr magic implementation. It is only adding attr as an allowed pathspec in git-add and git-stash, which was previously blocked by GUARD_PATHSPEC and a pathspec mask in parse_pathspec()). However, we fix a bug in prefix_magic() where attr values were unintentionally removed. This was triggerable when parse_pathspec() is called with PATHSPEC_PREFIX_ORIGIN as a flag, which was the case for git-stash (Bug originally filed here [*]) Furthermore, while other commands hit this code path it did not result in unexpected behavior because this bug only impacts the pathspec->items->original field which is NOT used to filter paths. However, git-stash does use pathspec->items->original when building args used to call other git commands. (See add_pathspecs() usage and implementation in stash.c) It is possible that when the attr pathspec feature was first added in b0db704 (pathspec: allow querying for attributes, 2017-03-13), "PATHSPEC_ATTR" was just unintentionally left out of a few GUARD_PATHSPEC() invocations. Later, to get a more user-friendly error message when attr was used with git-add, PATHSPEC_ATTR was added as a mask to git-add's invocation of parse_pathspec() 84d938b (add: do not accept pathspec magic 'attr', 2018-09-18). However, this user-friendly error message was never added for git-stash. [Reference] * https://lore.kernel.org/git/CAMmZTi-0QKtj7Q=sbC5qhipGsQxJFOY-Qkk1jfkRYwfF5FcUVg@mail.gmail.com/) Signed-off-by: Joanna Wang <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 692be87 commit 1164c72

File tree

4 files changed

+139
-18
lines changed

4 files changed

+139
-18
lines changed

builtin/add.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
424424
* Check the "pathspec '%s' did not match any files" block
425425
* below before enabling new magic.
426426
*/
427-
parse_pathspec(&pathspec, PATHSPEC_ATTR,
427+
parse_pathspec(&pathspec, 0,
428428
PATHSPEC_PREFER_FULL |
429429
PATHSPEC_SYMLINK_LEADING_PATH,
430430
prefix, argv);
@@ -433,7 +433,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
433433
if (pathspec.nr)
434434
die(_("'%s' and pathspec arguments cannot be used together"), "--pathspec-from-file");
435435

436-
parse_pathspec_file(&pathspec, PATHSPEC_ATTR,
436+
parse_pathspec_file(&pathspec, 0,
437437
PATHSPEC_PREFER_FULL |
438438
PATHSPEC_SYMLINK_LEADING_PATH,
439439
prefix, pathspec_from_file, pathspec_file_nul);
@@ -504,7 +504,8 @@ int cmd_add(int argc, const char **argv, const char *prefix)
504504
PATHSPEC_LITERAL |
505505
PATHSPEC_GLOB |
506506
PATHSPEC_ICASE |
507-
PATHSPEC_EXCLUDE);
507+
PATHSPEC_EXCLUDE |
508+
PATHSPEC_ATTR);
508509

509510
for (i = 0; i < pathspec.nr; i++) {
510511
const char *path = pathspec.items[i].match;

dir.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2179,7 +2179,8 @@ static int exclude_matches_pathspec(const char *path, int pathlen,
21792179
PATHSPEC_LITERAL |
21802180
PATHSPEC_GLOB |
21812181
PATHSPEC_ICASE |
2182-
PATHSPEC_EXCLUDE);
2182+
PATHSPEC_EXCLUDE |
2183+
PATHSPEC_ATTR);
21832184

21842185
for (i = 0; i < pathspec->nr; i++) {
21852186
const struct pathspec_item *item = &pathspec->items[i];

pathspec.c

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -109,16 +109,37 @@ static struct pathspec_magic {
109109
{ PATHSPEC_ATTR, '\0', "attr" },
110110
};
111111

112-
static void prefix_magic(struct strbuf *sb, int prefixlen, unsigned magic)
112+
static void prefix_magic(struct strbuf *sb, int prefixlen,
113+
unsigned magic, const char *element)
113114
{
114-
int i;
115-
strbuf_addstr(sb, ":(");
116-
for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
117-
if (magic & pathspec_magic[i].bit) {
118-
if (sb->buf[sb->len - 1] != '(')
119-
strbuf_addch(sb, ',');
120-
strbuf_addstr(sb, pathspec_magic[i].name);
115+
/* No magic was found in element, just add prefix magic */
116+
if (!magic) {
117+
strbuf_addf(sb, ":(prefix:%d)", prefixlen);
118+
return;
119+
}
120+
121+
/*
122+
* At this point, we know that parse_element_magic() was able
123+
* to extract some pathspec magic from element. So we know
124+
* element is correctly formatted in either shorthand or
125+
* longhand form
126+
*/
127+
if (element[1] != '(') {
128+
/* Process an element in shorthand form (e.g. ":!/<match>") */
129+
strbuf_addstr(sb, ":(");
130+
for (int i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {
131+
if ((magic & pathspec_magic[i].bit) &&
132+
pathspec_magic[i].mnemonic) {
133+
if (sb->buf[sb->len - 1] != '(')
134+
strbuf_addch(sb, ',');
135+
strbuf_addstr(sb, pathspec_magic[i].name);
136+
}
121137
}
138+
} else {
139+
/* For the longhand form, we copy everything up to the final ')' */
140+
size_t len = strchr(element, ')') - element;
141+
strbuf_add(sb, element, len);
142+
}
122143
strbuf_addf(sb, ",prefix:%d)", prefixlen);
123144
}
124145

@@ -493,7 +514,7 @@ static void init_pathspec_item(struct pathspec_item *item, unsigned flags,
493514
struct strbuf sb = STRBUF_INIT;
494515

495516
/* Preserve the actual prefix length of each pattern */
496-
prefix_magic(&sb, prefixlen, element_magic);
517+
prefix_magic(&sb, prefixlen, element_magic, elt);
497518

498519
strbuf_addstr(&sb, match);
499520
item->original = strbuf_detach(&sb, NULL);

t/t6135-pathspec-with-attrs.sh

Lines changed: 103 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,24 @@ test_expect_success 'setup .gitattributes' '
6464
fileSetLabel label
6565
fileValue label=foo
6666
fileWrongLabel label☺
67+
newFileA* labelA
68+
newFileB* labelB
6769
EOF
6870
echo fileSetLabel label1 >sub/.gitattributes &&
6971
git add .gitattributes sub/.gitattributes &&
7072
git commit -m "add attributes"
7173
'
7274

75+
test_expect_success 'setup .gitignore' '
76+
cat <<-\EOF >.gitignore &&
77+
actual
78+
expect
79+
pathspec_file
80+
EOF
81+
git add .gitignore &&
82+
git commit -m "add gitignore"
83+
'
84+
7385
test_expect_success 'check specific set attr' '
7486
cat <<-\EOF >expect &&
7587
fileSetLabel
@@ -150,6 +162,7 @@ test_expect_success 'check specific value attr (2)' '
150162
test_expect_success 'check unspecified attr' '
151163
cat <<-\EOF >expect &&
152164
.gitattributes
165+
.gitignore
153166
fileA
154167
fileAB
155168
fileAC
@@ -175,6 +188,7 @@ test_expect_success 'check unspecified attr' '
175188
test_expect_success 'check unspecified attr (2)' '
176189
cat <<-\EOF >expect &&
177190
HEAD:.gitattributes
191+
HEAD:.gitignore
178192
HEAD:fileA
179193
HEAD:fileAB
180194
HEAD:fileAC
@@ -200,6 +214,7 @@ test_expect_success 'check unspecified attr (2)' '
200214
test_expect_success 'check multiple unspecified attr' '
201215
cat <<-\EOF >expect &&
202216
.gitattributes
217+
.gitignore
203218
fileC
204219
fileNoLabel
205220
fileWrongLabel
@@ -239,16 +254,99 @@ test_expect_success 'fail on multiple attr specifiers in one pathspec item' '
239254
test_i18ngrep "Only one" actual
240255
'
241256

242-
test_expect_success 'fail if attr magic is used places not implemented' '
257+
test_expect_success 'fail if attr magic is used in places not implemented' '
243258
# The main purpose of this test is to check that we actually fail
244259
# when you attempt to use attr magic in commands that do not implement
245-
# attr magic. This test does not advocate git-add to stay that way,
246-
# though, but git-add is convenient as it has its own internal pathspec
247-
# parsing.
248-
test_must_fail git add ":(attr:labelB)" 2>actual &&
260+
# attr magic. This test does not advocate check-ignore to stay that way.
261+
# When you teach the command to grok the pathspec, you need to find
262+
# another command to replace it for the test.
263+
test_must_fail git check-ignore ":(attr:labelB)" 2>actual &&
249264
test_i18ngrep "magic not supported" actual
250265
'
251266

267+
test_expect_success 'check that attr magic works for git stash push' '
268+
cat <<-\EOF >expect &&
269+
A sub/newFileA-foo
270+
EOF
271+
>sub/newFileA-foo &&
272+
>sub/newFileB-foo &&
273+
git stash push --include-untracked -- ":(exclude,attr:labelB)" &&
274+
git stash show --include-untracked --name-status >actual &&
275+
test_cmp expect actual
276+
'
277+
278+
test_expect_success 'check that attr magic works for git add --all' '
279+
cat <<-\EOF >expect &&
280+
sub/newFileA-foo
281+
EOF
282+
>sub/newFileA-foo &&
283+
>sub/newFileB-foo &&
284+
git add --all ":(exclude,attr:labelB)" &&
285+
git diff --name-only --cached >actual &&
286+
git restore -W -S . &&
287+
test_cmp expect actual
288+
'
289+
290+
test_expect_success 'check that attr magic works for git add -u' '
291+
cat <<-\EOF >expect &&
292+
sub/fileA
293+
EOF
294+
>sub/newFileA-foo &&
295+
>sub/newFileB-foo &&
296+
>sub/fileA &&
297+
>sub/fileB &&
298+
git add -u ":(exclude,attr:labelB)" &&
299+
git diff --name-only --cached >actual &&
300+
git restore -S -W . && rm sub/new* &&
301+
test_cmp expect actual
302+
'
303+
304+
test_expect_success 'check that attr magic works for git add <path>' '
305+
cat <<-\EOF >expect &&
306+
fileA
307+
fileB
308+
sub/fileA
309+
EOF
310+
>fileA &&
311+
>fileB &&
312+
>sub/fileA &&
313+
>sub/fileB &&
314+
git add ":(exclude,attr:labelB)sub/*" &&
315+
git diff --name-only --cached >actual &&
316+
git restore -S -W . &&
317+
test_cmp expect actual
318+
'
319+
320+
test_expect_success 'check that attr magic works for git -add .' '
321+
cat <<-\EOF >expect &&
322+
sub/fileA
323+
EOF
324+
>fileA &&
325+
>fileB &&
326+
>sub/fileA &&
327+
>sub/fileB &&
328+
cd sub &&
329+
git add . ":(exclude,attr:labelB)" &&
330+
cd .. &&
331+
git diff --name-only --cached >actual &&
332+
git restore -S -W . &&
333+
test_cmp expect actual
334+
'
335+
336+
test_expect_success 'check that attr magic works for git add --pathspec-from-file' '
337+
cat <<-\EOF >pathspec_file &&
338+
:(exclude,attr:labelB)
339+
EOF
340+
cat <<-\EOF >expect &&
341+
sub/newFileA-foo
342+
EOF
343+
>sub/newFileA-foo &&
344+
>sub/newFileB-foo &&
345+
git add --all --pathspec-from-file=pathspec_file &&
346+
git diff --name-only --cached >actual &&
347+
test_cmp expect actual
348+
'
349+
252350
test_expect_success 'abort on giving invalid label on the command line' '
253351
test_must_fail git ls-files . ":(attr:☺)"
254352
'

0 commit comments

Comments
 (0)