{"thread":{"id":"32293","subject":"[PATCHv2] Add directory pattern matching to attributes","startedAt":"2012-12-08T20:04:39Z","lastAt":"2012-12-13T20:08:57Z","messageCount":2,"participants":["Jean-Noël AVILA","Junio C Hamano"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"204608","messageId":"201212082104.39411.avila.jn@gmail.com","threadId":"32293","inReplyTo":null,"subject":"[PATCHv2] Add directory pattern matching to attributes","fromName":"Jean-Noël AVILA","fromEmail":"avila.jn@gmail.com","sentAt":"2012-12-08T20:04:39Z","receivedAt":"2012-12-08T20:04:39Z","isPatch":false,"sender":{"key":"jn.avila@free.fr","avatar":"https://avatars.githubusercontent.com/u/156172?v=4"},"body":"The manpage of gitattributes says: \"The rules how the pattern\nmatches paths are the same as in .gitignore files\" and the gitignore\npattern matching has a pattern ending with / for directory matching.\n\nThis rule is specifically relevant for the 'export-ignore' rule used\nfor git archive.\n\nSigned-off-by: Jean-Noel Avila <jn.avila@free.fr>\n---\n archive.c                       |    3 ++-\n attr.c                          |   32 ++++++++++++++++------\n t/t5002-archive-attr-pattern.sh |   57 +++++++++++++++++++++++++++++++++++++++\n 3 files changed, 83 insertions(+), 9 deletions(-)\n create mode 100644 t/t5002-archive-attr-pattern.sh\n\ndiff --git a/archive.c b/archive.c\nindex 4666404..93e00bb 100644\n--- a/archive.c\n+++ b/archive.c\n@@ -120,6 +120,8 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,\n \tstrbuf_add(&path, args->base, args->baselen);\n \tstrbuf_add(&path, base, baselen);\n \tstrbuf_addstr(&path, filename);\n+\tif (S_ISDIR(mode) || S_ISGITLINK(mode))\n+\t\tstrbuf_addch(&path, '/');\n \tpath_without_prefix = path.buf + args->baselen;\n \n \tsetup_archive_check(check);\n@@ -130,7 +132,6 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,\n \t}\n \n \tif (S_ISDIR(mode) || S_ISGITLINK(mode)) {\n-\t\tstrbuf_addch(&path, '/');\n \t\tif (args->verbose)\n \t\t\tfprintf(stderr, \"%.*s\\n\", (int)path.len, path.buf);\n \t\terr = write_entry(args, sha1, path.buf, path.len, mode);\ndiff --git a/attr.c b/attr.c\nindex 097ae87..cdba88a 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -564,17 +564,31 @@ static void bootstrap_attr_stack(void)\n \tattr_stack = elem;\n }\n \n+static const char *find_basename(const char *path)\n+{\n+\tchar pathbuf[PATH_MAX];\n+\tint pathlen;\n+\tconst char *cp;\n+\n+\tpathlen =strlen(path);\n+\tif (path[pathlen-1] != '/') {\n+\t\tcp =strrchr(path, '/');\n+\t\treturn cp ? cp + 1: path;\n+\t} else {\n+\t\tstrncpy(pathbuf, path, pathlen);\n+\t\tpathbuf[pathlen-1] = '\\0';\n+\t\tcp =strrchr(pathbuf, '/');\n+\t\treturn cp ? path + (cp - pathbuf) + 1 : path;\n+\t}\n+}\n+\n static void prepare_attr_stack(const char *path)\n {\n \tstruct attr_stack *elem, *info;\n \tint dirlen, len;\n \tconst char *cp;\n \n-\tcp = strrchr(path, '/');\n-\tif (!cp)\n-\t\tdirlen = 0;\n-\telse\n-\t\tdirlen = cp - path;\n+\tdirlen = find_basename(path) - path;\n \n \t/*\n \t * At the bottom of the attribute stack is the built-in\n@@ -668,6 +682,10 @@ static int path_matches(const char *pathname, int pathlen,\n \tconst char *pattern = pat->pattern;\n \tint prefix = pat->nowildcardlen;\n \n+\tif ((pat->flags & EXC_FLAG_MUSTBEDIR) &&\n+\t    ((!pathlen) || (pathname[pathlen-1] != '/')))\n+\t\treturn 0;\n+\n \tif (pat->flags & EXC_FLAG_NODIR) {\n \t\treturn match_basename(basename,\n \t\t\t\t      pathlen - (basename - pathname),\n@@ -758,9 +776,7 @@ static void collect_all_attrs(const char *path)\n \tfor (i = 0; i < attr_nr; i++)\n \t\tcheck_all_attr[i].value = ATTR__UNKNOWN;\n \n-\tbasename = strrchr(path, '/');\n-\tbasename = basename ? basename + 1 : path;\n-\n+\tbasename = find_basename(path);\n \tpathlen = strlen(path);\n \trem = attr_nr;\n \tfor (stk = attr_stack; 0 < rem && stk; stk = stk->prev)\ndiff --git a/t/t5002-archive-attr-pattern.sh b/t/t5002-archive-attr-pattern.sh\nnew file mode 100644\nindex 0000000..0c847fb\n--- /dev/null\n+++ b/t/t5002-archive-attr-pattern.sh\n@@ -0,0 +1,57 @@\n+#!/bin/sh\n+\n+test_description='git archive attribute pattern tests'\n+\n+. ./test-lib.sh\n+\n+test_expect_exists() {\n+\ttest_expect_success \" $1 exists\" \"test -e $1\"\n+}\n+\n+test_expect_missing() {\n+\ttest_expect_success \" $1 does not exist\" \"test ! -e $1\"\n+}\n+\n+test_expect_success 'setup' '\n+\techo ignored >ignored &&\n+\techo ignored export-ignore >>.git/info/attributes &&\n+\tgit add ignored &&\n+\n+\tmkdir not-ignored-dir &&\n+\techo ignored-in-tree >not-ignored-dir/ignored &&\n+\techo not-ignored-in-tree >not-ignored-dir/ignored-only-if-dir &&\n+\tgit add not-ignored-dir &&\n+\n+\tmkdir ignored-only-if-dir &&\n+\techo ignored by ignored dir >ignored-only-if-dir/ignored-by-ignored-dir &&\n+\techo ignored-only-if-dir/ export-ignore >>.git/info/attributes &&\n+\tgit add ignored-only-if-dir &&\n+\n+\n+\tmkdir -p one-level-lower/two-levels-lower/ignored-only-if-dir &&\n+\techo ignored by ignored dir >one-level-lower/two-levels-lower/ignored-only-if-dir/ignored-by-ignored-dir &&\n+\tgit add one-level-lower &&\n+\n+\tgit commit -m. &&\n+\n+\tgit clone --bare . bare &&\n+\tcp .git/info/attributes bare/info/attributes\n+'\n+\n+test_expect_success 'git archive' '\n+\tgit archive HEAD >archive.tar &&\n+\t(mkdir archive && cd archive && \"$TAR\" xf -) <archive.tar\n+'\n+\n+test_expect_missing\tarchive/ignored\n+test_expect_missing\tarchive/not-ignored-dir/ignored\n+test_expect_exists\tarchive/not-ignored-dir/ignored-only-if-dir\n+test_expect_exists\tarchive/not-ignored-dir/\n+test_expect_missing\tarchive/ignored-only-if-dir/\n+test_expect_missing\tarchive/ignored-ony-if-dir/ignored-by-ignored-dir\n+test_expect_exists\tarchive/one-level-lower/\n+test_expect_missing\tarchive/one-level-lower/two-levels-lower/ignored-only-if-dir/\n+test_expect_missing\tarchive/one-level-lower/two-levels-lower/ignored-ony-if-dir/ignored-by-ignored-dir\n+\n+\n+test_done\n-- \n1.7.10.4\n"},{"id":"204850","messageId":"7vfw39yasm.fsf@alter.siamese.dyndns.org","threadId":"32293","inReplyTo":"201212082104.39411.avila.jn@gmail.com","subject":"Re: [PATCHv2] Add directory pattern matching to attributes","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-12-13T20:08:57Z","receivedAt":"2012-12-13T20:08:57Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Jean-Noël AVILA\" <avila.jn@gmail.com> writes:\n\n> The manpage of gitattributes says: \"The rules how the pattern\n> matches paths are the same as in .gitignore files\" and the gitignore\n> pattern matching has a pattern ending with / for directory matching.\n>\n> This rule is specifically relevant for the 'export-ignore' rule used\n> for git archive.\n>\n> Signed-off-by: Jean-Noel Avila <jn.avila@free.fr>\n> ---\n>  archive.c                       |    3 ++-\n>  attr.c                          |   32 ++++++++++++++++------\n>  t/t5002-archive-attr-pattern.sh |   57 +++++++++++++++++++++++++++++++++++++++\n>  3 files changed, 83 insertions(+), 9 deletions(-)\n>  create mode 100644 t/t5002-archive-attr-pattern.sh\n\nLooks nicely done.\n\n> diff --git a/attr.c b/attr.c\n> index 097ae87..cdba88a 100644\n> --- a/attr.c\n> +++ b/attr.c\n> @@ -564,17 +564,31 @@ static void bootstrap_attr_stack(void)\n>  \tattr_stack = elem;\n>  }\n>  \n> +static const char *find_basename(const char *path)\n> +{\n> +\tchar pathbuf[PATH_MAX];\n> +\tint pathlen;\n> +\tconst char *cp;\n> +\n> +\tpathlen =strlen(path);\n> +\tif (path[pathlen-1] != '/') {\n> +\t\tcp =strrchr(path, '/');\n> +\t\treturn cp ? cp + 1: path;\n> +\t} else {\n> +\t\tstrncpy(pathbuf, path, pathlen);\n> +\t\tpathbuf[pathlen-1] = '\\0';\n> +\t\tcp =strrchr(pathbuf, '/');\n> +\t\treturn cp ? path + (cp - pathbuf) + 1 : path;\n> +\t}\n> +}\n\nLet's do this function like this instead; shorter and equally easy\nto understand.\n\n        static const char *find_basename(const char *path)\n        {\n                const char *cp, *last_slash = NULL;\n\n                for (cp = path; *cp; cp++) {\n                        if (*cp == '/' && cp[1])\n                                last_slash = cp;\n                }\n                return last_slash ? last_slash + 1 : path;\n        }\n\nThanks; will queue.\n"}]}