{"thread":{"id":"39285","subject":"[PATCH v3 1/3] tree-walk: learn get_tree_entry_follow_symlinks","startedAt":"2015-05-09T20:36:07Z","lastAt":"2015-05-11T17:45:03Z","messageCount":8,"participants":["dturner@twopensource.com","Johannes Sixt","Junio C Hamano","David Turner"],"isPatch":true,"patchVersion":3,"patchTotal":3},"messages":[{"id":"260907","messageId":"1431203769-11855-1-git-send-email-dturner@twopensource.com","threadId":"39285","inReplyTo":null,"subject":"[PATCH v3 1/3] tree-walk: learn get_tree_entry_follow_symlinks","fromName":"","fromEmail":"dturner@twopensource.com","sentAt":"2015-05-09T20:36:07Z","receivedAt":"2015-05-09T20:36:07Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"From: David Turner <dturner@twitter.com>\n\nAdd a new function, get_tree_entry_follow_symlinks, to tree-walk.[ch].\nThe function is not yet used.  It will be used to implement git\ncat-file --batch --follow-symlinks.\n\nThe function locates an object by path, following symlinks in the\nrepository.  If the symlinks lead outside the repository, the function\nreports this to the caller.\n\nSigned-off-by: David Turner <dturner@twitter.com>\n---\n cache.h     |   5 ++\n tree-walk.c | 195 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n tree-walk.h |   2 +\n 3 files changed, 202 insertions(+)\n\ndiff --git a/cache.h b/cache.h\nindex 3d3244b..679faa9 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -922,6 +922,11 @@ struct object_context {\n \tunsigned char tree[20];\n \tchar path[PATH_MAX];\n \tunsigned mode;\n+\t/*\n+\t * symlink_path is only used by get_tree_entry_follow_symlinks,\n+\t * and only for symlinks that point outside the repository.\n+\t */\n+\tstruct strbuf symlink_path;\n };\n \n #define GET_SHA1_QUIETLY        01\ndiff --git a/tree-walk.c b/tree-walk.c\nindex 5dd9a71..f93492d 100644\n--- a/tree-walk.c\n+++ b/tree-walk.c\n@@ -415,6 +415,12 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)\n \treturn error;\n }\n \n+struct dir_state {\n+\tvoid *tree;\n+\tunsigned long size;\n+\tunsigned char sha1[20];\n+};\n+\n static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char *result, unsigned *mode)\n {\n \tint namelen = strlen(name);\n@@ -478,6 +484,195 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch\n \treturn retval;\n }\n \n+/* This is Linux's built-in max for the number of symlinks to follow.\n+ * That limit, of course, does not affect git, but it's a reasonable\n+ * choice.\n+ */\n+#define GET_TREE_ENTRY_FOLLOW_SYMLINKS_MAX_LINKS 40\n+\n+/**\n+ * Find a tree entry by following symlinks in tree_sha (which is\n+ * assumed to be the root of the repository).  In the event that a\n+ * symlink points outside the repository (e.g. a link to /foo or a\n+ * root-level link to ../foo), the portion of the link which is\n+ * outside the repository will be returned in result_path, and *mode\n+ * will be set to 0.  It is assumed that result_path is uninitialized.\n+ * If there are no symlinks, or the end result of the symlink chain\n+ * points to an object inside the repository, result will be filled in\n+ * with the sha1 of the found object, and *mode will hold the mode of\n+ * the object.  If there is a symlink loop, -1 will be returned.\n+ */\n+int get_tree_entry_follow_symlinks(unsigned char *tree_sha1, const char *name, unsigned char *result, struct strbuf *result_path, unsigned *mode)\n+{\n+\tint retval = -1;\n+\tvoid *tree;\n+\tstruct dir_state *parents = NULL;\n+\tsize_t parents_alloc = 0;\n+\tssize_t parents_nr = 0;\n+\tunsigned long size;\n+\tunsigned char root[20];\n+\tunsigned char current_tree_sha1[20];\n+\tstruct strbuf namebuf = STRBUF_INIT;\n+\tenum object_type type;\n+\tint already_have_tree = 0;\n+\tstruct tree_desc t = {0};\n+\tint follows_remaining = GET_TREE_ENTRY_FOLLOW_SYMLINKS_MAX_LINKS;\n+\tint i;\n+\n+\tresult_path->buf = 0;\n+\tresult_path->alloc = 0;\n+\tresult_path->len = 0;\n+\tstrbuf_addstr(&namebuf, name);\n+\thashcpy(current_tree_sha1, tree_sha1);\n+\n+\twhile (1) {\n+\t\tchar *first_slash;\n+\t\tchar *remainder = NULL;\n+\t\tint find_result;\n+\n+\t\tif (!t.buffer) {\n+\t\t\ttree = read_object_with_reference(current_tree_sha1,\n+\t\t\t\t\t\t\t  tree_type, &size,\n+\t\t\t\t\t\t\t  root);\n+\t\t\tif (!tree)\n+\t\t\t\tgoto done;\n+\n+\t\t\tALLOC_GROW(parents, parents_nr + 1, parents_alloc);\n+\t\t\tparents[parents_nr].tree = tree;\n+\t\t\tparents[parents_nr].size = size;\n+\t\t\thashcpy(parents[parents_nr].sha1, root);\n+\n+\t\t\tparents_nr++;\n+\n+\t\t\tif (namebuf.buf[0] == '\\0') {\n+\t\t\t\thashcpy(result, root);\n+\t\t\t\tretval = 0;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\n+\t\t\tif (!size)\n+\t\t\t\tgoto done;\n+\n+\t\t\t/* descend */\n+\t\t\tinit_tree_desc(&t, tree, size);\n+\t\t}\n+\n+\t\t/* Handle symlinks to e.g. a//b by removing leading slashes */\n+\t\twhile (namebuf.buf[0] == '/') {\n+\t\t\tstrbuf_remove(&namebuf, 0, 1);\n+\t\t}\n+\n+\t\t/* Split namebuf into a first component and a\n+\t\t * remainder */\n+\t\tif ((first_slash = strchr(namebuf.buf, '/'))) {\n+\t\t\t*first_slash = 0;\n+\t\t\tremainder = first_slash + 1;\n+\t\t}\n+\n+\t\tif (!strcmp(namebuf.buf, \"..\")) {\n+\t\t\tstruct dir_state *parent;\n+\t\t\t/* We could end up with .. in the namebuf if\n+\t\t\t * it appears in a symlink. */\n+\n+\t\t\tif (parents_nr == 1) {\n+\t\t\t\tif (remainder)\n+\t\t\t\t\t*first_slash = '/';\n+\t\t\t\tstrbuf_add(result_path, namebuf.buf,\n+\t\t\t\t\t   namebuf.len);\n+\t\t\t\t*mode = 0;\n+\t\t\t\tretval = 0;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\t\t\tparent = &parents[parents_nr - 1];\n+\t\t\tfree(parent->tree);\n+\t\t\tparents_nr--;\n+\t\t\tparent = &parents[parents_nr - 1];\n+\t\t\tinit_tree_desc(&t, parent->tree, parent->size);\n+\t\t\tstrbuf_remove(&namebuf, 0, remainder ? 3 : 2);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\t/* We could end up here via a symlink to dir/.. */\n+\t\tif (namebuf.buf[0] == '\\0') {\n+\t\t\thashcpy(result, parents[parents_nr - 1].sha1);\n+\t\t\tretval = 0;\n+\t\t\tgoto done;\n+\t\t}\n+\n+\t\t/* Look up the first (or only) path component\n+\t\t * in the tree. */\n+\t\tfind_result = find_tree_entry(&t, namebuf.buf,\n+\t\t\t\t\t      current_tree_sha1, mode);\n+\t\tif (find_result) {\n+\t\t\tretval = find_result;\n+\t\t\tgoto done;\n+\t\t}\n+\n+\t\tif (S_ISDIR(*mode)) {\n+\t\t\tif (!remainder) {\n+\t\t\t\thashcpy(result, current_tree_sha1);\n+\t\t\t\tretval = 0;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\t\t\t/* Descend the tree */\n+\t\t\tt.buffer = NULL;\n+\t\t\tstrbuf_remove(&namebuf, 0,\n+\t\t\t\t      1 + first_slash - namebuf.buf);\n+\t\t} else if (S_ISREG(*mode)) {\n+\t\t\tif (!remainder) {\n+\t\t\t\thashcpy(result, current_tree_sha1);\n+\t\t\t\tretval = 0;\n+\t\t\t}\n+\t\t\tgoto done;\n+\t\t} else if (S_ISLNK(*mode)) {\n+\t\t\t/* Follow a symlink */\n+\t\t\tsize_t link_len, len;\n+\t\t\tchar *contents, *contents_start;\n+\t\t\tstruct dir_state *parent;\n+\n+\t\t\tif (follows_remaining-- == 0)\n+\t\t\t\t/* Too many symlinks followed */\n+\t\t\t\tgoto done;\n+\n+\t\t\tcontents = read_sha1_file(current_tree_sha1, &type,\n+\t\t\t\t\t\t  &link_len);\n+\n+\t\t\tif (!contents)\n+\t\t\t\tgoto done;\n+\n+\t\t\tif (contents[0] == '/') {\n+\t\t\t\tstrbuf_addstr(result_path, contents);\n+\t\t\t\t*mode = 0;\n+\t\t\t\tretval = 0;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\n+\t\t\tif (remainder)\n+\t\t\t\tlen = first_slash - namebuf.buf;\n+\t\t\telse\n+\t\t\t\tlen = namebuf.len;\n+\n+\t\t\tcontents_start = contents;\n+\n+\t\t\tparent = &parents[parents_nr - 1];\n+\t\t\tinit_tree_desc(&t, parent->tree, parent->size);\n+\t\t\tstrbuf_splice(&namebuf, 0, len,\n+\t\t\t\t      contents_start, link_len);\n+\t\t\tif (remainder)\n+\t\t\t\tnamebuf.buf[link_len] = '/';\n+\t\t\tfree(contents);\n+\t\t}\n+\t}\n+done:\n+\tfor (i = 0; i < parents_nr; ++i) {\n+\t\tfree(parents[i].tree);\n+\t}\n+\tfree(parents);\n+\n+\tstrbuf_release(&namebuf);\n+\treturn retval;\n+}\n+\n static int match_entry(const struct pathspec_item *item,\n \t\t       const struct name_entry *entry, int pathlen,\n \t\t       const char *match, int matchlen,\ndiff --git a/tree-walk.h b/tree-walk.h\nindex ae7fb3a..d1670c6 100644\n--- a/tree-walk.h\n+++ b/tree-walk.h\n@@ -40,6 +40,8 @@ struct traverse_info;\n typedef int (*traverse_callback_t)(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *);\n int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info);\n \n+int get_tree_entry_follow_symlinks(unsigned char *tree_sha1, const char *name, unsigned char *result, struct strbuf *result_path, unsigned *mode);\n+\n struct traverse_info {\n \tstruct traverse_info *prev;\n \tstruct name_entry name;\n-- \n2.0.4.315.gad8727a-twtrsrc\n"},{"id":"260908","messageId":"1431203769-11855-2-git-send-email-dturner@twopensource.com","threadId":"39285","inReplyTo":"1431203769-11855-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 2/3] sha1_name: get_sha1_with_context learns to follow symlinks","fromName":"","fromEmail":"dturner@twopensource.com","sentAt":"2015-05-09T20:36:08Z","receivedAt":"2015-05-09T20:36:08Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"From: David Turner <dturner@twitter.com>\n\nWire up get_sha1_with_context to call get_tree_entry_follow_symlinks\nwhen GET_SHA1_FOLLOW_SYMLINKS is passed in flags. G_S_FOLLOW_SYMLINKS\nis incompatible with G_S_ONLY_TO_DIE because the diagnosis\nthat ONLY_TO_DIE triggers does not at present consider symlinks, and\nit would be a significant amount of additional code to allow it to\ndo so.\n\nSigned-off-by: David Turner <dturner@twitter.com>\n---\n cache.h     | 15 ++++++++-------\n sha1_name.c | 23 ++++++++++++++++-------\n 2 files changed, 24 insertions(+), 14 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex 679faa9..65505d1 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -929,13 +929,14 @@ struct object_context {\n \tstruct strbuf symlink_path;\n };\n \n-#define GET_SHA1_QUIETLY        01\n-#define GET_SHA1_COMMIT         02\n-#define GET_SHA1_COMMITTISH     04\n-#define GET_SHA1_TREE          010\n-#define GET_SHA1_TREEISH       020\n-#define GET_SHA1_BLOB\t       040\n-#define GET_SHA1_ONLY_TO_DIE 04000\n+#define GET_SHA1_QUIETLY           01\n+#define GET_SHA1_COMMIT            02\n+#define GET_SHA1_COMMITTISH        04\n+#define GET_SHA1_TREE             010\n+#define GET_SHA1_TREEISH          020\n+#define GET_SHA1_BLOB             040\n+#define GET_SHA1_FOLLOW_SYMLINKS 0100\n+#define GET_SHA1_ONLY_TO_DIE    04000\n \n extern int get_sha1(const char *str, unsigned char *sha1);\n extern int get_sha1_commit(const char *str, unsigned char *sha1);\ndiff --git a/sha1_name.c b/sha1_name.c\nindex 6d10f05..325f666 100644\n--- a/sha1_name.c\n+++ b/sha1_name.c\n@@ -1434,15 +1434,22 @@ static int get_sha1_with_context_1(const char *name,\n \t\t\tnew_filename = resolve_relative_path(filename);\n \t\t\tif (new_filename)\n \t\t\t\tfilename = new_filename;\n-\t\t\tret = get_tree_entry(tree_sha1, filename, sha1, &oc->mode);\n-\t\t\tif (ret && only_to_die) {\n-\t\t\t\tdiagnose_invalid_sha1_path(prefix, filename,\n-\t\t\t\t\t\t\t   tree_sha1,\n-\t\t\t\t\t\t\t   name, len);\n+\t\t\tif (flags & GET_SHA1_FOLLOW_SYMLINKS) {\n+\t\t\t\tret = get_tree_entry_follow_symlinks(tree_sha1,\n+\t\t\t\t\tfilename, sha1, &oc->symlink_path,\n+\t\t\t\t\t&oc->mode);\n+\t\t\t} else {\n+\t\t\t\tret = get_tree_entry(tree_sha1, filename,\n+\t\t\t\t\t\t     sha1, &oc->mode);\n+\t\t\t\tif (ret && only_to_die) {\n+\t\t\t\t\tdiagnose_invalid_sha1_path(prefix,\n+\t\t\t\t\t\t\t\t   filename,\n+\t\t\t\t\t\t\t\t   tree_sha1,\n+\t\t\t\t\t\t\t\t   name, len);\n+\t\t\t\t}\n \t\t\t}\n-\t\t\thashcpy(oc->tree, tree_sha1);\n \t\t\tstrlcpy(oc->path, filename, sizeof(oc->path));\n-\n+\t\t\thashcpy(oc->tree, tree_sha1);\n \t\t\tfree(new_filename);\n \t\t\treturn ret;\n \t\t} else {\n@@ -1469,5 +1476,7 @@ void maybe_die_on_misspelt_object_name(const char *name, const char *prefix)\n \n int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *orc)\n {\n+\tif (flags & GET_SHA1_FOLLOW_SYMLINKS && flags & GET_SHA1_ONLY_TO_DIE)\n+\t\tdie(\"BUG: incompatible flags for get_sha1_with_context\");\n \treturn get_sha1_with_context_1(str, flags, NULL, sha1, orc);\n }\n-- \n2.0.4.315.gad8727a-twtrsrc\n"},{"id":"260909","messageId":"1431203769-11855-3-git-send-email-dturner@twopensource.com","threadId":"39285","inReplyTo":"1431203769-11855-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v3 3/3] cat-file: add --follow-symlinks to --batch","fromName":"","fromEmail":"dturner@twopensource.com","sentAt":"2015-05-09T20:36:09Z","receivedAt":"2015-05-09T20:36:09Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"From: David Turner <dturner@twitter.com>\n\nThis wires the in-repo-symlink following code through to the cat-file\nbuiltin.  In the event of an out-of-repo link, cat-file will print\nthe link in a new format.\n\nSigned-off-by: David Turner <dturner@twitter.com>\n---\n Documentation/git-cat-file.txt |  28 +++++-\n builtin/cat-file.c             |  24 +++++-\n t/t1006-cat-file.sh            | 190 +++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 237 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt\nindex f6a16f4..18b67a3 100644\n--- a/Documentation/git-cat-file.txt\n+++ b/Documentation/git-cat-file.txt\n@@ -10,7 +10,7 @@ SYNOPSIS\n --------\n [verse]\n 'git cat-file' (-t | -s | -e | -p | <type> | --textconv ) <object>\n-'git cat-file' (--batch | --batch-check) < <list-of-objects>\n+'git cat-file' (--batch | --batch-check) [--follow-symlinks] < <list-of-objects>\n \n DESCRIPTION\n -----------\n@@ -69,6 +69,19 @@ OPTIONS\n \tnot be combined with any other options or arguments.  See the\n \tsection `BATCH OUTPUT` below for details.\n \n+--follow-symlinks::\n+\tFollow symlinks inside the repository.  Instead of providing\n+\toutput about the link itself, provide output about the linked-to\n+\tobject.  This option requires --batch or --batch-check.  In the\n+\tevent of a symlink loop (or more than 40 symlinks in a symlink\n+\tresolution chain), the file will be treated as missing.  If a\n+\tsymlink points outside the repository (e.g. a link to /foo or a\n+\troot-level link to ../foo), the portion of the link which is\n+\toutside the repository will be printed.  Follow-symlinks will\n+\tbe silently turned off if <object> specifies an object in the\n+\tindex rather than one in the object database.\n+\n+\n OUTPUT\n ------\n If '-t' is specified, one of the <type>.\n@@ -148,6 +161,19 @@ the repository, then `cat-file` will ignore any custom format and print:\n <object> SP missing LF\n ------------\n \n+If --follow-symlinks is used, and a symlink in the repository points\n+outside the repository, then `cat-file` will ignore any custom format\n+and print:\n+\n+------------\n+symlink SP <size> LF <symlink> LF\n+------------\n+\n+The symlink will either be absolute (beginning with a /), or relative\n+to the repository root.  For instance, if dir/link points to ../../foo,\n+then <symlink> will be ../foo.  <size> is the size of the symlink in\n+bytes.\n+\n \n CAVEATS\n -------\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex df99df4..6bcf9a0 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -224,6 +224,7 @@ static void print_object_or_die(int fd, struct expand_data *data)\n \n struct batch_options {\n \tint enabled;\n+\tint follow_symlinks;\n \tint print_contents;\n \tconst char *format;\n };\n@@ -232,16 +233,25 @@ static int batch_one_object(const char *obj_name, struct batch_options *opt,\n \t\t\t    struct expand_data *data)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n+\tstruct object_context ctx;\n+\tint flags = opt->follow_symlinks ? GET_SHA1_FOLLOW_SYMLINKS : 0;\n \n \tif (!obj_name)\n \t   return 1;\n \n-\tif (get_sha1(obj_name, data->sha1)) {\n+\tif (get_sha1_with_context(obj_name, flags, data->sha1, &ctx)) {\n \t\tprintf(\"%s missing\\n\", obj_name);\n \t\tfflush(stdout);\n \t\treturn 0;\n \t}\n \n+\tif (ctx.mode == 0) {\n+\t\tprintf(\"symlink %\"PRIuMAX\"\\n%s\\n\",\n+\t\t       (uintmax_t)ctx.symlink_path.len,\n+\t\t       ctx.symlink_path.buf);\n+\t\treturn 0;\n+\t}\n+\n \tif (sha1_object_info_extended(data->sha1, &data->info, LOOKUP_REPLACE_OBJECT) < 0) {\n \t\tprintf(\"%s missing\\n\", obj_name);\n \t\tfflush(stdout);\n@@ -342,9 +352,8 @@ static int batch_option_callback(const struct option *opt,\n {\n \tstruct batch_options *bo = opt->value;\n \n-\tif (unset) {\n-\t\tmemset(bo, 0, sizeof(*bo));\n-\t\treturn 0;\n+\tif (bo->enabled) {\n+\t\treturn 1;\n \t}\n \n \tbo->enabled = 1;\n@@ -369,6 +378,9 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)\n \t\tOPT_SET_INT('p', NULL, &opt, N_(\"pretty-print object's content\"), 'p'),\n \t\tOPT_SET_INT(0, \"textconv\", &opt,\n \t\t\t    N_(\"for blob objects, run textconv on object's content\"), 'c'),\n+\t\tOPT_SET_INT(0, \"follow-symlinks\", &batch.follow_symlinks,\n+\t\t\tN_(\"follow in-repo symlinks; report out-of-repo symlinks (requires --batch or --batch-check)\"),\n+\t\t\t    1),\n \t\t{ OPTION_CALLBACK, 0, \"batch\", &batch, \"format\",\n \t\t\tN_(\"show info and content of objects fed from the standard input\"),\n \t\t\tPARSE_OPT_OPTARG, batch_option_callback },\n@@ -402,6 +414,10 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(cat_file_usage, options);\n \t}\n \n+\tif (batch.follow_symlinks && !batch.enabled) {\n+\t\tusage_with_options(cat_file_usage, options);\n+\t}\n+\n \tif (batch.enabled)\n \t\treturn batch_objects(&batch);\n \ndiff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\nindex ab36b1e..dd80c08 100755\n--- a/t/t1006-cat-file.sh\n+++ b/t/t1006-cat-file.sh\n@@ -189,6 +189,13 @@ do\n     '\n done\n \n+for opt in t s e p\n+do\n+    test_expect_success \"Passing -$opt with --follow-symlinks fails\" '\n+\t    test_must_fail git cat-file --follow-symlinks -$opt $hello_sha1\n+\t'\n+done\n+\n test_expect_success \"--batch-check for a non-existent named object\" '\n     test \"foobar42 missing\n foobar84 missing\" = \\\n@@ -296,4 +303,187 @@ test_expect_success '%(deltabase) reports packed delta bases' '\n \t}\n '\n \n+# Tests for git cat-file --follow-symlinks\n+test_expect_success 'prep for symlink tests' '\n+\techo_without_newline \"$hello_content\" > morx &&\n+\tln -s morx same-dir-link &&\n+\tln -s ../fleem out-of-repo-link &&\n+\tln -s .. out-of-repo-link-dir &&\n+\tln -s same-dir-link link-to-link &&\n+\tln -s nope broken-same-dir-link &&\n+\tmkdir dir &&\n+\tln -s ../morx dir/parent-dir-link &&\n+\tln -s .. dir/link-dir &&\n+\tln -s ../../escape dir/out-of-repo-link &&\n+\tln -s ../.. dir/out-of-repo-link-dir &&\n+\tln -s nope dir/broken-link-in-dir &&\n+\tmkdir dir/subdir &&\n+\tln -s ../../morx dir/subdir/grandparent-dir-link &&\n+\tln -s ../../../great-escape dir/subdir/out-of-repo-link &&\n+\tln -s ../../.. dir/subdir/out-of-repo-link-dir &&\n+\tln -s ../../../ dir/subdir/out-of-repo-link-dir-trailing &&\n+\tln -s ../parent-dir-link dir/subdir/parent-dir-link-to-link &&\n+\techo_without_newline \"$hello_content\" >dir/subdir/ind2 &&\n+\techo_without_newline \"$hello_content\" >dir/ind1 &&\n+\tln -s dir dirlink &&\n+\tln -s dir/subdir subdirlink &&\n+\tln -s subdir/ind2 dir/link-to-child &&\n+\tln -s dir/link-to-child link-to-down-link &&\n+\tln -s dir/.. up-down &&\n+\tln -s dir/../ up-down-trailing &&\n+\tln -s dir/../morx up-down-file &&\n+\tln -s dir/../../morx up-up-down-file &&\n+\tln -s subdirlink/../../morx up-two-down-file &&\n+\tln -s loop1 loop2 &&\n+\tln -s loop2 loop1 &&\n+\tgit add . &&\n+\tgit commit -am \"test\"\n+'\n+\n+echo $hello_sha1 blob $hello_size > found\n+\n+test_expect_success 'git cat-file --batch-check --follow-symlinks works for non-links' '\n+\techo HEAD:morx | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp found actual &&\n+\techo HEAD:nope missing > expect &&\n+\techo HEAD:nope | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git cat-file --batch-check --follow-symlinks works for in-repo, same-dir links' '\n+\techo HEAD:same-dir-link | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp found actual\n+'\n+\n+test_expect_success 'git cat-file --batch-check --follow-symlinks works for broken in-repo, same-dir links' '\n+\techo HEAD:broken-same-dir-link missing > expect &&\n+\techo HEAD:broken-same-dir-link | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git cat-file --batch-check --follow-symlinks works for same-dir links-to-links' '\n+\techo HEAD:link-to-link | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp found actual\n+'\n+\n+test_expect_success 'git cat-file --batch-check --follow-symlinks works for parent-dir links' '\n+\techo HEAD:dir/parent-dir-link | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp found actual &&\n+\techo HEAD:dir/parent-dir-link/nope missing > expect &&\n+\techo HEAD:dir/parent-dir-link/nope | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git cat-file --batch-check --follow-symlinks works for .. links' '\n+\techo HEAD:dir/link-dir/nope missing > expect &&\n+\techo HEAD:dir/link-dir/nope | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp expect actual &&\n+\techo HEAD:dir/link-dir/morx | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp found actual &&\n+\techo HEAD:dir/broken-link-in-dir missing > expect &&\n+\techo HEAD:dir/broken-link-in-dir | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git cat-file --batch-check --follow-symlinks works for ../.. links' '\n+\techo HEAD:dir/subdir/grandparent-dir-link/nope missing > expect &&\n+\techo HEAD:dir/subdir/grandparent-dir-link/nope | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp expect actual &&\n+\techo HEAD:dir/subdir/grandparent-dir-link | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp found actual &&\n+\techo HEAD:dir/subdir/parent-dir-link-to-link | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp found actual\n+'\n+\n+test_expect_success 'git cat-file --batch-check --follow-symlinks works for dir/ links' '\n+\techo HEAD:dirlink/morx missing > expect &&\n+\techo HEAD:dirlink/morx | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp expect actual &&\n+\techo $hello_sha1 blob $hello_size > expect &&\n+\techo HEAD:dirlink/ind1 | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git cat-file --batch-check --follow-symlinks works for dir/subdir links' '\n+\techo HEAD:subdirlink/morx missing > expect &&\n+\techo HEAD:subdirlink/morx | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp expect actual &&\n+\techo HEAD:subdirlink/ind2 | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp found actual\n+'\n+\n+test_expect_success 'git cat-file --batch-check --follow-symlinks works for dir -> subdir links' '\n+\techo HEAD:dir/link-to-child/morx missing > expect &&\n+\techo HEAD:dir/link-to-child/morx | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp expect actual &&\n+\techo HEAD:dir/link-to-child | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp found actual &&\n+\techo HEAD:link-to-down-link | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp found actual\n+'\n+\n+test_expect_success 'git cat-file --batch-check --follow-symlinks works for out-of-repo symlinks' '\n+\techo symlink 8 > expect &&\n+\techo ../fleem >> expect &&\n+\techo HEAD:out-of-repo-link | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp expect actual &&\n+\techo symlink 2 > expect &&\n+\techo .. >> expect &&\n+\techo HEAD:out-of-repo-link-dir | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git cat-file --batch-check --follow-symlinks works for out-of-repo symlinks in dirs' '\n+\techo symlink 9 > expect &&\n+\techo ../escape >> expect &&\n+\techo HEAD:dir/out-of-repo-link | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp expect actual &&\n+\techo symlink 2 > expect &&\n+\techo .. >> expect &&\n+\techo HEAD:dir/out-of-repo-link-dir | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git cat-file --batch-check --follow-symlinks works for out-of-repo symlinks in subdirs' '\n+\techo symlink 15 > expect &&\n+\techo ../great-escape >> expect &&\n+\techo HEAD:dir/subdir/out-of-repo-link | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp expect actual &&\n+\techo symlink 2 > expect &&\n+\techo .. >> expect &&\n+\techo HEAD:dir/subdir/out-of-repo-link-dir | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp expect actual &&\n+\techo symlink 3 > expect &&\n+\techo ../ >> expect &&\n+\techo HEAD:dir/subdir/out-of-repo-link-dir-trailing | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git cat-file --batch-check --follow-symlinks works for symlinks with internal ..' '\n+\techo HEAD: | git cat-file --batch-check > expect &&\n+\techo HEAD:up-down | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp expect actual &&\n+\techo HEAD:up-down-trailing | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp expect actual &&\n+\techo HEAD:up-down-file | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp found actual &&\n+\techo symlink 7 > expect &&\n+\techo ../morx >> expect &&\n+\techo HEAD:up-up-down-file | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp expect actual &&\n+\techo HEAD:up-two-down-file | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp found actual\n+'\n+\n+test_expect_success 'git cat-file --batch-check --follow-symlink breaks loops' '\n+\techo HEAD:loop1 missing > expect &&\n+\techo HEAD:loop1 | git cat-file --batch-check --follow-symlinks > actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git cat-file --batch --follow-symlink returns correct sha and mode' '\n+\techo HEAD:morx | git cat-file --batch > expect &&\n+\techo HEAD:morx | git cat-file --batch --follow-symlinks > actual &&\n+\ttest_cmp expect actual\n+'\n test_done\n-- \n2.0.4.315.gad8727a-twtrsrc\n"},{"id":"260920","messageId":"554F0378.2070803@kdbg.org","threadId":"39285","inReplyTo":"1431203769-11855-3-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v3 3/3] cat-file: add --follow-symlinks to --batch","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2015-05-10T07:06:32Z","receivedAt":"2015-05-10T07:06:32Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 09.05.2015 um 22:36 schrieb dturner@twopensource.com:\n> diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\n> index ab36b1e..dd80c08 100755\n> --- a/t/t1006-cat-file.sh\n> +++ b/t/t1006-cat-file.sh\n> @@ -189,6 +189,13 @@ do\n>       '\n>   done\n>\n> +for opt in t s e p\n> +do\n> +    test_expect_success \"Passing -$opt with --follow-symlinks fails\" '\n> +\t    test_must_fail git cat-file --follow-symlinks -$opt $hello_sha1\n> +\t'\n> +done\n> +\n>   test_expect_success \"--batch-check for a non-existent named object\" '\n>       test \"foobar42 missing\n>   foobar84 missing\" = \\\n> @@ -296,4 +303,187 @@ test_expect_success '%(deltabase) reports packed delta bases' '\n>   \t}\n>   '\n>\n> +# Tests for git cat-file --follow-symlinks\n> +test_expect_success 'prep for symlink tests' '\n> +\techo_without_newline \"$hello_content\" > morx &&\n> +\tln -s morx same-dir-link &&\n> +\tln -s ../fleem out-of-repo-link &&\n> +\tln -s .. out-of-repo-link-dir &&\n> +\tln -s same-dir-link link-to-link &&\n> +\tln -s nope broken-same-dir-link &&\n> +\tmkdir dir &&\n> +\tln -s ../morx dir/parent-dir-link &&\n> +\tln -s .. dir/link-dir &&\n> +\tln -s ../../escape dir/out-of-repo-link &&\n> +\tln -s ../.. dir/out-of-repo-link-dir &&\n> +\tln -s nope dir/broken-link-in-dir &&\n> +\tmkdir dir/subdir &&\n> +\tln -s ../../morx dir/subdir/grandparent-dir-link &&\n> +\tln -s ../../../great-escape dir/subdir/out-of-repo-link &&\n> +\tln -s ../../.. dir/subdir/out-of-repo-link-dir &&\n> +\tln -s ../../../ dir/subdir/out-of-repo-link-dir-trailing &&\n> +\tln -s ../parent-dir-link dir/subdir/parent-dir-link-to-link &&\n> +\techo_without_newline \"$hello_content\" >dir/subdir/ind2 &&\n> +\techo_without_newline \"$hello_content\" >dir/ind1 &&\n> +\tln -s dir dirlink &&\n> +\tln -s dir/subdir subdirlink &&\n> +\tln -s subdir/ind2 dir/link-to-child &&\n> +\tln -s dir/link-to-child link-to-down-link &&\n> +\tln -s dir/.. up-down &&\n> +\tln -s dir/../ up-down-trailing &&\n> +\tln -s dir/../morx up-down-file &&\n> +\tln -s dir/../../morx up-up-down-file &&\n> +\tln -s subdirlink/../../morx up-two-down-file &&\n> +\tln -s loop1 loop2 &&\n> +\tln -s loop2 loop1 &&\n> +\tgit add . &&\n> +\tgit commit -am \"test\"\n> +'\n\nThese tests will fail on a file system that does not support symbolic \nlinks, such as on Windows. Would you please separate the test cases \n(that come after the setup) into two groups:\n\n1. Those that inspect the filesystem and expect a symbolic link. Protect \nthese tests with a SYMLINKS prerequisite.\n\n2. Those that look only at repository or index contents and do not \nrequire a symbolic link on the file system. These do not need to be \nprotected.\n\nNote that you do not require a symlink enabled file system to generate a \nrepository with symlinks, i.e., you don't have to protect the setup code \nwith SYMLINKS. For this, you can use 'test_ln_s_add' instead of the \nabove sequence of 'ln -s' followed by a single 'git add .'.\n\n> +\n> +echo $hello_sha1 blob $hello_size > found\n\nThis seems to be used only in the next test. Please move it inside \ntest_expect_success.\n\n> +\n> +test_expect_success 'git cat-file --batch-check --follow-symlinks works for non-links' '\n> +\techo HEAD:morx | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp found actual &&\n> +\techo HEAD:nope missing > expect &&\n> +\techo HEAD:nope | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'git cat-file --batch-check --follow-symlinks works for in-repo, same-dir links' '\n> +\techo HEAD:same-dir-link | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp found actual\n> +'\n> +\n> +test_expect_success 'git cat-file --batch-check --follow-symlinks works for broken in-repo, same-dir links' '\n> +\techo HEAD:broken-same-dir-link missing > expect &&\n> +\techo HEAD:broken-same-dir-link | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'git cat-file --batch-check --follow-symlinks works for same-dir links-to-links' '\n> +\techo HEAD:link-to-link | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp found actual\n> +'\n> +\n> +test_expect_success 'git cat-file --batch-check --follow-symlinks works for parent-dir links' '\n> +\techo HEAD:dir/parent-dir-link | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp found actual &&\n> +\techo HEAD:dir/parent-dir-link/nope missing > expect &&\n> +\techo HEAD:dir/parent-dir-link/nope | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'git cat-file --batch-check --follow-symlinks works for .. links' '\n> +\techo HEAD:dir/link-dir/nope missing > expect &&\n> +\techo HEAD:dir/link-dir/nope | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp expect actual &&\n> +\techo HEAD:dir/link-dir/morx | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp found actual &&\n> +\techo HEAD:dir/broken-link-in-dir missing > expect &&\n> +\techo HEAD:dir/broken-link-in-dir | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'git cat-file --batch-check --follow-symlinks works for ../.. links' '\n> +\techo HEAD:dir/subdir/grandparent-dir-link/nope missing > expect &&\n> +\techo HEAD:dir/subdir/grandparent-dir-link/nope | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp expect actual &&\n> +\techo HEAD:dir/subdir/grandparent-dir-link | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp found actual &&\n> +\techo HEAD:dir/subdir/parent-dir-link-to-link | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp found actual\n> +'\n> +\n> +test_expect_success 'git cat-file --batch-check --follow-symlinks works for dir/ links' '\n> +\techo HEAD:dirlink/morx missing > expect &&\n> +\techo HEAD:dirlink/morx | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp expect actual &&\n> +\techo $hello_sha1 blob $hello_size > expect &&\n> +\techo HEAD:dirlink/ind1 | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'git cat-file --batch-check --follow-symlinks works for dir/subdir links' '\n> +\techo HEAD:subdirlink/morx missing > expect &&\n> +\techo HEAD:subdirlink/morx | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp expect actual &&\n> +\techo HEAD:subdirlink/ind2 | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp found actual\n> +'\n> +\n> +test_expect_success 'git cat-file --batch-check --follow-symlinks works for dir -> subdir links' '\n> +\techo HEAD:dir/link-to-child/morx missing > expect &&\n> +\techo HEAD:dir/link-to-child/morx | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp expect actual &&\n> +\techo HEAD:dir/link-to-child | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp found actual &&\n> +\techo HEAD:link-to-down-link | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp found actual\n> +'\n> +\n> +test_expect_success 'git cat-file --batch-check --follow-symlinks works for out-of-repo symlinks' '\n> +\techo symlink 8 > expect &&\n> +\techo ../fleem >> expect &&\n> +\techo HEAD:out-of-repo-link | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp expect actual &&\n> +\techo symlink 2 > expect &&\n> +\techo .. >> expect &&\n> +\techo HEAD:out-of-repo-link-dir | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'git cat-file --batch-check --follow-symlinks works for out-of-repo symlinks in dirs' '\n> +\techo symlink 9 > expect &&\n> +\techo ../escape >> expect &&\n> +\techo HEAD:dir/out-of-repo-link | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp expect actual &&\n> +\techo symlink 2 > expect &&\n> +\techo .. >> expect &&\n> +\techo HEAD:dir/out-of-repo-link-dir | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'git cat-file --batch-check --follow-symlinks works for out-of-repo symlinks in subdirs' '\n> +\techo symlink 15 > expect &&\n> +\techo ../great-escape >> expect &&\n> +\techo HEAD:dir/subdir/out-of-repo-link | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp expect actual &&\n> +\techo symlink 2 > expect &&\n> +\techo .. >> expect &&\n> +\techo HEAD:dir/subdir/out-of-repo-link-dir | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp expect actual &&\n> +\techo symlink 3 > expect &&\n> +\techo ../ >> expect &&\n> +\techo HEAD:dir/subdir/out-of-repo-link-dir-trailing | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'git cat-file --batch-check --follow-symlinks works for symlinks with internal ..' '\n> +\techo HEAD: | git cat-file --batch-check > expect &&\n> +\techo HEAD:up-down | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp expect actual &&\n> +\techo HEAD:up-down-trailing | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp expect actual &&\n> +\techo HEAD:up-down-file | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp found actual &&\n> +\techo symlink 7 > expect &&\n> +\techo ../morx >> expect &&\n> +\techo HEAD:up-up-down-file | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp expect actual &&\n> +\techo HEAD:up-two-down-file | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp found actual\n> +'\n> +\n> +test_expect_success 'git cat-file --batch-check --follow-symlink breaks loops' '\n> +\techo HEAD:loop1 missing > expect &&\n> +\techo HEAD:loop1 | git cat-file --batch-check --follow-symlinks > actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'git cat-file --batch --follow-symlink returns correct sha and mode' '\n> +\techo HEAD:morx | git cat-file --batch > expect &&\n> +\techo HEAD:morx | git cat-file --batch --follow-symlinks > actual &&\n> +\ttest_cmp expect actual\n> +'\n>   test_done\n>\n\n-- Hannes\n"},{"id":"260928","messageId":"xmqqd228gwha.fsf@gitster.dls.corp.google.com","threadId":"39285","inReplyTo":"1431203769-11855-1-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v3 1/3] tree-walk: learn get_tree_entry_follow_symlinks","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-05-10T17:42:41Z","receivedAt":"2015-05-10T17:42:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"dturner@twopensource.com writes:\n\n> From: David Turner <dturner@twitter.com>\n>\n> Add a new function, get_tree_entry_follow_symlinks, to tree-walk.[ch].\n> The function is not yet used.  It will be used to implement git\n> cat-file --batch --follow-symlinks.\n>\n> The function locates an object by path, following symlinks in the\n> repository.  If the symlinks lead outside the repository, the function\n> reports this to the caller.\n>\n> Signed-off-by: David Turner <dturner@twitter.com>\n> ---\n\nThanks.\n\n>  cache.h     |   5 ++\n>  tree-walk.c | 195 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n>  tree-walk.h |   2 +\n>  3 files changed, 202 insertions(+)\n>\n> diff --git a/cache.h b/cache.h\n> index 3d3244b..679faa9 100644\n> --- a/cache.h\n> +++ b/cache.h\n> @@ -922,6 +922,11 @@ struct object_context {\n>  \tunsigned char tree[20];\n>  \tchar path[PATH_MAX];\n>  \tunsigned mode;\n> +\t/*\n> +\t * symlink_path is only used by get_tree_entry_follow_symlinks,\n> +\t * and only for symlinks that point outside the repository.\n> +\t */\n> +\tstruct strbuf symlink_path;\n>  };\n\nThe new low-level helper introduced here does not fill this field,\nit seems, and it would make it clear to move the above hunk to\n[2/3] where it becomes necessary.\n\nI haven't checked carefully enough to say there isn't any missing\ncorner cases in the follow-symlinks codepath with confidence, but\nthe logic there seems very sound.  Other than the above suggestion\nand removal of \"int already_have_tree\" variable that is not used, I\nhave only minor style nits (attached to be squashable) [*1*] on this\npatch.\n\nThanks.\n\n[Footnote]\n\n *1* Style nits:\n\n  a. Multi-line comment format.\n\n  b. ALLOC_GROW() followed by filling parents_nr slot is concluded\n     by incrementing parents_nr itself; that single logical unit is\n     easier to see without a blank line in the middle.\n\n  c. In C, we prefer post-increment i++ over pre-increment when its\n     value is discarded.\n\n  d. We tend to omit {} around a single-statement block.\n\n\n tree-walk.c | 21 ++++++++++-----------\n 1 file changed, 10 insertions(+), 11 deletions(-)\n\ndiff --git a/tree-walk.c b/tree-walk.c\nindex f93492d..b7ee665 100644\n--- a/tree-walk.c\n+++ b/tree-walk.c\n@@ -484,7 +484,8 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch\n \treturn retval;\n }\n \n-/* This is Linux's built-in max for the number of symlinks to follow.\n+/*\n+ * This is Linux's built-in max for the number of symlinks to follow.\n  * That limit, of course, does not affect git, but it's a reasonable\n  * choice.\n  */\n@@ -514,7 +515,6 @@ int get_tree_entry_follow_symlinks(unsigned char *tree_sha1, const char *name, u\n \tunsigned char current_tree_sha1[20];\n \tstruct strbuf namebuf = STRBUF_INIT;\n \tenum object_type type;\n-\tint already_have_tree = 0;\n \tstruct tree_desc t = {0};\n \tint follows_remaining = GET_TREE_ENTRY_FOLLOW_SYMLINKS_MAX_LINKS;\n \tint i;\n@@ -541,7 +541,6 @@ int get_tree_entry_follow_symlinks(unsigned char *tree_sha1, const char *name, u\n \t\t\tparents[parents_nr].tree = tree;\n \t\t\tparents[parents_nr].size = size;\n \t\t\thashcpy(parents[parents_nr].sha1, root);\n-\n \t\t\tparents_nr++;\n \n \t\t\tif (namebuf.buf[0] == '\\0') {\n@@ -562,8 +561,7 @@ int get_tree_entry_follow_symlinks(unsigned char *tree_sha1, const char *name, u\n \t\t\tstrbuf_remove(&namebuf, 0, 1);\n \t\t}\n \n-\t\t/* Split namebuf into a first component and a\n-\t\t * remainder */\n+\t\t/* Split namebuf into a first component and a remainder */\n \t\tif ((first_slash = strchr(namebuf.buf, '/'))) {\n \t\t\t*first_slash = 0;\n \t\t\tremainder = first_slash + 1;\n@@ -571,8 +569,10 @@ int get_tree_entry_follow_symlinks(unsigned char *tree_sha1, const char *name, u\n \n \t\tif (!strcmp(namebuf.buf, \"..\")) {\n \t\t\tstruct dir_state *parent;\n-\t\t\t/* We could end up with .. in the namebuf if\n-\t\t\t * it appears in a symlink. */\n+\t\t\t/*\n+\t\t\t * We could end up with .. in the namebuf if it\n+\t\t\t * appears in a symlink.\n+\t\t\t */\n \n \t\t\tif (parents_nr == 1) {\n \t\t\t\tif (remainder)\n@@ -599,8 +599,7 @@ int get_tree_entry_follow_symlinks(unsigned char *tree_sha1, const char *name, u\n \t\t\tgoto done;\n \t\t}\n \n-\t\t/* Look up the first (or only) path component\n-\t\t * in the tree. */\n+\t\t/* Look up the first (or only) path component in the tree. */\n \t\tfind_result = find_tree_entry(&t, namebuf.buf,\n \t\t\t\t\t      current_tree_sha1, mode);\n \t\tif (find_result) {\n@@ -664,9 +663,8 @@ int get_tree_entry_follow_symlinks(unsigned char *tree_sha1, const char *name, u\n \t\t}\n \t}\n done:\n-\tfor (i = 0; i < parents_nr; ++i) {\n+\tfor (i = 0; i < parents_nr; i++)\n \t\tfree(parents[i].tree);\n-\t}\n \tfree(parents);\n \n \tstrbuf_release(&namebuf);\n"},{"id":"260929","messageId":"xmqq8ucwgw8o.fsf@gitster.dls.corp.google.com","threadId":"39285","inReplyTo":"1431203769-11855-2-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v3 2/3] sha1_name: get_sha1_with_context learns to follow symlinks","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-05-10T17:47:51Z","receivedAt":"2015-05-10T17:47:51Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"dturner@twopensource.com writes:\n\n> -#define GET_SHA1_QUIETLY        01\n> -#define GET_SHA1_COMMIT         02\n> -#define GET_SHA1_COMMITTISH     04\n> -#define GET_SHA1_TREE          010\n> -#define GET_SHA1_TREEISH       020\n> -#define GET_SHA1_BLOB\t       040\n> -#define GET_SHA1_ONLY_TO_DIE 04000\n> +#define GET_SHA1_QUIETLY           01\n> +#define GET_SHA1_COMMIT            02\n> +#define GET_SHA1_COMMITTISH        04\n> +#define GET_SHA1_TREE             010\n> +#define GET_SHA1_TREEISH          020\n> +#define GET_SHA1_BLOB             040\n> +#define GET_SHA1_FOLLOW_SYMLINKS 0100\n> +#define GET_SHA1_ONLY_TO_DIE    04000\n\nThere is nothing wrong per-se, but this effort for aligning looks\namusing ;-) Perhaps the next person who wants to mimick this pattern\nwill order their constants in such a way that gives a shoter code to\na symbol with a longer name?\n\n> diff --git a/sha1_name.c b/sha1_name.c\n> index 6d10f05..325f666 100644\n> --- a/sha1_name.c\n> +++ b/sha1_name.c\n> @@ -1434,15 +1434,22 @@ static int get_sha1_with_context_1(const char *name,\n>  \t\t\tnew_filename = resolve_relative_path(filename);\n>  \t\t\tif (new_filename)\n>  \t\t\t\tfilename = new_filename;\n> -\t\t\tret = get_tree_entry(tree_sha1, filename, sha1, &oc->mode);\n> -\t\t\tif (ret && only_to_die) {\n> -\t\t\t\tdiagnose_invalid_sha1_path(prefix, filename,\n> -\t\t\t\t\t\t\t   tree_sha1,\n> -\t\t\t\t\t\t\t   name, len);\n> +\t\t\tif (flags & GET_SHA1_FOLLOW_SYMLINKS) {\n> +\t\t\t\tret = get_tree_entry_follow_symlinks(tree_sha1,\n> +\t\t\t\t\tfilename, sha1, &oc->symlink_path,\n> +\t\t\t\t\t&oc->mode);\n> +\t\t\t} else {\n> +\t\t\t\tret = get_tree_entry(tree_sha1, filename,\n> +\t\t\t\t\t\t     sha1, &oc->mode);\n> +\t\t\t\tif (ret && only_to_die) {\n> +\t\t\t\t\tdiagnose_invalid_sha1_path(prefix,\n> +\t\t\t\t\t\t\t\t   filename,\n> +\t\t\t\t\t\t\t\t   tree_sha1,\n> +\t\t\t\t\t\t\t\t   name, len);\n> +\t\t\t\t}\n>  \t\t\t}\n\n> -\t\t\thashcpy(oc->tree, tree_sha1);\n>  \t\t\tstrlcpy(oc->path, filename, sizeof(oc->path));\n> -\n> +\t\t\thashcpy(oc->tree, tree_sha1);\n\nDid the order and additional blank line matter here?  I think you'd\nprefer to keep these two lines as before...\n\n>  \t\t\tfree(new_filename);\n>  \t\t\treturn ret;\n>  \t\t} else {\n> @@ -1469,5 +1476,7 @@ void maybe_die_on_misspelt_object_name(const char *name, const char *prefix)\n>  \n>  int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *orc)\n>  {\n> +\tif (flags & GET_SHA1_FOLLOW_SYMLINKS && flags & GET_SHA1_ONLY_TO_DIE)\n> +\t\tdie(\"BUG: incompatible flags for get_sha1_with_context\");\n>  \treturn get_sha1_with_context_1(str, flags, NULL, sha1, orc);\n>  }\n\nOther than that, the patch looks good.\n\nThanks.\n"},{"id":"260993","messageId":"1431363819.15048.2.camel@ubuntu","threadId":"39285","inReplyTo":"554F0378.2070803@kdbg.org","subject":"Re: [PATCH v3 3/3] cat-file: add --follow-symlinks to --batch","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-05-11T17:03:39Z","receivedAt":"2015-05-11T17:03:39Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"On Sun, 2015-05-10 at 09:06 +0200, Johannes Sixt wrote:\n> > +\tln -s loop2 loop1 &&\n> > +\tgit add . &&\n> > +\tgit commit -am \"test\"\n> > +'\n> \n> These tests will fail on a file system that does not support symbolic \n> links, such as on Windows. Would you please separate the test cases \n> (that come after the setup) into two groups:\n> \n> 1. Those that inspect the filesystem and expect a symbolic link. Protect \n> these tests with a SYMLINKS prerequisite.\n\nI believe that none of these require that.\n\n> Note that you do not require a symlink enabled file system to generate a \n> repository with symlinks, i.e., you don't have to protect the setup code \n> with SYMLINKS. For this, you can use 'test_ln_s_add' instead of the \n> above sequence of 'ln -s' followed by a single 'git add .'.\n\nWill fix, thanks.\n\n> > +\n> > +echo $hello_sha1 blob $hello_size > found\n> \n> This seems to be used only in the next test. Please move it inside \n> test_expect_success.\n\nThis is used in a number of tests, e.g this one:\n\n> > +test_expect_success 'git cat-file --batch-check --follow-symlinks works for in-repo, same-dir links' '\n> > +\techo HEAD:same-dir-link | git cat-file --batch-check --follow-symlinks > actual &&\n> > +\ttest_cmp found actual\n> > +'\n"},{"id":"260997","messageId":"xmqqzj5bdn4w.fsf@gitster.dls.corp.google.com","threadId":"39285","inReplyTo":"1431363819.15048.2.camel@ubuntu","subject":"Re: [PATCH v3 3/3] cat-file: add --follow-symlinks to --batch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-05-11T17:45:03Z","receivedAt":"2015-05-11T17:45:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"David Turner <dturner@twopensource.com> writes:\n\n> On Sun, 2015-05-10 at 09:06 +0200, Johannes Sixt wrote:\n>> > +\tln -s loop2 loop1 &&\n>> > +\tgit add . &&\n>> > +\tgit commit -am \"test\"\n>> > +'\n>> \n>> These tests will fail on a file system that does not support symbolic \n>> links, such as on Windows. Would you please separate the test cases \n>> (that come after the setup) into two groups:\n>> \n>> 1. Those that inspect the filesystem and expect a symbolic link. Protect \n>> these tests with a SYMLINKS prerequisite.\n>\n> I believe that none of these require that.\n>\n>> Note that you do not require a symlink enabled file system to generate a \n>> repository with symlinks, i.e., you don't have to protect the setup code \n>> with SYMLINKS. For this, you can use 'test_ln_s_add' instead of the \n>> above sequence of 'ln -s' followed by a single 'git add .'.\n>\n> Will fix, thanks.\n\nYeah, thanks. There doesn't seem to be any reason to forbid this new\nfeature from being used on symlink-challenged filesystem (after all,\n\"cat-file --batch\" should run fine in a bare repository) and the\nsuggestion makes sens to me.\n\n>> > +\n>> > +echo $hello_sha1 blob $hello_size > found\n>> \n>> This seems to be used only in the next test. Please move it inside \n>> test_expect_success.\n>\n> This is used in a number of tests, e.g this one:\n\nPerhaps then the preparetion of 'found' is part of this thing:\n\n    test_expect_success 'prep for symlink tests' '\n\nA micronit on style; please drop SP between redirection and its\ntarget (but have one before redirection), i.e.\n\n\techo ... >found\n\tcommand <input\n\nThanks.\n\n>\n>> > +test_expect_success 'git cat-file --batch-check --follow-symlinks\n>> > works for in-repo, same-dir links' '\n>> > +\techo HEAD:same-dir-link | git cat-file --batch-check --follow-symlinks > actual &&\n>> > +\ttest_cmp found actual\n>> > +'\n"}]}