Skip to content

Commit d8b0ec4

Browse files
committed
Merge branch 'jw/git-add-attr-pathspec'
"git add" and "git stash" learned to support the ":(attr:...)" magic pathspec. * jw/git-add-attr-pathspec: attr: enable attr pathspec magic for git-add and git-stash
2 parents 34401b7 + 1164c72 commit d8b0ec4

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_grep "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_grep "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)