Add directory pattern matching to attributes

The manpage of gitattributes says: "The rules how the pattern matches paths are the same as in .gitignore files" and the gitignore pattern matching has a pattern ending with / for directory matching. This rule is specifically relevant for the 'export-ignore' rule used for git archive. Signed-off-by: Jean-Noel Avila <jn.avila@free.fr> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jean-Noël AVILA committed Dec 8, 2012 at 21:04 UTC 94bc671a1f2e8610de475c2494d2763355a99f65
3 files changed +76 -9
archive.c
+2 -1
@@ -120,6 +120,8 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,
120 strbuf_add(&path, args->base, args->baselen);
121 strbuf_add(&path, base, baselen);
122 strbuf_addstr(&path, filename);
123 + if (S_ISDIR(mode) || S_ISGITLINK(mode))
124 + strbuf_addch(&path, '/');
125 path_without_prefix = path.buf + args->baselen;
126
127 setup_archive_check(check);
@@ -130,7 +132,6 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,
132 }
133
134 if (S_ISDIR(mode) || S_ISGITLINK(mode)) {
133 - strbuf_addch(&path, '/');
135 if (args->verbose)
136 fprintf(stderr, "%.*s\n", (int)path.len, path.buf);
137 err = write_entry(args, sha1, path.buf, path.len, mode);
attr.c
+17 -8
@@ -564,17 +564,24 @@ static void bootstrap_attr_stack(void)
564 attr_stack = elem;
565 }
566
567 +static const char *find_basename(const char *path)
568 +{
569 + const char *cp, *last_slash = NULL;
570 +
571 + for (cp = path; *cp; cp++) {
572 + if (*cp == '/' && cp[1])
573 + last_slash = cp;
574 + }
575 + return last_slash ? last_slash + 1 : path;
576 +}
577 +
578 static void prepare_attr_stack(const char *path)
579 {
580 struct attr_stack *elem, *info;
581 int dirlen, len;
582 const char *cp;
583
573 - cp = strrchr(path, '/');
574 - if (!cp)
575 - dirlen = 0;
576 - else
577 - dirlen = cp - path;
584 + dirlen = find_basename(path) - path;
585
586 /*
587 * At the bottom of the attribute stack is the built-in
@@ -668,6 +675,10 @@ static int path_matches(const char *pathname, int pathlen,
675 const char *pattern = pat->pattern;
676 int prefix = pat->nowildcardlen;
677
678 + if ((pat->flags & EXC_FLAG_MUSTBEDIR) &&
679 + ((!pathlen) || (pathname[pathlen-1] != '/')))
680 + return 0;
681 +
682 if (pat->flags & EXC_FLAG_NODIR) {
683 return match_basename(basename,
684 pathlen - (basename - pathname),
@@ -758,9 +769,7 @@ static void collect_all_attrs(const char *path)
769 for (i = 0; i < attr_nr; i++)
770 check_all_attr[i].value = ATTR__UNKNOWN;
771
761 - basename = strrchr(path, '/');
762 - basename = basename ? basename + 1 : path;
763 -
772 + basename = find_basename(path);
773 pathlen = strlen(path);
774 rem = attr_nr;
775 for (stk = attr_stack; 0 < rem && stk; stk = stk->prev)
t/t5002-archive-attr-pattern.sh new
+57
@@ -0,0 +1,57 @@
1 +#!/bin/sh
2 +
3 +test_description='git archive attribute pattern tests'
4 +
5 +. ./test-lib.sh
6 +
7 +test_expect_exists() {
8 + test_expect_success " $1 exists" "test -e $1"
9 +}
10 +
11 +test_expect_missing() {
12 + test_expect_success " $1 does not exist" "test ! -e $1"
13 +}
14 +
15 +test_expect_success 'setup' '
16 + echo ignored >ignored &&
17 + echo ignored export-ignore >>.git/info/attributes &&
18 + git add ignored &&
19 +
20 + mkdir not-ignored-dir &&
21 + echo ignored-in-tree >not-ignored-dir/ignored &&
22 + echo not-ignored-in-tree >not-ignored-dir/ignored-only-if-dir &&
23 + git add not-ignored-dir &&
24 +
25 + mkdir ignored-only-if-dir &&
26 + echo ignored by ignored dir >ignored-only-if-dir/ignored-by-ignored-dir &&
27 + echo ignored-only-if-dir/ export-ignore >>.git/info/attributes &&
28 + git add ignored-only-if-dir &&
29 +
30 +
31 + mkdir -p one-level-lower/two-levels-lower/ignored-only-if-dir &&
32 + echo ignored by ignored dir >one-level-lower/two-levels-lower/ignored-only-if-dir/ignored-by-ignored-dir &&
33 + git add one-level-lower &&
34 +
35 + git commit -m. &&
36 +
37 + git clone --bare . bare &&
38 + cp .git/info/attributes bare/info/attributes
39 +'
40 +
41 +test_expect_success 'git archive' '
42 + git archive HEAD >archive.tar &&
43 + (mkdir archive && cd archive && "$TAR" xf -) <archive.tar
44 +'
45 +
46 +test_expect_missing archive/ignored
47 +test_expect_missing archive/not-ignored-dir/ignored
48 +test_expect_exists archive/not-ignored-dir/ignored-only-if-dir
49 +test_expect_exists archive/not-ignored-dir/
50 +test_expect_missing archive/ignored-only-if-dir/
51 +test_expect_missing archive/ignored-ony-if-dir/ignored-by-ignored-dir
52 +test_expect_exists archive/one-level-lower/
53 +test_expect_missing archive/one-level-lower/two-levels-lower/ignored-only-if-dir/
54 +test_expect_missing archive/one-level-lower/two-levels-lower/ignored-ony-if-dir/ignored-by-ignored-dir
55 +
56 +
57 +test_done