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-stashmaint
commit
d8b0ec44b1
|
@ -424,7 +424,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
|
||||||
* Check the "pathspec '%s' did not match any files" block
|
* Check the "pathspec '%s' did not match any files" block
|
||||||
* below before enabling new magic.
|
* below before enabling new magic.
|
||||||
*/
|
*/
|
||||||
parse_pathspec(&pathspec, PATHSPEC_ATTR,
|
parse_pathspec(&pathspec, 0,
|
||||||
PATHSPEC_PREFER_FULL |
|
PATHSPEC_PREFER_FULL |
|
||||||
PATHSPEC_SYMLINK_LEADING_PATH,
|
PATHSPEC_SYMLINK_LEADING_PATH,
|
||||||
prefix, argv);
|
prefix, argv);
|
||||||
|
@ -433,7 +433,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
|
||||||
if (pathspec.nr)
|
if (pathspec.nr)
|
||||||
die(_("'%s' and pathspec arguments cannot be used together"), "--pathspec-from-file");
|
die(_("'%s' and pathspec arguments cannot be used together"), "--pathspec-from-file");
|
||||||
|
|
||||||
parse_pathspec_file(&pathspec, PATHSPEC_ATTR,
|
parse_pathspec_file(&pathspec, 0,
|
||||||
PATHSPEC_PREFER_FULL |
|
PATHSPEC_PREFER_FULL |
|
||||||
PATHSPEC_SYMLINK_LEADING_PATH,
|
PATHSPEC_SYMLINK_LEADING_PATH,
|
||||||
prefix, pathspec_from_file, pathspec_file_nul);
|
prefix, pathspec_from_file, pathspec_file_nul);
|
||||||
|
@ -504,7 +504,8 @@ int cmd_add(int argc, const char **argv, const char *prefix)
|
||||||
PATHSPEC_LITERAL |
|
PATHSPEC_LITERAL |
|
||||||
PATHSPEC_GLOB |
|
PATHSPEC_GLOB |
|
||||||
PATHSPEC_ICASE |
|
PATHSPEC_ICASE |
|
||||||
PATHSPEC_EXCLUDE);
|
PATHSPEC_EXCLUDE |
|
||||||
|
PATHSPEC_ATTR);
|
||||||
|
|
||||||
for (i = 0; i < pathspec.nr; i++) {
|
for (i = 0; i < pathspec.nr; i++) {
|
||||||
const char *path = pathspec.items[i].match;
|
const char *path = pathspec.items[i].match;
|
||||||
|
|
3
dir.c
3
dir.c
|
@ -2179,7 +2179,8 @@ static int exclude_matches_pathspec(const char *path, int pathlen,
|
||||||
PATHSPEC_LITERAL |
|
PATHSPEC_LITERAL |
|
||||||
PATHSPEC_GLOB |
|
PATHSPEC_GLOB |
|
||||||
PATHSPEC_ICASE |
|
PATHSPEC_ICASE |
|
||||||
PATHSPEC_EXCLUDE);
|
PATHSPEC_EXCLUDE |
|
||||||
|
PATHSPEC_ATTR);
|
||||||
|
|
||||||
for (i = 0; i < pathspec->nr; i++) {
|
for (i = 0; i < pathspec->nr; i++) {
|
||||||
const struct pathspec_item *item = &pathspec->items[i];
|
const struct pathspec_item *item = &pathspec->items[i];
|
||||||
|
|
31
pathspec.c
31
pathspec.c
|
@ -109,16 +109,37 @@ static struct pathspec_magic {
|
||||||
{ PATHSPEC_ATTR, '\0', "attr" },
|
{ PATHSPEC_ATTR, '\0', "attr" },
|
||||||
};
|
};
|
||||||
|
|
||||||
static void prefix_magic(struct strbuf *sb, int prefixlen, unsigned magic)
|
static void prefix_magic(struct strbuf *sb, int prefixlen,
|
||||||
|
unsigned magic, const char *element)
|
||||||
{
|
{
|
||||||
int i;
|
/* No magic was found in element, just add prefix magic */
|
||||||
|
if (!magic) {
|
||||||
|
strbuf_addf(sb, ":(prefix:%d)", prefixlen);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* At this point, we know that parse_element_magic() was able
|
||||||
|
* to extract some pathspec magic from element. So we know
|
||||||
|
* element is correctly formatted in either shorthand or
|
||||||
|
* longhand form
|
||||||
|
*/
|
||||||
|
if (element[1] != '(') {
|
||||||
|
/* Process an element in shorthand form (e.g. ":!/<match>") */
|
||||||
strbuf_addstr(sb, ":(");
|
strbuf_addstr(sb, ":(");
|
||||||
for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
|
for (int i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {
|
||||||
if (magic & pathspec_magic[i].bit) {
|
if ((magic & pathspec_magic[i].bit) &&
|
||||||
|
pathspec_magic[i].mnemonic) {
|
||||||
if (sb->buf[sb->len - 1] != '(')
|
if (sb->buf[sb->len - 1] != '(')
|
||||||
strbuf_addch(sb, ',');
|
strbuf_addch(sb, ',');
|
||||||
strbuf_addstr(sb, pathspec_magic[i].name);
|
strbuf_addstr(sb, pathspec_magic[i].name);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* For the longhand form, we copy everything up to the final ')' */
|
||||||
|
size_t len = strchr(element, ')') - element;
|
||||||
|
strbuf_add(sb, element, len);
|
||||||
|
}
|
||||||
strbuf_addf(sb, ",prefix:%d)", prefixlen);
|
strbuf_addf(sb, ",prefix:%d)", prefixlen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -493,7 +514,7 @@ static void init_pathspec_item(struct pathspec_item *item, unsigned flags,
|
||||||
struct strbuf sb = STRBUF_INIT;
|
struct strbuf sb = STRBUF_INIT;
|
||||||
|
|
||||||
/* Preserve the actual prefix length of each pattern */
|
/* Preserve the actual prefix length of each pattern */
|
||||||
prefix_magic(&sb, prefixlen, element_magic);
|
prefix_magic(&sb, prefixlen, element_magic, elt);
|
||||||
|
|
||||||
strbuf_addstr(&sb, match);
|
strbuf_addstr(&sb, match);
|
||||||
item->original = strbuf_detach(&sb, NULL);
|
item->original = strbuf_detach(&sb, NULL);
|
||||||
|
|
|
@ -64,12 +64,24 @@ test_expect_success 'setup .gitattributes' '
|
||||||
fileSetLabel label
|
fileSetLabel label
|
||||||
fileValue label=foo
|
fileValue label=foo
|
||||||
fileWrongLabel label☺
|
fileWrongLabel label☺
|
||||||
|
newFileA* labelA
|
||||||
|
newFileB* labelB
|
||||||
EOF
|
EOF
|
||||||
echo fileSetLabel label1 >sub/.gitattributes &&
|
echo fileSetLabel label1 >sub/.gitattributes &&
|
||||||
git add .gitattributes sub/.gitattributes &&
|
git add .gitattributes sub/.gitattributes &&
|
||||||
git commit -m "add attributes"
|
git commit -m "add attributes"
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'setup .gitignore' '
|
||||||
|
cat <<-\EOF >.gitignore &&
|
||||||
|
actual
|
||||||
|
expect
|
||||||
|
pathspec_file
|
||||||
|
EOF
|
||||||
|
git add .gitignore &&
|
||||||
|
git commit -m "add gitignore"
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success 'check specific set attr' '
|
test_expect_success 'check specific set attr' '
|
||||||
cat <<-\EOF >expect &&
|
cat <<-\EOF >expect &&
|
||||||
fileSetLabel
|
fileSetLabel
|
||||||
|
@ -150,6 +162,7 @@ test_expect_success 'check specific value attr (2)' '
|
||||||
test_expect_success 'check unspecified attr' '
|
test_expect_success 'check unspecified attr' '
|
||||||
cat <<-\EOF >expect &&
|
cat <<-\EOF >expect &&
|
||||||
.gitattributes
|
.gitattributes
|
||||||
|
.gitignore
|
||||||
fileA
|
fileA
|
||||||
fileAB
|
fileAB
|
||||||
fileAC
|
fileAC
|
||||||
|
@ -175,6 +188,7 @@ test_expect_success 'check unspecified attr' '
|
||||||
test_expect_success 'check unspecified attr (2)' '
|
test_expect_success 'check unspecified attr (2)' '
|
||||||
cat <<-\EOF >expect &&
|
cat <<-\EOF >expect &&
|
||||||
HEAD:.gitattributes
|
HEAD:.gitattributes
|
||||||
|
HEAD:.gitignore
|
||||||
HEAD:fileA
|
HEAD:fileA
|
||||||
HEAD:fileAB
|
HEAD:fileAB
|
||||||
HEAD:fileAC
|
HEAD:fileAC
|
||||||
|
@ -200,6 +214,7 @@ test_expect_success 'check unspecified attr (2)' '
|
||||||
test_expect_success 'check multiple unspecified attr' '
|
test_expect_success 'check multiple unspecified attr' '
|
||||||
cat <<-\EOF >expect &&
|
cat <<-\EOF >expect &&
|
||||||
.gitattributes
|
.gitattributes
|
||||||
|
.gitignore
|
||||||
fileC
|
fileC
|
||||||
fileNoLabel
|
fileNoLabel
|
||||||
fileWrongLabel
|
fileWrongLabel
|
||||||
|
@ -239,16 +254,99 @@ test_expect_success 'fail on multiple attr specifiers in one pathspec item' '
|
||||||
test_grep "Only one" actual
|
test_grep "Only one" actual
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 'fail if attr magic is used places not implemented' '
|
test_expect_success 'fail if attr magic is used in places not implemented' '
|
||||||
# The main purpose of this test is to check that we actually fail
|
# The main purpose of this test is to check that we actually fail
|
||||||
# when you attempt to use attr magic in commands that do not implement
|
# when you attempt to use attr magic in commands that do not implement
|
||||||
# attr magic. This test does not advocate git-add to stay that way,
|
# attr magic. This test does not advocate check-ignore to stay that way.
|
||||||
# though, but git-add is convenient as it has its own internal pathspec
|
# When you teach the command to grok the pathspec, you need to find
|
||||||
# parsing.
|
# another command to replace it for the test.
|
||||||
test_must_fail git add ":(attr:labelB)" 2>actual &&
|
test_must_fail git check-ignore ":(attr:labelB)" 2>actual &&
|
||||||
test_grep "magic not supported" actual
|
test_grep "magic not supported" actual
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'check that attr magic works for git stash push' '
|
||||||
|
cat <<-\EOF >expect &&
|
||||||
|
A sub/newFileA-foo
|
||||||
|
EOF
|
||||||
|
>sub/newFileA-foo &&
|
||||||
|
>sub/newFileB-foo &&
|
||||||
|
git stash push --include-untracked -- ":(exclude,attr:labelB)" &&
|
||||||
|
git stash show --include-untracked --name-status >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'check that attr magic works for git add --all' '
|
||||||
|
cat <<-\EOF >expect &&
|
||||||
|
sub/newFileA-foo
|
||||||
|
EOF
|
||||||
|
>sub/newFileA-foo &&
|
||||||
|
>sub/newFileB-foo &&
|
||||||
|
git add --all ":(exclude,attr:labelB)" &&
|
||||||
|
git diff --name-only --cached >actual &&
|
||||||
|
git restore -W -S . &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'check that attr magic works for git add -u' '
|
||||||
|
cat <<-\EOF >expect &&
|
||||||
|
sub/fileA
|
||||||
|
EOF
|
||||||
|
>sub/newFileA-foo &&
|
||||||
|
>sub/newFileB-foo &&
|
||||||
|
>sub/fileA &&
|
||||||
|
>sub/fileB &&
|
||||||
|
git add -u ":(exclude,attr:labelB)" &&
|
||||||
|
git diff --name-only --cached >actual &&
|
||||||
|
git restore -S -W . && rm sub/new* &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'check that attr magic works for git add <path>' '
|
||||||
|
cat <<-\EOF >expect &&
|
||||||
|
fileA
|
||||||
|
fileB
|
||||||
|
sub/fileA
|
||||||
|
EOF
|
||||||
|
>fileA &&
|
||||||
|
>fileB &&
|
||||||
|
>sub/fileA &&
|
||||||
|
>sub/fileB &&
|
||||||
|
git add ":(exclude,attr:labelB)sub/*" &&
|
||||||
|
git diff --name-only --cached >actual &&
|
||||||
|
git restore -S -W . &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'check that attr magic works for git -add .' '
|
||||||
|
cat <<-\EOF >expect &&
|
||||||
|
sub/fileA
|
||||||
|
EOF
|
||||||
|
>fileA &&
|
||||||
|
>fileB &&
|
||||||
|
>sub/fileA &&
|
||||||
|
>sub/fileB &&
|
||||||
|
cd sub &&
|
||||||
|
git add . ":(exclude,attr:labelB)" &&
|
||||||
|
cd .. &&
|
||||||
|
git diff --name-only --cached >actual &&
|
||||||
|
git restore -S -W . &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'check that attr magic works for git add --pathspec-from-file' '
|
||||||
|
cat <<-\EOF >pathspec_file &&
|
||||||
|
:(exclude,attr:labelB)
|
||||||
|
EOF
|
||||||
|
cat <<-\EOF >expect &&
|
||||||
|
sub/newFileA-foo
|
||||||
|
EOF
|
||||||
|
>sub/newFileA-foo &&
|
||||||
|
>sub/newFileB-foo &&
|
||||||
|
git add --all --pathspec-from-file=pathspec_file &&
|
||||||
|
git diff --name-only --cached >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success 'abort on giving invalid label on the command line' '
|
test_expect_success 'abort on giving invalid label on the command line' '
|
||||||
test_must_fail git ls-files . ":(attr:☺)"
|
test_must_fail git ls-files . ":(attr:☺)"
|
||||||
'
|
'
|
||||||
|
|
Loading…
Reference in New Issue