{"thread":{"id":"7031","subject":"[PATCH 1/2] pathattr: path based configuration of various attributes.","startedAt":"2007-03-01T07:18:01Z","lastAt":"2007-03-01T09:24:51Z","messageCount":3,"participants":["Junio C Hamano","Andy Parkins"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"35921","messageId":"7vps7txwie.fsf@assigned-by-dhcp.cox.net","threadId":"7031","inReplyTo":null,"subject":"[PATCH 1/2] pathattr: path based configuration of various attributes.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2007-03-01T07:18:01Z","receivedAt":"2007-03-01T07:18:01Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This still does not actually add per-directory \"configuration\" files\nnor reading from the global configuration via git_config() yet,\nbut lays the groundwork for implementing them.\n\nThe idea is to have sections like these in the configuration\nfiles and .gitattributes files in each directory:\n\n\t[pathattr \"text/plain\"]\n        \tconv_i = crlf\n\t\tpath = \"*.txt\"\n                path = \"*.c\"\n                path = \"*.h\"\n\n        [pathattr \"binary\"]\n        \t; conv_i = none\n                ; conv_o = none\n\t\tpath = \"*.png\"\n                path = \"*.mov\"\n                path = \"*.mp3\"\n\nThese sections can contain \"path\", and other variables (let's\ncall the latter \"traits\").\n\n\"path\" specifications are used as fnmatch(3) patterns to\ndetermine if the traits in the section applies to a given path.\nTraits give attributes to paths that match the section, and\nthere are currently two traits defined: conv_i and conv_o.\nThese are to specify how to convert what's read from the\nfilesystem before making a blob object out of it, and how to\nconvert a blob object data before writing it out to the\nfilesystem.\n\nThese configuration sections are grouped by the directory they\nwere read from, and arranged in a stack, so that we can apply\nthe ones read from the most specific configuration file,just\nlike what we do with the current .gitignore mechanism.\nIn fact, we might want to add \"ignore\" as the third trait.\n\nSigned-off-by: Junio C Hamano <junkio@cox.net>\n---\n\n * This is relative to 'next'; you can cherry pick b875e71 from\n   'next' on top of 'master' to apply this if you do not want to\n   slurp the remainder of 'next'.\n\n Makefile   |    4 +-\n convert.c  |   43 ++++++----------\n convert.h  |   10 ++++\n pathattr.c |  160 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n pathattr.h |   18 +++++++\n 5 files changed, 206 insertions(+), 29 deletions(-)\n create mode 100644 convert.h\n create mode 100644 pathattr.c\n create mode 100644 pathattr.h\n\ndiff --git a/Makefile b/Makefile\nindex 23ab7d6..ac90543 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -247,7 +247,7 @@ LIB_H = \\\n \tdiff.h object.h pack.h pkt-line.h quote.h refs.h list-objects.h sideband.h \\\n \trun-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \\\n \ttree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h \\\n-\tutf8.h reflog-walk.h\n+\tutf8.h reflog-walk.h pathattr.h\n \n DIFF_OBJS = \\\n \tdiff.o diff-lib.o diffcore-break.o diffcore-order.o \\\n@@ -260,7 +260,7 @@ LIB_OBJS = \\\n \tinterpolate.o \\\n \tlockfile.o \\\n \tobject.o pack-check.o patch-delta.o path.o pkt-line.o sideband.o \\\n-\treachable.o reflog-walk.o \\\n+\treachable.o reflog-walk.o pathattr.o \\\n \tquote.o read-cache.o refs.o run-command.o dir.o object-refs.o \\\n \tserver-info.o setup.o sha1_file.o sha1_name.o strbuf.o \\\n \ttag.o tree.o usage.o config.o environment.o ctype.o copy.o \\\ndiff --git a/convert.c b/convert.c\nindex e1db76a..c39946d 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -1,4 +1,6 @@\n #include \"cache.h\"\n+#include \"pathattr.h\"\n+\n /*\n  * convert.c - convert a file when checking it out and checking it in.\n  *\n@@ -46,7 +48,7 @@ static int convo_crlf_real(char *in, char *out, unsigned long isize)\n /*\n  * Forced CRLF conversion.\n  */\n-static int convi_crlf(const char *path, char **bufp, unsigned long *sizep)\n+int convi_crlf(const char *path, char **bufp, unsigned long *sizep)\n {\n \tunsigned long size, cr, i, nsize;\n \tchar *buf, *nbuf;\n@@ -65,7 +67,7 @@ static int convi_crlf(const char *path, char **bufp, unsigned long *sizep)\n \treturn convi_crlf_real(buf, nbuf, size);\n }\n \n-static int convo_crlf(const char *path, char **bufp, unsigned long *sizep)\n+int convo_crlf(const char *path, char **bufp, unsigned long *sizep)\n {\n \tunsigned long size, cr, i, nsize;\n \tunsigned char last;\n@@ -245,34 +247,18 @@ static int convo_autocrlf(const char *path, char **bufp, unsigned long *sizep)\n \n /******************************************************************/\n \n-typedef int (*convi_fn)(const char *, char **, unsigned long *);\n-typedef int (*convo_fn)(const char *, char **, unsigned long *);\n-\n-static convi_fn pathattr_find_conv_i(const char *path)\n-{\n-\t/* A silly example */\n-\tif (!fnmatch(\"*.dos-txt\", path, 0))\n-\t\treturn convi_crlf;\n-\treturn NULL;\n-}\n-\n-static convo_fn pathattr_find_conv_o(const char *path)\n-{\n-\t/* A silly example */\n-\tif (!fnmatch(\"*.dos-txt\", path, 0))\n-\t\treturn convo_crlf;\n-\treturn NULL;\n-}\n-\n /*\n  * Main entry points\n  */\n int convert_to_git(const char *path, char **bufp, unsigned long *sizep)\n {\n \tif (path) {\n-\t\tconvi_fn conv_i = pathattr_find_conv_i(path);\n-\t\tif (conv_i)\n-\t\t\treturn conv_i(path, bufp, sizep);\n+\t\tconst struct pathattr *attr = pathattr_lookup(path);\n+\t\tif (attr) {\n+\t\t\tif (!attr->conv_i)\n+\t\t\t\treturn 0;\n+\t\t\treturn attr->conv_i(path, bufp, sizep);\n+\t\t}\n \t}\n \t/* Fallback */\n \treturn convi_autocrlf(path, bufp, sizep);\n@@ -282,9 +268,12 @@ int convert_to_working_tree(const char *path, char **bufp,\n \t\t\t    unsigned long *sizep)\n {\n \tif (path) {\n-\t\tconvi_fn conv_o = pathattr_find_conv_o(path);\n-\t\tif (conv_o)\n-\t\t\treturn conv_o(path, bufp, sizep);\n+\t\tconst struct pathattr *attr = pathattr_lookup(path);\n+\t\tif (attr) {\n+\t\t\tif (!attr->conv_o)\n+\t\t\t\treturn 0;\n+\t\t\treturn attr->conv_o(path, bufp, sizep);\n+\t\t}\n \t}\n \t/* Fallback */\n \treturn convo_autocrlf(path, bufp, sizep);\ndiff --git a/convert.h b/convert.h\nnew file mode 100644\nindex 0000000..e54b3ef\n--- /dev/null\n+++ b/convert.h\n@@ -0,0 +1,10 @@\n+#ifndef CONVERT_H\n+#define CONVERT_H\n+\n+typedef int (*convi_fn)(const char *, char **, unsigned long *);\n+typedef int (*convo_fn)(const char *, char **, unsigned long *);\n+\n+int convi_crlf(const char *path, char **bufp, unsigned long *sizep);\n+int convo_crlf(const char *path, char **bufp, unsigned long *sizep);\n+\n+#endif /* CONVERT_H */\ndiff --git a/pathattr.c b/pathattr.c\nnew file mode 100644\nindex 0000000..06bbb4e\n--- /dev/null\n+++ b/pathattr.c\n@@ -0,0 +1,160 @@\n+#include \"cache.h\"\n+#include \"pathattr.h\"\n+\n+/*\n+ * Path match specification\n+ */\n+struct pathattr_match {\n+\tint type;\n+\tchar *pattern;\n+\tstruct pathattr *attr;\n+};\n+\n+/*\n+ * List of pathattr specifications are maintained per\n+ * directory.\n+ */\n+struct pathattr_stack {\n+\tstruct pathattr_stack *next;\n+\tint baselen;\n+\tconst char *base;\n+\n+\tint attr_nr;\n+\tint attr_alloc;\n+\tint match_nr;\n+\tint match_alloc;\n+\tstruct pathattr *attr;\n+\tstruct pathattr_match *match;\n+};\n+\n+static struct pathattr_stack *stk;\n+\n+/*\n+ * Bottom stack element is special -- it is what we read\n+ * from the configuration file.\n+ */\n+static struct pathattr_stack *btm;\n+\n+static inline int match_base(const char *path, int pathlen,\n+\t\t\t     const char *base, int baselen)\n+{\n+\tif (pathlen < baselen)\n+\t\treturn 0;\n+\tif (strncmp(path, base, baselen))\n+\t\treturn 0;\n+\tif (baselen && path[baselen])\n+\t\treturn 0;\n+\treturn 1;\n+}\n+\n+static struct pathattr *locate(struct pathattr_stack *s, const char *name)\n+{\n+\tstruct pathattr *a;\n+\tint i;\n+\n+\tfor (i = 0; i < s->attr_nr; i++)\n+\t\tif (!strcmp(s->attr[i].name, name))\n+\t\t\treturn &(s->attr[i]);\n+\tif (s->attr_nr <= s->attr_alloc) {\n+\t\ts->attr_alloc = alloc_nr(s->attr_nr);\n+\t\ts->attr = xrealloc(s->attr, sizeof(*a) * s->attr_alloc);\n+\t}\n+\ta = &(s->attr[s->attr_nr++]);\n+\ta->name = xstrdup(name);\n+\treturn a;\n+}\n+\n+static void add_match(struct pathattr_stack *s, struct pathattr *a, const char *pattern)\n+{\n+\tstruct pathattr_match *m;\n+\n+\tif (s->match_nr <= s->match_alloc) {\n+\t\ts->match_alloc = alloc_nr(s->match_nr);\n+\t\ts->match = xrealloc(s->match, sizeof(*m) * s->match_alloc);\n+\t}\n+\tm = &(s->match[s->match_nr++]);\n+\tm->pattern = xstrdup(pattern);\n+\tm->type = strchr(pattern, '/') ? FNM_PATHNAME : 0;\n+\tm->attr = a;\n+}\n+\n+static void init_stack(void)\n+{\n+\tstruct pathattr *a;\n+\n+\tbtm = stk = xcalloc(1, sizeof(*stk));\n+\n+\t/* A couple of silly example as an illustration */\n+\ta = locate(stk, \"silly\");\n+\ta->conv_i = convi_crlf;\n+\ta->conv_o = convo_crlf;\n+\tadd_match(stk, a, \"*.dos-txt\");\n+\n+\ta = locate(stk, \"image/*\");\n+\ta->conv_i = NULL;\n+\ta->conv_o = NULL;\n+\tadd_match(stk, a, \"*.png\");\n+\tadd_match(stk, a, \"*.jpg\");\n+\tadd_match(stk, a, \"*.gif\");\n+\n+\t/* NEEDSWORK: read from config */\n+}\n+\n+static void pop_stack(void)\n+{\n+\tstruct pathattr_stack *s;\n+\tint i;\n+\n+\tif (stk == btm)\n+\t\treturn;\n+\ts = stk;\n+\tfor (i = 0; i < s->attr_nr; i++)\n+\t\tfree(s->attr[i].name);\n+\tfree(s->attr);\n+\tfor (i = 0; i < s->match_nr; i++)\n+\t\tfree(s->match[i].pattern);\n+\tfree(s->match);\n+\tstk = stk->next;\n+}\n+\n+static void adjust_stack(const char *path, int len)\n+{\n+\tif (!btm)\n+\t\tinit_stack();\n+\twhile (stk && stk != btm &&\n+\t       match_base(path, len, stk->base, stk->baselen))\n+\t\tpop_stack();\n+\t/*\n+\t * NEEDSWORK:\n+\t *\n+\t * Now, (stk->base, stk->baselen) is a leading directory\n+\t * of (path, len), and we would need to read the entries\n+\t * for remaining subdirectories.\n+\t *\n+\t * e.g. stk->base = \"\", path = \"a/b/c\" or\n+\t * stk->base = \"a/\", path = \"a/b/c\".\n+\t */\n+}\n+\n+\n+const struct pathattr *pathattr_lookup(const char *path)\n+{\n+\tint len = strlen(path);\n+\tstruct pathattr_stack *s;\n+\n+\tif (!stk || !match_base(path, len, stk->base, stk->baselen))\n+\t\tadjust_stack(path, len);\n+\n+\tfor (s = stk; s; s = s->next) {\n+\t\tint i;\n+\t\tint baselen = s->baselen;\n+\n+\t\tfor (i = 0; i < s->match_nr; i++) {\n+\t\t\tstruct pathattr_match *m = &(s->match[i]);\n+\n+\t\t\tif (!fnmatch(m->pattern, path + baselen, m->type))\n+\t\t\t\treturn m->attr;\n+\t\t}\n+\t}\n+\treturn NULL;\n+}\ndiff --git a/pathattr.h b/pathattr.h\nnew file mode 100644\nindex 0000000..4ca4e60\n--- /dev/null\n+++ b/pathattr.h\n@@ -0,0 +1,18 @@\n+#ifndef PATHATTR_H\n+#define PATHATTR_H\n+\n+#include \"convert.h\"\n+\n+/*\n+ * Data in one section of [pathattr \"name\"], except\n+ * the path match specification.\n+ */\n+struct pathattr {\n+\tchar *name;\n+\tconvi_fn conv_i;\n+\tconvo_fn conv_o;\n+};\n+\n+const struct pathattr *pathattr_lookup(const char *);\n+\n+#endif /* PATHATTR_H */\n-- \n1.5.0.2.809.g0f936\n"},{"id":"35938","messageId":"200703010911.56048.andyparkins@gmail.com","threadId":"7031","inReplyTo":"7vps7txwie.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH 1/2] pathattr: path based configuration of various attributes.","fromName":"Andy Parkins","fromEmail":"andyparkins@gmail.com","sentAt":"2007-03-01T09:11:54Z","receivedAt":"2007-03-01T09:11:54Z","isPatch":true,"sender":{"key":"andyparkins@gmail.com","avatar":null},"body":"On Thursday 2007 March 01 07:18, Junio C Hamano wrote:\n\n> \t[pathattr \"text/plain\"]\n>         \tconv_i = crlf\n> \t\tpath = \"*.txt\"\n>                 path = \"*.c\"\n>                 path = \"*.h\"\n\nCan I suggest that \"crlf\" is not the right name for something like this?  What \nyou actually want is to say\n\n conv_i = convert_from_local_lineendings\n conv_o = convert_to_local_lineendings\n\n(Obviously I'm not really suggesting such long names).  \"crlf\" implies that \nyou want to convert to crlf endings /whatever/ the local line ending.  Given \nthat exactly the same .gitattributes would be in use on both windows and \nlinux for the same repository, it's got to be an attribute name that makes \nsense in both cases.  How about:\n\n conv_i = eol_to_lf\n conv_o = eol_to_local\n\nI'm not sure about \"conv\" either.  \"filter\" would be more accurate.\n\n infilter = eol_to_lf\n outfilter = eol_to_local\n\n--- Wild rantings start here, ignore at will ---\n\nAlso; for things like Ted's example of OpenDocument files, you might want\n\n infilter = gunzip\n outfilter = gzip\n\nThen of course, order has to be significant:\n\n infilter = gunzip eol_to_lf\n outfilter = eol_to_local gzip\n\n(That's probably a crazy thing to want to do, but it would make git \nspectacularly impressive).  Here's another idea, what about making the \nfilters configurable, with some builtin (and overridable)\n\n[infilter \"perfectpatch\"]\n  exec = hooks/perfectpatch\n\n[infilter \"codingstandards\"]\n  exec = scripts/check-for-spaces-in-the-wrong-place\n\n[pathattr \"text/c\"]\n  infilter = perfectpatch\n  path = *.cc\n  path = *.cpp\n  path = *.c\n  path = *.C\n  path = *.h\n\nWe could also support a prettyfilter for getting your display example from \npatch 2/2 in the right place.\n\n[prettyfilter]\n  exec = display -\n\n[pathattr \"image/\"]\n  prettyfilter = rundisplay\n  path = *.png\n\nWhich would be equally good for fixing line endings for display; prettyfilter \ncould, I suppose, default to be the same as outfilter, which would be correct \nin most cases.\n\n[pathattr \"text/c\"]\n  prettyfilter = eol_to_local\n\nSorry for the long rambling email, it's very exciting this attribute stuff.  I \nalready love it.\n\n\n\nAndy\n-- \nDr Andy Parkins, M Eng (hons), MIET\nandyparkins@gmail.com\n"},{"id":"35942","messageId":"7vhct5wc2k.fsf@assigned-by-dhcp.cox.net","threadId":"7031","inReplyTo":"200703010911.56048.andyparkins@gmail.com","subject":"Re: [PATCH 1/2] pathattr: path based configuration of various attributes.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2007-03-01T09:24:51Z","receivedAt":"2007-03-01T09:24:51Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Andy Parkins <andyparkins@gmail.com> writes:\n\n> On Thursday 2007 March 01 07:18, Junio C Hamano wrote:\n>\n>> \t[pathattr \"text/plain\"]\n>>         \tconv_i = crlf\n>> \t\tpath = \"*.txt\"\n>>                 path = \"*.c\"\n>>                 path = \"*.h\"\n>\n> Can I suggest that \"crlf\" is not the right name for something like this?  What \n> you actually want is to say\n>\n>  conv_i = convert_from_local_lineendings\n>  conv_o = convert_to_local_lineendings\n\nThe sample traits' value \"crlf\" is probably a misnomer.  As you\nnoticed, they are to _force_ the conversion.  You can add one\nthat honors core.autocrlf (which would be set to true on Windows\nand to false on UNIX).\n\n>  conv_i = eol_to_lf\n>  conv_o = eol_to_local\n\nProbably.\n\n> --- Wild rantings start here, ignore at will ---\n\nWill ignore for now, since I have more interesting patches\ncoming for your enjoyment ;-).\n"}]}