{"thread":{"id":"717","subject":"[PATCH] ls-tree matching a prefix","startedAt":"2005-05-26T03:47:56Z","lastAt":"2005-05-26T18:06:53Z","messageCount":6,"participants":["Jason McMullan","Junio C Hamano","McMullan, Jason"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"3969","messageId":"20050526034756.GA1488@port.evillabs.net","threadId":"717","inReplyTo":null,"subject":"[PATCH] ls-tree matching a prefix","fromName":"Jason McMullan","fromEmail":"jason.mcmullan@timesys.com","sentAt":"2005-05-26T03:47:56Z","receivedAt":"2005-05-26T03:47:56Z","isPatch":true,"sender":{"key":"jason.mcmullan@timesys.com","avatar":null},"body":"In the Porcelain I've been working on, I have found it useful\nto retrieve a single file's SHA1 out of a tree when I don't \nwant to create an index.\n\nFor this purpose, I've enhanced git-ls-tree to allow the\nspecification of an optional 'match path' that restricts\nthat output of git-ls-tree to just the path requested.\n\nIf the patch has a '/' in it, it implies -r.\n\nie:\n\n$ git-ls-tree HEAD Makefile\n100644  blob    92d0e87535ecaa5e52a6503c43dd30dd546ea6b7        Makefile\n\n$ git-ls-tree HEAD t\n040000  tree    33ce2f3201c99d5da785bb777639c1e2374c44d2        t\n\n$ git-ls-tree HEAD t/test-lib.sh \n100755  blob    d3f71d1932310197219155b426687d155bf63c5b\tt/test-lib.sh\n\nSigned-Off-By: Jason McMullan <jason.mcmullan@timesys.com>\n\ndiff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt\n--- a/Documentation/git-ls-tree.txt\n+++ b/Documentation/git-ls-tree.txt\n@@ -27,6 +27,10 @@ OPTIONS\n -z::\n \t\\0 line termination on output\n \n+[path]::\n+\tOnly return items that match the specified path, relative to the\n+\troot of the tree. If a patch has a '/' in it, implies -r\n+\n Output Format\n -------------\n         <mode>\\t\t<type>\\t\t<object>\\t\t<file>\ndiff --git a/ls-tree.c b/ls-tree.c\n--- a/ls-tree.c\n+++ b/ls-tree.c\n@@ -26,10 +26,32 @@ static void print_path_prefix(struct pat\n static void list_recursive(void *buffer,\n \t\t\t   const char *type,\n \t\t\t   unsigned long size,\n-\t\t\t   struct path_prefix *prefix)\n+\t\t\t   struct path_prefix *prefix,\n+\t\t\t   const char *match_path)\n {\n \tstruct path_prefix this_prefix;\n \tthis_prefix.prev = prefix;\n+\tchar mpref[PATH_MAX];\n+\tsize_t mlen = 0;\n+\tchar *cp = NULL;\n+\tif (match_path != NULL) {\n+\t\tif (*match_path == 0)\n+\t\t\treturn;\n+\t\tcp = strchr(match_path,'/');\n+\t\tif (cp == NULL) {\n+\t\t\tstrcpy(mpref,match_path);\n+\t\t\tmatch_path = NULL;\n+\t\t} else {\n+\t\t\trecursive = 1;\n+\t\t\tstrncpy(mpref,match_path,cp-match_path);\n+\t\t\tmpref[cp-match_path]=0;\n+\t\t\tcp++;\n+\t\t\tmatch_path = cp;\n+\t\t\tif (*cp == 0)\n+\t\t\t\tcp = NULL;\n+\t\t}\n+\t\tmlen = strlen(mpref);\n+\t}\n \n \tif (strcmp(type, \"tree\"))\n \t\tdie(\"expected a 'tree' node\");\n@@ -48,27 +70,35 @@ static void list_recursive(void *buffer,\n \t\tbuffer = sha1 + 20;\n \t\tsize -= namelen + 20;\n \n-\t\tprintf(\"%06o\\t%s\\t%s\\t\", mode,\n-\t\t       S_ISDIR(mode) ? \"tree\" : \"blob\",\n-\t\t       sha1_to_hex(sha1));\n-\t\tprint_path_prefix(prefix);\n-\t\tfputs(path, stdout);\n-\t\tputchar(line_termination);\n+\t\tif (mlen && strcmp(mpref, path) != 0)\n+\t\t\tcontinue;\n+\n+\t\tif (cp == NULL) {\n+\t\t\tprintf(\"%06o\\t%s\\t%s\\t\", mode,\n+\t\t\t       S_ISDIR(mode) ? \"tree\" : \"blob\",\n+\t\t\t       sha1_to_hex(sha1));\n+\t\t\tprint_path_prefix(prefix);\n+\t\t\tfputs(path, stdout);\n+\t\t\tputchar(line_termination);\n+\t\t}\n \n \t\tif (! recursive || ! S_ISDIR(mode))\n \t\t\tcontinue;\n \n+\t\tif (mlen && cp == NULL)\n+\t\t\tcontinue;\n+\n \t\tif (! (eltbuf = read_sha1_file(sha1, elttype, &eltsize)) ) {\n \t\t\terror(\"cannot read %s\", sha1_to_hex(sha1));\n \t\t\tcontinue;\n \t\t}\n \t\tthis_prefix.name = path;\n-\t\tlist_recursive(eltbuf, elttype, eltsize, &this_prefix);\n+\t\tlist_recursive(eltbuf, elttype, eltsize, &this_prefix, match_path);\n \t\tfree(eltbuf);\n \t}\n }\n \n-static int list(unsigned char *sha1)\n+static int list(unsigned char *sha1, const char *match_path)\n {\n \tvoid *buffer;\n \tunsigned long size;\n@@ -76,12 +106,12 @@ static int list(unsigned char *sha1)\n \tbuffer = read_object_with_reference(sha1, \"tree\", &size, NULL);\n \tif (!buffer)\n \t\tdie(\"unable to read sha1 file\");\n-\tlist_recursive(buffer, \"tree\", size, NULL);\n+\tlist_recursive(buffer, \"tree\", size, NULL, match_path);\n \tfree(buffer);\n \treturn 0;\n }\n \n-static const char *ls_tree_usage = \"git-ls-tree [-r] [-z] <key>\";\n+static const char *ls_tree_usage = \"git-ls-tree [-r] [-z] <key> [path]\";\n \n int main(int argc, char **argv)\n {\n@@ -101,11 +131,11 @@ int main(int argc, char **argv)\n \t\targc--; argv++;\n \t}\n \n-\tif (argc != 2)\n+\tif (argc != 2 && argc != 3)\n \t\tusage(ls_tree_usage);\n \tif (get_sha1(argv[1], sha1) < 0)\n \t\tusage(ls_tree_usage);\n-\tif (list(sha1) < 0)\n+\tif (list(sha1, argc==3 ? argv[2] : NULL) < 0)\n \t\tdie(\"list failed\");\n \treturn 0;\n }\n"},{"id":"3974","messageId":"7vsm0aeguo.fsf@assigned-by-dhcp.cox.net","threadId":"717","inReplyTo":"20050526034756.GA1488@port.evillabs.net","subject":"Re: [PATCH] ls-tree matching a prefix","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2005-05-26T04:44:31Z","receivedAt":"2005-05-26T04:44:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":">>>>> \"JM\" == Jason McMullan <jason.mcmullan@timesys.com> writes:\n\nJM> For this purpose, I've enhanced git-ls-tree to allow the\nJM> specification of an optional 'match path' that restricts\nJM> that output of git-ls-tree to just the path requested.\n\nJM> If the patch has a '/' in it, it implies -r.\n\nI'd rather see the behaviour match existing commands with path\nrestriction, like diff-tree, diff-cache, and diff-files.  That\nis, to take a list of paths and limit your output to those that\nmatch one of them.  I do not think this enhancement would\nnegatively affect your stated use of getting one entry with the\nexact match.\n\n"},{"id":"3985","messageId":"1117111843.11542.11.camel@jmcmullan.timesys","threadId":"717","inReplyTo":"7vsm0aeguo.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH] ls-tree matching a prefix","fromName":"McMullan, Jason","fromEmail":"jason.mcmullan@timesys.com","sentAt":"2005-05-26T12:50:41Z","receivedAt":"2005-05-26T12:50:41Z","isPatch":true,"sender":{"key":"jason.mcmullan@timesys.com","avatar":null},"body":"On Wed, 2005-05-25 at 21:44 -0700, Junio C Hamano wrote:\n> I'd rather see the behaviour match existing commands with path\n> restriction, like diff-tree, diff-cache, and diff-files.  That\n> is, to take a list of paths and limit your output to those that\n> match one of them.  I do not think this enhancement would\n> negatively affect your stated use of getting one entry with the\n> exact match.\n\nOk, will do.\n\n-- \nJason McMullan <jason.mcmullan@timesys.com>\nTimeSys Corporation\n\n"},{"id":"3986","messageId":"1117120411.11542.16.camel@jmcmullan.timesys","threadId":"717","inReplyTo":"1117111843.11542.11.camel@jmcmullan.timesys","subject":"Re: [PATCH] ls-tree matching multiple paths","fromName":"Jason McMullan","fromEmail":"jason.mcmullan@timesys.com","sentAt":"2005-05-26T15:13:29Z","receivedAt":"2005-05-26T15:13:29Z","isPatch":true,"sender":{"key":"jason.mcmullan@timesys.com","avatar":null},"body":"\nSame as before, but can now match multiple paths.\n\nRelies upon the name sorted order of the default git-ls-tree -r \ninternals.\n\nSigned-Off-By: Jason McMullan <jason.mcmullan@timesys.com>\n\ndiff --git a/Documentation/git-ls-tree.txt\nb/Documentation/git-ls-tree.txt\n--- a/Documentation/git-ls-tree.txt\n+++ b/Documentation/git-ls-tree.txt\n@@ -9,7 +9,7 @@ git-ls-tree - Displays a tree object in \n \n SYNOPSIS\n --------\n-'git-ls-tree' [-r] [-z] <tree-ish>\n+'git-ls-tree' [-r] [-z] <tree-ish> [paths...]\n \n DESCRIPTION\n -----------\n@@ -27,6 +27,11 @@ OPTIONS\n -z::\n \t\\0 line termination on output\n \n+paths::\n+\tOptionally, restrict the output of git-ls-tree to specific\n+\tpaths. Directories will only list their tree blob ids.\n+\tImplies -r.\n+\n Output Format\n -------------\n         <mode>\\t\t<type>\\t\t<object>\\t\t<file>\ndiff --git a/ls-tree.c b/ls-tree.c\n--- a/ls-tree.c\n+++ b/ls-tree.c\n@@ -13,20 +13,79 @@ struct path_prefix {\n \tconst char *name;\n };\n \n+#define DEBUG(fmt, ...)\t\n+\n+static int string_path_prefix(char *buff, size_t blen, struct\npath_prefix *prefix)\n+{\n+\tint len = 0;\n+\tif (prefix) {\n+\t\tif (prefix->prev) {\n+\t\t\tlen = string_path_prefix(buff,blen,prefix->prev);\n+\t\t\tbuff += len;\n+\t\t\tblen -= len;\n+\t\t\tif (blen > 0) {\n+\t\t\t\t*buff = '/';\n+\t\t\t\tlen++;\n+\t\t\t\tbuff++;\n+\t\t\t\tblen--;\n+\t\t\t}\n+\t\t}\n+\t\tstrncpy(buff,prefix->name,blen);\n+\t\treturn len + strlen(prefix->name);\n+\t}\n+\n+\treturn 0;\n+}\n+\n static void print_path_prefix(struct path_prefix *prefix)\n {\n \tif (prefix) {\n-\t\tif (prefix->prev)\n+\t\tif (prefix->prev) {\n \t\t\tprint_path_prefix(prefix->prev);\n+\t\t\tputchar('/');\n+\t\t}\n \t\tfputs(prefix->name, stdout);\n-\t\tputchar('/');\n \t}\n }\n \n+/*\n+ * return:\n+ * \t-1 if prefix is *not* a subset of path\n+ * \t 0 if prefix == path\n+ * \t 1 if prefix is a subset of path\n+ */\n+static int pathcmp(const char *path, struct path_prefix *prefix)\n+{\n+\tchar buff[PATH_MAX];\n+\tint len,slen;\n+\n+\tif (prefix == NULL)\n+\t\treturn 1;\n+\n+\tlen = string_path_prefix(buff, sizeof buff, prefix);\n+\tslen = strlen(path);\n+\n+\tif (slen < len)\n+\t\treturn -1;\n+\n+\tif (strncmp(path,buff,len) == 0) {\n+\t\tif (slen == len)\n+\t\t\treturn 0;\n+\t\telse\n+\t\t\treturn 1;\n+\t}\n+\n+\treturn -1;\n+}\t\n+\n+/*\n+ * match may be NULL, or a *sorted* list of paths\n+ */\n static void list_recursive(void *buffer,\n \t\t\t   const char *type,\n \t\t\t   unsigned long size,\n-\t\t\t   struct path_prefix *prefix)\n+\t\t\t   struct path_prefix *prefix,\n+\t\t\t   char **match, int matches)\n {\n \tstruct path_prefix this_prefix;\n \tthis_prefix.prev = prefix;\n@@ -34,54 +93,96 @@ static void list_recursive(void *buffer,\n \tif (strcmp(type, \"tree\"))\n \t\tdie(\"expected a 'tree' node\");\n \n+\tif (matches)\n+\t\trecursive = 1;\n+\n \twhile (size) {\n \t\tint namelen = strlen(buffer)+1;\n-\t\tvoid *eltbuf;\n+\t\tvoid *eltbuf = NULL;\n \t\tchar elttype[20];\n \t\tunsigned long eltsize;\n \t\tunsigned char *sha1 = buffer + namelen;\n \t\tchar *path = strchr(buffer, ' ') + 1;\n \t\tunsigned int mode;\n+\t\tconst char *matched = NULL;\n+\t\tint mtype = -1;\n+\t\tint mindex;\n \n \t\tif (size < namelen + 20 || sscanf(buffer, \"%o\", &mode) != 1)\n \t\t\tdie(\"corrupt 'tree' file\");\n \t\tbuffer = sha1 + 20;\n \t\tsize -= namelen + 20;\n \n-\t\tprintf(\"%06o\\t%s\\t%s\\t\", mode,\n-\t\t       S_ISDIR(mode) ? \"tree\" : \"blob\",\n-\t\t       sha1_to_hex(sha1));\n-\t\tprint_path_prefix(prefix);\n-\t\tfputs(path, stdout);\n-\t\tputchar(line_termination);\n+\t\tthis_prefix.name = path;\n+\t\tfor ( mindex = 0; mindex < matches; mindex++) {\n+\t\t\tmtype = pathcmp(match[mindex],&this_prefix);\n+\t\t\tif (mtype >= 0) {\n+\t\t\t\tmatched = match[mindex];\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n+\t\t/*\n+\t\t * If we're not matching, or if this is an exact match,\n+\t\t * print out the info\n+\t\t */\n+\t\tif (!matches || (matched != NULL && mtype == 0)) {\n+\t\t\tprintf(\"%06o\\t%s\\t%s\\t\", mode,\n+\t\t\t       S_ISDIR(mode) ? \"tree\" : \"blob\",\n+\t\t\t       sha1_to_hex(sha1));\n+\t\t\tprint_path_prefix(&this_prefix);\n+\t\t\tputchar(line_termination);\n+\t\t}\n \n \t\tif (! recursive || ! S_ISDIR(mode))\n \t\t\tcontinue;\n \n+\t\tif (matches && ! matched)\n+\t\t\tcontinue;\n+\n \t\tif (! (eltbuf = read_sha1_file(sha1, elttype, &eltsize)) ) {\n \t\t\terror(\"cannot read %s\", sha1_to_hex(sha1));\n \t\t\tcontinue;\n \t\t}\n-\t\tthis_prefix.name = path;\n-\t\tlist_recursive(eltbuf, elttype, eltsize, &this_prefix);\n+\n+\t\t/* If this is an exact directory match, we may have\n+\t\t * directory files following this path. Match on them.\n+\t\t * Otherwise, we're at a pach subcomponent, and we need\n+\t\t * to try to match again.\n+\t\t */\n+\t\tif (mtype == 0)\n+\t\t\tmindex++;\n+\n+\t\tlist_recursive(eltbuf, elttype, eltsize, &this_prefix,\n&match[mindex], matches-mindex);\n \t\tfree(eltbuf);\n \t}\n }\n \n-static int list(unsigned char *sha1)\n+static int qcmp(const void *a, const void *b)\n+{\n+\treturn strcmp(*(char **)a, *(char **)b);\n+}\n+\n+static int list(unsigned char *sha1,char **path)\n {\n \tvoid *buffer;\n \tunsigned long size;\n+\tint npaths;\n+\n+\tfor (npaths = 0; path[npaths] != NULL; npaths++)\n+\t\t;\n+\n+\tqsort(path,npaths,sizeof(char *),qcmp);\n \n \tbuffer = read_object_with_reference(sha1, \"tree\", &size, NULL);\n \tif (!buffer)\n \t\tdie(\"unable to read sha1 file\");\n-\tlist_recursive(buffer, \"tree\", size, NULL);\n+\tlist_recursive(buffer, \"tree\", size, NULL, path, npaths);\n \tfree(buffer);\n \treturn 0;\n }\n \n-static const char *ls_tree_usage = \"git-ls-tree [-r] [-z] <key>\";\n+static const char *ls_tree_usage = \"git-ls-tree [-r] [-z] <key>\n[paths...]\";\n \n int main(int argc, char **argv)\n {\n@@ -101,11 +202,11 @@ int main(int argc, char **argv)\n \t\targc--; argv++;\n \t}\n \n-\tif (argc != 2)\n+\tif (argc < 2)\n \t\tusage(ls_tree_usage);\n \tif (get_sha1(argv[1], sha1) < 0)\n \t\tusage(ls_tree_usage);\n-\tif (list(sha1) < 0)\n+\tif (list(sha1, &argv[2]) < 0)\n \t\tdie(\"list failed\");\n \treturn 0;\n }\n\n\n\n-- \nJason McMullan <jason.mcmullan@timesys.com>\nTimeSys Corporation\n\n"},{"id":"3987","messageId":"7vacmhdgct.fsf@assigned-by-dhcp.cox.net","threadId":"717","inReplyTo":"1117120411.11542.16.camel@jmcmullan.timesys","subject":"[PATCH] ls-tree matching multiple paths","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2005-05-26T17:52:50Z","receivedAt":"2005-05-26T17:52:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jason, could you double check your MUA setup?  The copy I got, and\nthe mailing list archive MARC has, both seems to have got line\nwrapped.  Here is the beginning of your message cut & pasted from\nhttp://marc.theaimsgroup.com/?l=git&m=111712054707691&w=2\n\n    ...\n    Signed-Off-By: Jason McMullan <jason.mcmullan@timesys.com>\n\n    diff --git a/Documentation/git-ls-tree.txt\n    b/Documentation/git-ls-tree.txt\n    --- a/Documentation/git-ls-tree.txt\n    ...\n\nI fixed it up by hand and added a test case, which your test passes\nfine.  Again, thanks for doing this.\n\n------------\nFrom: Jason McMullan <jason.mcmullan@timesys.com>\n\nEnhance git-ls-tree to allow optional 'match paths' that\nrestricts the output of git-ls-tree.  This is useful to retrieve\na single file's SHA1 out of a tree without creating an index.\n\n[JC: I added the test case]\n\nSigned-off-by: Junio C Hamano <junkio@cox.net>\n---\n\nDocumentation/git-ls-tree.txt |    7 +-\nls-tree.c                     |  135 ++++++++++++++++++++++++++++++++++++------\nt/t3100-ls-tree-restrict.sh   |  103 ++++++++++++++++++++++++++++++++\n3 files changed, 227 insertions(+), 18 deletions(-)\nnew file (100755): t/t3100-ls-tree-restrict.sh\n\ndiff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt\n--- a/Documentation/git-ls-tree.txt\n+++ b/Documentation/git-ls-tree.txt\n@@ -9,7 +9,7 @@ git-ls-tree - Displays a tree object in \n \n SYNOPSIS\n --------\n-'git-ls-tree' [-r] [-z] <tree-ish>\n+'git-ls-tree' [-r] [-z] <tree-ish> [paths...]\n \n DESCRIPTION\n -----------\n@@ -27,6 +27,11 @@ OPTIONS\n -z::\n \t\\0 line termination on output\n \n+paths::\n+\tOptionally, restrict the output of git-ls-tree to specific\n+\tpaths. Directories will only list their tree blob ids.\n+\tImplies -r.\n+\n Output Format\n -------------\n         <mode>\\t\t<type>\\t\t<object>\\t\t<file>\ndiff --git a/ls-tree.c b/ls-tree.c\n--- a/ls-tree.c\n+++ b/ls-tree.c\n@@ -13,20 +13,79 @@ struct path_prefix {\n \tconst char *name;\n };\n \n+#define DEBUG(fmt, ...)\t\n+\n+static int string_path_prefix(char *buff, size_t blen, struct path_prefix *prefix)\n+{\n+\tint len = 0;\n+\tif (prefix) {\n+\t\tif (prefix->prev) {\n+\t\t\tlen = string_path_prefix(buff,blen,prefix->prev);\n+\t\t\tbuff += len;\n+\t\t\tblen -= len;\n+\t\t\tif (blen > 0) {\n+\t\t\t\t*buff = '/';\n+\t\t\t\tlen++;\n+\t\t\t\tbuff++;\n+\t\t\t\tblen--;\n+\t\t\t}\n+\t\t}\n+\t\tstrncpy(buff,prefix->name,blen);\n+\t\treturn len + strlen(prefix->name);\n+\t}\n+\n+\treturn 0;\n+}\n+\n static void print_path_prefix(struct path_prefix *prefix)\n {\n \tif (prefix) {\n-\t\tif (prefix->prev)\n+\t\tif (prefix->prev) {\n \t\t\tprint_path_prefix(prefix->prev);\n+\t\t\tputchar('/');\n+\t\t}\n \t\tfputs(prefix->name, stdout);\n-\t\tputchar('/');\n \t}\n }\n \n+/*\n+ * return:\n+ * \t-1 if prefix is *not* a subset of path\n+ * \t 0 if prefix == path\n+ * \t 1 if prefix is a subset of path\n+ */\n+static int pathcmp(const char *path, struct path_prefix *prefix)\n+{\n+\tchar buff[PATH_MAX];\n+\tint len,slen;\n+\n+\tif (prefix == NULL)\n+\t\treturn 1;\n+\n+\tlen = string_path_prefix(buff, sizeof buff, prefix);\n+\tslen = strlen(path);\n+\n+\tif (slen < len)\n+\t\treturn -1;\n+\n+\tif (strncmp(path,buff,len) == 0) {\n+\t\tif (slen == len)\n+\t\t\treturn 0;\n+\t\telse\n+\t\t\treturn 1;\n+\t}\n+\n+\treturn -1;\n+}\t\n+\n+/*\n+ * match may be NULL, or a *sorted* list of paths\n+ */\n static void list_recursive(void *buffer,\n \t\t\t   const char *type,\n \t\t\t   unsigned long size,\n-\t\t\t   struct path_prefix *prefix)\n+\t\t\t   struct path_prefix *prefix,\n+\t\t\t   char **match, int matches)\n {\n \tstruct path_prefix this_prefix;\n \tthis_prefix.prev = prefix;\n@@ -34,54 +93,96 @@ static void list_recursive(void *buffer,\n \tif (strcmp(type, \"tree\"))\n \t\tdie(\"expected a 'tree' node\");\n \n+\tif (matches)\n+\t\trecursive = 1;\n+\n \twhile (size) {\n \t\tint namelen = strlen(buffer)+1;\n-\t\tvoid *eltbuf;\n+\t\tvoid *eltbuf = NULL;\n \t\tchar elttype[20];\n \t\tunsigned long eltsize;\n \t\tunsigned char *sha1 = buffer + namelen;\n \t\tchar *path = strchr(buffer, ' ') + 1;\n \t\tunsigned int mode;\n+\t\tconst char *matched = NULL;\n+\t\tint mtype = -1;\n+\t\tint mindex;\n \n \t\tif (size < namelen + 20 || sscanf(buffer, \"%o\", &mode) != 1)\n \t\t\tdie(\"corrupt 'tree' file\");\n \t\tbuffer = sha1 + 20;\n \t\tsize -= namelen + 20;\n \n-\t\tprintf(\"%06o\\t%s\\t%s\\t\", mode,\n-\t\t       S_ISDIR(mode) ? \"tree\" : \"blob\",\n-\t\t       sha1_to_hex(sha1));\n-\t\tprint_path_prefix(prefix);\n-\t\tfputs(path, stdout);\n-\t\tputchar(line_termination);\n+\t\tthis_prefix.name = path;\n+\t\tfor ( mindex = 0; mindex < matches; mindex++) {\n+\t\t\tmtype = pathcmp(match[mindex],&this_prefix);\n+\t\t\tif (mtype >= 0) {\n+\t\t\t\tmatched = match[mindex];\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\n+\t\t/*\n+\t\t * If we're not matching, or if this is an exact match,\n+\t\t * print out the info\n+\t\t */\n+\t\tif (!matches || (matched != NULL && mtype == 0)) {\n+\t\t\tprintf(\"%06o\\t%s\\t%s\\t\", mode,\n+\t\t\t       S_ISDIR(mode) ? \"tree\" : \"blob\",\n+\t\t\t       sha1_to_hex(sha1));\n+\t\t\tprint_path_prefix(&this_prefix);\n+\t\t\tputchar(line_termination);\n+\t\t}\n \n \t\tif (! recursive || ! S_ISDIR(mode))\n \t\t\tcontinue;\n \n+\t\tif (matches && ! matched)\n+\t\t\tcontinue;\n+\n \t\tif (! (eltbuf = read_sha1_file(sha1, elttype, &eltsize)) ) {\n \t\t\terror(\"cannot read %s\", sha1_to_hex(sha1));\n \t\t\tcontinue;\n \t\t}\n-\t\tthis_prefix.name = path;\n-\t\tlist_recursive(eltbuf, elttype, eltsize, &this_prefix);\n+\n+\t\t/* If this is an exact directory match, we may have\n+\t\t * directory files following this path. Match on them.\n+\t\t * Otherwise, we're at a pach subcomponent, and we need\n+\t\t * to try to match again.\n+\t\t */\n+\t\tif (mtype == 0)\n+\t\t\tmindex++;\n+\n+\t\tlist_recursive(eltbuf, elttype, eltsize, &this_prefix, &match[mindex], matches-mindex);\n \t\tfree(eltbuf);\n \t}\n }\n \n-static int list(unsigned char *sha1)\n+static int qcmp(const void *a, const void *b)\n+{\n+\treturn strcmp(*(char **)a, *(char **)b);\n+}\n+\n+static int list(unsigned char *sha1,char **path)\n {\n \tvoid *buffer;\n \tunsigned long size;\n+\tint npaths;\n+\n+\tfor (npaths = 0; path[npaths] != NULL; npaths++)\n+\t\t;\n+\n+\tqsort(path,npaths,sizeof(char *),qcmp);\n \n \tbuffer = read_object_with_reference(sha1, \"tree\", &size, NULL);\n \tif (!buffer)\n \t\tdie(\"unable to read sha1 file\");\n-\tlist_recursive(buffer, \"tree\", size, NULL);\n+\tlist_recursive(buffer, \"tree\", size, NULL, path, npaths);\n \tfree(buffer);\n \treturn 0;\n }\n \n-static const char *ls_tree_usage = \"git-ls-tree [-r] [-z] <key>\";\n+static const char *ls_tree_usage = \"git-ls-tree [-r] [-z] <key> [paths...]\";\n \n int main(int argc, char **argv)\n {\n@@ -101,11 +202,11 @@ int main(int argc, char **argv)\n \t\targc--; argv++;\n \t}\n \n-\tif (argc != 2)\n+\tif (argc < 2)\n \t\tusage(ls_tree_usage);\n \tif (get_sha1(argv[1], sha1) < 0)\n \t\tusage(ls_tree_usage);\n-\tif (list(sha1) < 0)\n+\tif (list(sha1, &argv[2]) < 0)\n \t\tdie(\"list failed\");\n \treturn 0;\n }\ndiff --git a/t/t3100-ls-tree-restrict.sh b/t/t3100-ls-tree-restrict.sh\nnew file mode 100755\n--- /dev/null\n+++ b/t/t3100-ls-tree-restrict.sh\n@@ -0,0 +1,103 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2005 Junio C Hamano\n+#\n+\n+test_description='git-ls-tree test.\n+\n+This test runs git-ls-tree with the following in a tree.\n+\n+    path0       - a file\n+    path1\t- a symlink\n+    path2/foo   - a file in a directory\n+    path2/bazbo - a symlink in a directory\n+    path2/baz/b - a file in a directory in a directory\n+\n+The new path restriction code should do the right thing for path2 and\n+path2/baz\n+'\n+. ./test-lib.sh\n+\n+test_expect_success \\\n+    'setup' \\\n+    'mkdir path2 path2/baz &&\n+     echo Hi >path0 &&\n+     ln -s path0 path1 &&\n+     echo Lo >path2/foo &&\n+     ln -s ../path1 path2/bazbo &&\n+     echo Mi >path2/baz/b &&\n+     find path? \\( -type f -o -type l \\) -print |\n+     xargs git-update-cache --add &&\n+     tree=`git-write-tree` &&\n+     echo $tree'\n+\n+_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'\n+_x40=\"$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40\"\n+test_output () {\n+    sed -e \"s/\t$_x40\t/\tX\t/\" <current >check\n+    diff -u expected check\n+}\n+\n+test_expect_success \\\n+    'ls-tree plain' \\\n+    'git-ls-tree $tree >current &&\n+     cat >expected <<\\EOF &&\n+100644\tblob\tX\tpath0\n+120000\tblob\tX\tpath1\n+040000\ttree\tX\tpath2\n+EOF\n+     test_output'\n+\n+test_expect_success \\\n+    'ls-tree recursive' \\\n+    'git-ls-tree -r $tree >current &&\n+     cat >expected <<\\EOF &&\n+100644\tblob\tX\tpath0\n+120000\tblob\tX\tpath1\n+040000\ttree\tX\tpath2\n+040000\ttree\tX\tpath2/baz\n+100644\tblob\tX\tpath2/baz/b\n+120000\tblob\tX\tpath2/bazbo\n+100644\tblob\tX\tpath2/foo\n+EOF\n+     test_output'\n+\n+test_expect_success \\\n+    'ls-tree filtered' \\\n+    'git-ls-tree $tree path >current &&\n+     cat >expected <<\\EOF &&\n+EOF\n+     test_output'\n+\n+\n+test_expect_success \\\n+    'ls-tree filtered' \\\n+    'git-ls-tree $tree path1 path0 >current &&\n+     cat >expected <<\\EOF &&\n+100644\tblob\tX\tpath0\n+120000\tblob\tX\tpath1\n+EOF\n+     test_output'\n+\n+test_expect_success \\\n+    'ls-tree filtered' \\\n+    'git-ls-tree $tree path2 >current &&\n+     cat >expected <<\\EOF &&\n+040000\ttree\tX\tpath2\n+040000\ttree\tX\tpath2/baz\n+100644\tblob\tX\tpath2/baz/b\n+120000\tblob\tX\tpath2/bazbo\n+100644\tblob\tX\tpath2/foo\n+EOF\n+     test_output'\n+\n+test_expect_success \\\n+    'ls-tree filtered' \\\n+    'git-ls-tree $tree path2/baz >current &&\n+     cat >expected <<\\EOF &&\n+040000\ttree\tX\tpath2/baz\n+100644\tblob\tX\tpath2/baz/b\n+EOF\n+     test_output'\n+\n+test_done\n\n"},{"id":"3988","messageId":"7v4qcpdfpe.fsf@assigned-by-dhcp.cox.net","threadId":"717","inReplyTo":"7vacmhdgct.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH] ls-tree matching multiple paths","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2005-05-26T18:06:53Z","receivedAt":"2005-05-26T18:06:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":">>>>> \"JCH\" == Junio C Hamano <junkio@cox.net> writes:\n\nJCH> +040000\ttree\tX\tpath2/baz\nJCH> +100644\tblob\tX\tpath2/baz/b\n\nSide note.  Linus, don't you think it would make more sense to\nchange the column separator befor type and SHA1 from TAB to SP?\nWe should keep the one before the path to TAB for easier\nsplitting with cut (which defaults to a TAB).  This comes from\nthe same reasoning as the latest diff-raw format design we did.\n\nWhile we are at it, we may want to do the same to ls-files,\nwhich does not use TAB for inter_name_termination (it uses SP\nfor everything).\n\n"}]}