{"thread":{"id":"39296","subject":"(unknown)","startedAt":"2015-05-11T17:56:26Z","lastAt":"2015-05-11T22:39:46Z","messageCount":8,"participants":["dturner@twopensource.com","Junio C Hamano","David Turner"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"261000","messageId":"1431366989-7405-1-git-send-email-dturner@twopensource.com","threadId":"39296","inReplyTo":null,"subject":"(unknown)","fromName":"","fromEmail":"dturner@twopensource.com","sentAt":"2015-05-11T17:56:26Z","receivedAt":"2015-05-11T17:56:26Z","isPatch":false,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"This version includes style fixups suggested by Junio Hamano.  As\nsuggested, I moved the addition of the new object_context field to the\npatch where it is used.\n\nIn addition, I moved a few variables into smaller scopes.\n\nI also (hopefully) fixed the tests on Windows thanks to some\nsuggestions from Johannes Sixt\n"},{"id":"261003","messageId":"1431366989-7405-2-git-send-email-dturner@twopensource.com","threadId":"39296","inReplyTo":"1431366989-7405-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v4 1/3] tree-walk: learn get_tree_entry_follow_symlinks","fromName":"","fromEmail":"dturner@twopensource.com","sentAt":"2015-05-11T17:56:27Z","receivedAt":"2015-05-11T17:56:27Z","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 tree-walk.c | 193 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n tree-walk.h |   2 +\n 2 files changed, 195 insertions(+)\n\ndiff --git a/tree-walk.c b/tree-walk.c\nindex 5dd9a71..3d088b6 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,193 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch\n \treturn retval;\n }\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+\tstruct dir_state *parents = NULL;\n+\tsize_t parents_alloc = 0;\n+\tssize_t parents_nr = 0;\n+\tunsigned char current_tree_sha1[20];\n+\tstruct strbuf namebuf = STRBUF_INIT;\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\tint find_result;\n+\t\tchar *first_slash;\n+\t\tchar *remainder = NULL;\n+\n+\t\tif (!t.buffer) {\n+\t\t\tvoid *tree;\n+\t\t\tunsigned char root[20];\n+\t\t\tunsigned long size;\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+\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 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/*\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+\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 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+\t\t\tenum object_type type;\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+\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":"261001","messageId":"1431366989-7405-3-git-send-email-dturner@twopensource.com","threadId":"39296","inReplyTo":"1431366989-7405-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v4 2/3] sha1_name: get_sha1_with_context learns to follow symlinks","fromName":"","fromEmail":"dturner@twopensource.com","sentAt":"2015-05-11T17:56:28Z","receivedAt":"2015-05-11T17:56:28Z","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     | 20 +++++++++++++-------\n sha1_name.c | 20 +++++++++++++++-----\n 2 files changed, 28 insertions(+), 12 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex 3d3244b..65505d1 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -922,15 +922,21 @@ 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\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..0c26515 100644\n--- a/sha1_name.c\n+++ b/sha1_name.c\n@@ -1434,11 +1434,19 @@ 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@@ -1469,5 +1477,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":"261002","messageId":"1431366989-7405-4-git-send-email-dturner@twopensource.com","threadId":"39296","inReplyTo":"1431366989-7405-1-git-send-email-dturner@twopensource.com","subject":"[PATCH v4 3/3] cat-file: add --follow-symlinks to --batch","fromName":"","fromEmail":"dturner@twopensource.com","sentAt":"2015-05-11T17:56:29Z","receivedAt":"2015-05-11T17:56:29Z","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            | 189 +++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 236 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..500cfa9 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,186 @@ 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+\ttest_ln_s_add morx same-dir-link &&\n+\ttest_ln_s_add ../fleem out-of-repo-link &&\n+\ttest_ln_s_add .. out-of-repo-link-dir &&\n+\ttest_ln_s_add same-dir-link link-to-link &&\n+\ttest_ln_s_add nope broken-same-dir-link &&\n+\tmkdir dir &&\n+\ttest_ln_s_add ../morx dir/parent-dir-link &&\n+\ttest_ln_s_add .. dir/link-dir &&\n+\ttest_ln_s_add ../../escape dir/out-of-repo-link &&\n+\ttest_ln_s_add ../.. dir/out-of-repo-link-dir &&\n+\ttest_ln_s_add nope dir/broken-link-in-dir &&\n+\tmkdir dir/subdir &&\n+\ttest_ln_s_add ../../morx dir/subdir/grandparent-dir-link &&\n+\ttest_ln_s_add ../../../great-escape dir/subdir/out-of-repo-link &&\n+\ttest_ln_s_add ../../.. dir/subdir/out-of-repo-link-dir &&\n+\ttest_ln_s_add ../../../ dir/subdir/out-of-repo-link-dir-trailing &&\n+\ttest_ln_s_add ../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+\ttest_ln_s_add dir dirlink &&\n+\ttest_ln_s_add dir/subdir subdirlink &&\n+\ttest_ln_s_add subdir/ind2 dir/link-to-child &&\n+\ttest_ln_s_add dir/link-to-child link-to-down-link &&\n+\ttest_ln_s_add dir/.. up-down &&\n+\ttest_ln_s_add dir/../ up-down-trailing &&\n+\ttest_ln_s_add dir/../morx up-down-file &&\n+\ttest_ln_s_add dir/../../morx up-up-down-file &&\n+\ttest_ln_s_add subdirlink/../../morx up-two-down-file &&\n+\ttest_ln_s_add loop1 loop2 &&\n+\ttest_ln_s_add loop2 loop1 &&\n+\tgit add morx dir/subdir/ind2 dir/ind1 &&\n+\tgit commit -am \"test\"\n+\techo $hello_sha1 blob $hello_size >found\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-- \n2.0.4.315.gad8727a-twtrsrc\n"},{"id":"261014","messageId":"xmqqh9ri3nps.fsf@gitster.dls.corp.google.com","threadId":"39296","inReplyTo":"1431366989-7405-3-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v4 2/3] sha1_name: get_sha1_with_context learns to follow symlinks","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-05-11T19:42:39Z","receivedAt":"2015-05-11T19:42:39Z","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> Wire up get_sha1_with_context to call get_tree_entry_follow_symlinks\n> when GET_SHA1_FOLLOW_SYMLINKS is passed in flags. G_S_FOLLOW_SYMLINKS\n> is incompatible with G_S_ONLY_TO_DIE because the diagnosis\n> that ONLY_TO_DIE triggers does not at present consider symlinks, and\n> it would be a significant amount of additional code to allow it to\n> do so.\n>\n> Signed-off-by: David Turner <dturner@twitter.com>\n> ---\n>  cache.h     | 20 +++++++++++++-------\n>  sha1_name.c | 20 +++++++++++++++-----\n>  2 files changed, 28 insertions(+), 12 deletions(-)\n>\n> diff --git a/cache.h b/cache.h\n> index 3d3244b..65505d1 100644\n> --- a/cache.h\n> +++ b/cache.h\n> @@ -922,15 +922,21 @@ 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> @@ -1469,5 +1477,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\nLooks good; thanks.\n"},{"id":"261017","messageId":"xmqqd2263lxf.fsf@gitster.dls.corp.google.com","threadId":"39296","inReplyTo":"1431366989-7405-4-git-send-email-dturner@twopensource.com","subject":"Re: [PATCH v4 3/3] cat-file: add --follow-symlinks to --batch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-05-11T20:21:16Z","receivedAt":"2015-05-11T20:21:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"dturner@twopensource.com writes:\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\nOne thing that I found is missing from the above and other places in\nthe documents updated by this patch, when I pretend that I am an\nend-user who never saw the discussion for the problem definition and\ndesign of the solution on the list, is what \"symlink\" this refers\nto and how I am expected to use this feature.\n\nSuch an end-user in me without inside knowledge naively expects that\nthe command invocation would be like this:\n\n\t$ git cat-file --batch-check --follow-symlinks <<\\EOF\n\te156455ea49124c140a67623f22a393db62d5d98\n\t55aeb4d32091e0e6062759958c30d316f77a570e\n        e50885caa1043665dfe2d995e0a2f7a7a7bb51ca\n\tEOF\n\nwhere the last one is output from \"git rev-parse v2.0.0:RelNotes\"\n(the second one is v2.0.0^{tree}).\n\nAs somebody who is familiar with how the implementation works, I\n_know_ that such an expectation is wrong.  At least the command\nwould need a hint that lets it realize that the blob sits inside a\ntree with mode bits that says it is a symlink, e.g.\n\n\t$ git cat-file --batch-check --follow-symlinks <<\\EOF\n\t55aeb4d32091e0e6062759958c30d316f77a570e:RelNotes\n\tEOF\n\nBut that requirement is not hinted in the documentation, so there is\nno way for a new user to discover how useful this feature is and how\nto use it.\n\nThe above description mentions \"points outside the repository\", but\nthat also may need some clarification to such an end-user in me\nwithout inside knowledge.\n\nFor example, if I had a tree T that records a subtree T:sub, in\nwhich a symbolic link sub/link that points at \"../target\", i.e. with\nthis setup (8fb753 is T):\n\n    $ git rev-parse 8fb7535cc62fd9a0a804f21389667f9505299fb4:sub\n    13056eb35d037f4af8be37e6a1ba983235bd7613\n    $ git ls-tree 13056eb35d037f4af8be37e6a1ba983235bd7613 link\n    120000 blob 78bc33729bacb569b76aaef40fec807af2ec1274    link\n    $ git cat-file blob 78bc33729ba\n    ../target\n    $ git ls-tree 8fb7535cc62 target\n    100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391    target\n\nThese behave differently\n\n    $ git cat-file --batch-check --follow-symlinks <<\\EOF\n    8fb7535cc62f:sub/link\n    13056eb35d03:link\n    EOF\n\nand that is correct for obvious reasons, but is the reason obvious\nenough to an end-user in me without inside knowledge?  The symlink\npoints inside the repository, but it is outside the starting object\nof the extended SHA-1 expression that was used to name the object.\n\nSo in short, the description above should mention that this feature\nis about asking for the object with extended SHA-1 expression that\nbegins with a tree-ish, colon and a path to in the tree.  And it\nneeds to rephrase \"outside the repository\" with something like\n\"outside the tree-ish on the left hand side of the colon in the\nextended SHA-1 expression\" or something.\n\nOthers better at phrasing may be able to offer a better wording than\nmy clumsy attempt above, though ;-)\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\nA symlink contain LF, obviously, and we should use some quoting\nconvention.  Perhaps quote_c_style() on a string that needs it is\nsufficient---most sane people do not put LF or literally '\\' 'n' or\n'\"' in their symbolic links, so the ugly output is not visible for\nthem, and it is far better to be able to say \"If you have funny\ncharacters in your symlinks, they will be quoted\" than \"If you have\nfunny characters in your symlinks, you are SOL\".\n\nThanks.\n"},{"id":"261021","messageId":"1431383471.15048.9.camel@ubuntu","threadId":"39296","inReplyTo":"xmqqd2263lxf.fsf@gitster.dls.corp.google.com","subject":"Re: [PATCH v4 3/3] cat-file: add --follow-symlinks to --batch","fromName":"David Turner","fromEmail":"dturner@twopensource.com","sentAt":"2015-05-11T22:31:11Z","receivedAt":"2015-05-11T22:31:11Z","isPatch":true,"sender":{"key":"novalis@novalis.org","avatar":"https://avatars.githubusercontent.com/u/77003?v=4"},"body":"On Mon, 2015-05-11 at 13:21 -0700, Junio C Hamano wrote:\ndturner@twopensource.com writes:\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> One thing that I found is missing from the above and other places in\n> the documents updated by this patch, when I pretend that I am an\n> end-user who never saw the discussion for the problem definition and\n> design of the solution on the list, is what \"symlink\" this refers\n> to and how I am expected to use this feature.\n> \nI'll send something like this when I re-roll:\n\nFollow symlinks inside the repository when requesting objects with\nextended SHA-1 expressions of the form tree-ish:path-in-tree. Instead of\nproviding output about the link itself, provide output about the\nlinked-to object.  This option requires --batch or\t--batch-check.  In\nthe event of a symlink loop (or more than 40 symlinks in a symlink\nresolution chain), the file will be treated as missing.  If a symlink\npoints outside the tree-ish (e.g. a link to /foo or a root-level link\nto ../foo), the portion of the link which is outside the tree will be\nprinted. Follow-symlinks will be silently turned off if <object>\nspecifies an object in the index rather than one in the object database.\n\nI could also provide some examples, if you think this would be useful.\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\nformat\n> > +and print:\n> > +\n> > +------------\n> > +symlink SP <size> LF <symlink> LF\n> > +------------\n> \n> A symlink contain LF, obviously, and we should use some quoting\n> convention.  Perhaps quote_c_style() on a string that needs it is\n> sufficient---most sane people do not put LF or literally '\\' 'n' or\n> '\"' in their symbolic links, so the ugly output is not visible for\n> them, and it is far better to be able to say \"If you have funny\n> characters in your symlinks, they will be quoted\" than \"If you have\n> funny characters in your symlinks, you are SOL\".\n> \nThere is no need for quoting because <size> is sufficient to\ndisambiguate the parsing (just as is the case for blobs, which might\ncontain arbitrary characters).\n"},{"id":"261022","messageId":"xmqqtwvi20y5.fsf@gitster.dls.corp.google.com","threadId":"39296","inReplyTo":"1431383471.15048.9.camel@ubuntu","subject":"Re: [PATCH v4 3/3] cat-file: add --follow-symlinks to --batch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-05-11T22:39:46Z","receivedAt":"2015-05-11T22:39:46Z","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> I'll send something like this when I re-roll:\n>\n> Follow symlinks inside the repository when requesting objects with\n> extended SHA-1 expressions of the form tree-ish:path-in-tree. Instead of\n> providing output about the link itself, provide output about the\n> linked-to object.  This option requires --batch or\t--batch-check.  In\n> the event of a symlink loop (or more than 40 symlinks in a symlink\n> resolution chain), the file will be treated as missing.  If a symlink\n> points outside the tree-ish (e.g. a link to /foo or a root-level link\n> to ../foo), the portion of the link which is outside the tree will be\n> printed. Follow-symlinks will be silently turned off if <object>\n> specifies an object in the index rather than one in the object database.\n\nVery understandable. I like it.\n\n>\n> I could also provide some examples, if you think this would be useful.\n\nThat may not be a bad idea.\n\n>> > +symlink SP <size> LF <symlink> LF\n>> > +------------\n>> \n>> A symlink contain LF, obviously, and we should use some quoting\n>> convention.  Perhaps quote_c_style() on a string that needs it is\n>> sufficient---most sane people do not put LF or literally '\\' 'n' or\n>> '\"' in their symbolic links, so the ugly output is not visible for\n>> them, and it is far better to be able to say \"If you have funny\n>> characters in your symlinks, they will be quoted\" than \"If you have\n>> funny characters in your symlinks, you are SOL\".\n>> \n> There is no need for quoting because <size> is sufficient to\n> disambiguate the parsing (just as is the case for blobs, which might\n> contain arbitrary characters).\n\nOK.  That is a very sensible way to think about it.\n\nI briefly wondered if trailing LF is a healthy thing, but blob\noutput also gives an extra LF after <contents> (i.e. the reader of\n\"<name> SP blob SP <size> LF <contents> LF\" must skip (size + 1)\nbytes to get to the next record), so it is consistent.\n\nThanks.\n"}]}