{"thread":{"id":"13232","subject":"[PATCH 01/11] Add tests for git cat-file","startedAt":"2008-04-23T19:17:42Z","lastAt":"2008-05-06T06:41:40Z","messageCount":19,"participants":["Adam Roben","Eric Wong","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":11},"messages":[{"id":"75085","messageId":"1208978273-98146-1-git-send-email-aroben@apple.com","threadId":"13232","inReplyTo":null,"subject":"Speed up git-svn fetch","fromName":"Adam Roben","fromEmail":"aroben@apple.com","sentAt":"2008-04-23T19:17:42Z","receivedAt":"2008-04-23T19:17:42Z","isPatch":false,"sender":{"key":"aroben@apple.com","avatar":"https://gravatar.com/avatar/9d3697e1de53890adf241331f4b970bdd2b18962b2ff0b8028ebb00e085807f8?d=mp&s=160"},"body":"\nI first sent this patch series 6 months ago today [1], then resent it after\nsome comments from Junio, Johannes, and Brian [2]. Here it is again, addressing\nfurther comments from Junio and Eric.\n\nThe point of the series is to speed up git-svn fetch on Cygwin, where forking\nis quite slow. In my informal testing, this patch seems to speed things up by\n~1.4-~1.7x. We accomplish this by having a single long-lived git-cat-file\nprocess and a single long-lived git-hash-object process for the duration of the\ngit-svn invocation.\n\nThis series is based on top of next.\n\n-Adam\n\n--\n Documentation/git-cat-file.txt    |   43 +++++++-\n Documentation/git-hash-object.txt |    5 +-\n builtin-cat-file.c                |  153 ++++++++++++++++++++++++---\n git-svn.perl                      |   42 ++++----\n hash-object.c                     |   44 ++++++++-\n perl/Git.pm                       |  208 ++++++++++++++++++++++++++++++++++++-\n t/t1006-cat-file.sh               |  181 ++++++++++++++++++++++++++++++++\n t/t1007-hash-object.sh            |  139 +++++++++++++++++++++++++\n t/t5303-hash-object.sh            |   35 ------\n 9 files changed, 768 insertions(+), 82 deletions(-)\n"},{"id":"75082","messageId":"1208978273-98146-2-git-send-email-aroben@apple.com","threadId":"13232","inReplyTo":"1208978273-98146-1-git-send-email-aroben@apple.com","subject":"[PATCH 01/11] Add tests for git cat-file","fromName":"Adam Roben","fromEmail":"aroben@apple.com","sentAt":"2008-04-23T19:17:43Z","receivedAt":"2008-04-23T19:17:43Z","isPatch":true,"sender":{"key":"aroben@apple.com","avatar":"https://gravatar.com/avatar/9d3697e1de53890adf241331f4b970bdd2b18962b2ff0b8028ebb00e085807f8?d=mp&s=160"},"body":"\nSigned-off-by: Adam Roben <aroben@apple.com>\n---\n t/t1006-cat-file.sh |  101 +++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 101 insertions(+), 0 deletions(-)\n create mode 100755 t/t1006-cat-file.sh\n\ndiff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\nnew file mode 100755\nindex 0000000..15741d9\n--- /dev/null\n+++ b/t/t1006-cat-file.sh\n@@ -0,0 +1,101 @@\n+#!/bin/sh\n+\n+test_description='git cat-file'\n+\n+. ./test-lib.sh\n+\n+function echo_without_newline()\n+{\n+    echo \"$@\\c\"\n+}\n+\n+function strlen()\n+{\n+    echo_without_newline \"$1\" | wc -c | sed -e 's/^ *//'\n+}\n+\n+function maybe_remove_timestamp()\n+{\n+    if test -z \"$2\"; then\n+        echo_without_newline \"$1\"\n+    else\n+        echo_without_newline \"$1\" | sed -e 's/ [0-9]\\{10\\} [+-][0-9]\\{4\\}$//'\n+    fi\n+}\n+\n+function run_tests()\n+{\n+    type=$1\n+    sha1=$2\n+    size=$3\n+    content=$4\n+    pretty_content=$5\n+    no_timestamp=$6\n+\n+    test_expect_success \\\n+        \"$type exists\" \\\n+        \"git cat-file -e $sha1\"\n+    test_expect_success \\\n+        \"Type of $type is correct\" \\\n+        \"test $type = \\\"\\$(git cat-file -t $sha1)\\\"\"\n+    test_expect_success \\\n+        \"Size of $type is correct\" \\\n+        \"test $size = \\\"\\$(git cat-file -s $sha1)\\\"\"\n+    test -z \"$content\" || test_expect_success \\\n+        \"Content of $type is correct\" \\\n+        \"test \\\"\\$(maybe_remove_timestamp '$content' $no_timestamp)\\\" = \\\"\\$(maybe_remove_timestamp \\\"\\$(git cat-file $type $sha1)\\\" $no_timestamp)\\\"\"\n+    test_expect_success \\\n+        \"Pretty content of $type is correct\" \\\n+        \"test \\\"\\$(maybe_remove_timestamp '$pretty_content' $no_timestamp)\\\" = \\\"\\$(maybe_remove_timestamp \\\"\\$(git cat-file -p $sha1)\\\" $no_timestamp)\\\"\"\n+}\n+\n+hello_content=\"Hello World\"\n+hello_size=$(strlen \"$hello_content\")\n+hello_sha1=$(echo_without_newline \"$hello_content\" | git hash-object --stdin)\n+\n+test_expect_success \\\n+    \"setup\" \\\n+    \"echo_without_newline \\\"$hello_content\\\" > hello &&\n+     git update-index --add hello\"\n+\n+run_tests 'blob' $hello_sha1 $hello_size \"$hello_content\" \"$hello_content\"\n+\n+tree_sha1=$(git write-tree)\n+tree_size=33\n+tree_pretty_content=\"100644 blob $hello_sha1\thello\"\n+\n+run_tests 'tree' $tree_sha1 $tree_size \"\" \"$tree_pretty_content\"\n+\n+commit_message=\"Intial commit\"\n+commit_sha1=$(echo_without_newline \"$commit_message\" | git commit-tree $tree_sha1)\n+commit_size=176\n+commit_content=\"tree $tree_sha1\n+author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> 0000000000 +0000\n+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 0000000000 +0000\n+\n+$commit_message\"\n+\n+run_tests 'commit' $commit_sha1 $commit_size \"$commit_content\" \"$commit_content\" 1\n+\n+tag_header_without_timestamp=\"object $hello_sha1\n+type blob\n+tag hellotag\n+tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\"\n+tag_description=\"This is a tag\"\n+tag_content=\"$tag_header_without_timestamp 0000000000 +0000\n+\n+$tag_description\"\n+tag_pretty_content=\"$tag_header_without_timestamp Thu Jan 1 00:00:00 1970 +0000\n+\n+$tag_description\"\n+\n+tag_sha1=$(echo_without_newline \"$tag_content\" | git mktag)\n+tag_size=$(strlen \"$tag_content\")\n+\n+run_tests 'tag' $tag_sha1 $tag_size \"$tag_content\" \"$tag_pretty_content\" 1\n+\n+test_expect_success \\\n+    \"Reach a blob from a tag pointing to it\" \\\n+    \"test '$hello_content' = \\\"\\$(git cat-file blob $tag_sha1)\\\"\"\n+\n+test_done\n-- \n1.5.5.1.152.g9aeb7\n"},{"id":"75087","messageId":"1208978273-98146-3-git-send-email-aroben@apple.com","threadId":"13232","inReplyTo":"1208978273-98146-2-git-send-email-aroben@apple.com","subject":"[PATCH 02/11] git-cat-file: Small refactor of cmd_cat_file","fromName":"Adam Roben","fromEmail":"aroben@apple.com","sentAt":"2008-04-23T19:17:44Z","receivedAt":"2008-04-23T19:17:44Z","isPatch":true,"sender":{"key":"aroben@apple.com","avatar":"https://gravatar.com/avatar/9d3697e1de53890adf241331f4b970bdd2b18962b2ff0b8028ebb00e085807f8?d=mp&s=160"},"body":"I separated the logic of parsing the arguments from the logic of fetching and\noutputting the data. cat_one_file now does the latter.\n\nSigned-off-by: Adam Roben <aroben@apple.com>\n---\n builtin-cat-file.c |   38 ++++++++++++++++++++++----------------\n 1 files changed, 22 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin-cat-file.c b/builtin-cat-file.c\nindex f132d58..34a63d1 100644\n--- a/builtin-cat-file.c\n+++ b/builtin-cat-file.c\n@@ -76,31 +76,16 @@ static void pprint_tag(const unsigned char *sha1, const char *buf, unsigned long\n \t\twrite_or_die(1, cp, endp - cp);\n }\n \n-int cmd_cat_file(int argc, const char **argv, const char *prefix)\n+static int cat_one_file(int opt, const char *exp_type, const char *obj_name)\n {\n \tunsigned char sha1[20];\n \tenum object_type type;\n \tvoid *buf;\n \tunsigned long size;\n-\tint opt;\n-\tconst char *exp_type, *obj_name;\n-\n-\tgit_config(git_default_config);\n-\tif (argc != 3)\n-\t\tusage(\"git-cat-file [-t|-s|-e|-p|<type>] <sha1>\");\n-\texp_type = argv[1];\n-\tobj_name = argv[2];\n \n \tif (get_sha1(obj_name, sha1))\n \t\tdie(\"Not a valid object name %s\", obj_name);\n \n-\topt = 0;\n-\tif ( exp_type[0] == '-' ) {\n-\t\topt = exp_type[1];\n-\t\tif ( !opt || exp_type[2] )\n-\t\t\topt = -1; /* Not a single character option */\n-\t}\n-\n \tbuf = NULL;\n \tswitch (opt) {\n \tcase 't':\n@@ -157,3 +142,24 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)\n \twrite_or_die(1, buf, size);\n \treturn 0;\n }\n+\n+int cmd_cat_file(int argc, const char **argv, const char *prefix)\n+{\n+\tint opt;\n+\tconst char *exp_type, *obj_name;\n+\n+\tgit_config(git_default_config);\n+\tif (argc != 3)\n+\t\tusage(\"git-cat-file [-t|-s|-e|-p|<type>] <sha1>\");\n+\texp_type = argv[1];\n+\tobj_name = argv[2];\n+\n+\topt = 0;\n+\tif ( exp_type[0] == '-' ) {\n+\t\topt = exp_type[1];\n+\t\tif ( !opt || exp_type[2] )\n+\t\t\topt = -1; /* Not a single character option */\n+\t}\n+\n+\treturn cat_one_file(opt, exp_type, obj_name);\n+}\n-- \n1.5.5.1.152.g9aeb7\n"},{"id":"75091","messageId":"1208978273-98146-4-git-send-email-aroben@apple.com","threadId":"13232","inReplyTo":"1208978273-98146-3-git-send-email-aroben@apple.com","subject":"[PATCH 03/11] git-cat-file: Make option parsing a little more flexible","fromName":"Adam Roben","fromEmail":"aroben@apple.com","sentAt":"2008-04-23T19:17:45Z","receivedAt":"2008-04-23T19:17:45Z","isPatch":true,"sender":{"key":"aroben@apple.com","avatar":"https://gravatar.com/avatar/9d3697e1de53890adf241331f4b970bdd2b18962b2ff0b8028ebb00e085807f8?d=mp&s=160"},"body":"This will make it easier to add newer options later.\n\nSigned-off-by: Adam Roben <aroben@apple.com>\n---\nJunio C Hamano <gitster@pobox.com> wrote:\n> > diff --git a/builtin-cat-file.c b/builtin-cat-file.c\n> > index 34a63d1..3a0be4a 100644\n> > --- a/builtin-cat-file.c\n> > +++ b/builtin-cat-file.c\n> > @@ -143,23 +143,41 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)\n> >     return 0;\n> >  }\n> >  \n> > +static const char cat_file_usage[] = \"git-cat-file [-t|-s|-e|-p|<type>] <sha1>\";\n> > +\n> >  int cmd_cat_file(int argc, const char **argv, const char *prefix)\n> >  {\n> > -   int opt;\n> > -   const char *exp_type, *obj_name;\n> > +   int i, opt = 0;\n> > +   const char *exp_type = 0, *obj_name = 0;\n> \n> NULL pointer constants in git sources are spelled \"NULL\", not\n> \"0\".\n\nFixed.\n\n builtin-cat-file.c |   42 ++++++++++++++++++++++++++++++------------\n 1 files changed, 30 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin-cat-file.c b/builtin-cat-file.c\nindex 34a63d1..a76bb16 100644\n--- a/builtin-cat-file.c\n+++ b/builtin-cat-file.c\n@@ -143,23 +143,41 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)\n \treturn 0;\n }\n \n+static const char cat_file_usage[] = \"git-cat-file [-t|-s|-e|-p|<type>] <sha1>\";\n+\n int cmd_cat_file(int argc, const char **argv, const char *prefix)\n {\n-\tint opt;\n-\tconst char *exp_type, *obj_name;\n+\tint i, opt = 0;\n+\tconst char *exp_type = NULL, *obj_name = NULL;\n \n \tgit_config(git_default_config);\n-\tif (argc != 3)\n-\t\tusage(\"git-cat-file [-t|-s|-e|-p|<type>] <sha1>\");\n-\texp_type = argv[1];\n-\tobj_name = argv[2];\n-\n-\topt = 0;\n-\tif ( exp_type[0] == '-' ) {\n-\t\topt = exp_type[1];\n-\t\tif ( !opt || exp_type[2] )\n-\t\t\topt = -1; /* Not a single character option */\n+\n+\tfor (i = 1; i < argc; ++i) {\n+\t\tconst char *arg = argv[i];\n+\n+\t\tif (!strcmp(arg, \"-t\") || !strcmp(arg, \"-s\") || !strcmp(arg, \"-e\") || !strcmp(arg, \"-p\")) {\n+\t\t\texp_type = arg;\n+\t\t\topt = exp_type[1];\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (arg[0] == '-')\n+\t\t\tusage(cat_file_usage);\n+\n+\t\tif (!exp_type) {\n+\t\t\texp_type = arg;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (obj_name)\n+\t\t\tusage(cat_file_usage);\n+\n+\t\tobj_name = arg;\n+\t\tbreak;\n \t}\n \n+\tif (!exp_type || !obj_name)\n+\t\tusage(cat_file_usage);\n+\n \treturn cat_one_file(opt, exp_type, obj_name);\n }\n-- \n1.5.5.1.152.g9aeb7\n"},{"id":"75088","messageId":"1208978273-98146-5-git-send-email-aroben@apple.com","threadId":"13232","inReplyTo":"1208978273-98146-4-git-send-email-aroben@apple.com","subject":"[PATCH 04/11] git-cat-file: Add --batch-check option","fromName":"Adam Roben","fromEmail":"aroben@apple.com","sentAt":"2008-04-23T19:17:46Z","receivedAt":"2008-04-23T19:17:46Z","isPatch":true,"sender":{"key":"aroben@apple.com","avatar":"https://gravatar.com/avatar/9d3697e1de53890adf241331f4b970bdd2b18962b2ff0b8028ebb00e085807f8?d=mp&s=160"},"body":"This new option allows multiple objects to be specified on stdin. For each\nobject specified, a line of the following form is printed:\n\n<sha1> SP <type> SP <size> LF\n\nIf the object does not exist in the repository, a line of the following form is\nprinted:\n\n<object> SP missing LF\n\nSigned-off-by: Adam Roben <aroben@apple.com>\n---\nJunio C Hamano <gitster@pobox.com> wrote:\n> Here is an alternative suggestion.\n> \n>    Two new options, --batch and --batch-check, are introduced.\n>    These options are incompatible with -[tsep] or an object type\n>    given as the first parameter to git-cat-file.\n> \n>    * git-cat-file --batch-check <list-of-sha1\n> \n>      outputs a record of this form\n> \n>           <sha1> SP <type> SP <size> LF\n> \n>      for each of the input lines.\n> \n>    * git-cat-file --batch <list-of-sha1\n> \n>      outputs a record of this form\n> \n>           <sha1> SP <type> SP <size> LF <contents> LF\n> \n>      for each of the input lines.\n> \n>   For a missing object, either option gives a record of form:\n> \n>           <sha1> SP missing LF\n\nThis patch introduces --batch-check as described above. The next patch introduces --batch.\n\n Documentation/git-cat-file.txt |   31 ++++++++++++++---\n builtin-cat-file.c             |   74 ++++++++++++++++++++++++++++++++++++++-\n t/t1006-cat-file.sh            |   52 ++++++++++++++++++++++++++++\n 3 files changed, 150 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt\nindex df42cb1..d5821af 100644\n--- a/Documentation/git-cat-file.txt\n+++ b/Documentation/git-cat-file.txt\n@@ -9,12 +9,16 @@ git-cat-file - Provide content or type/size information for repository objects\n SYNOPSIS\n --------\n 'git-cat-file' [-t | -s | -e | -p | <type>] <object>\n+'git-cat-file' --batch-check < <list-of-objects>\n \n DESCRIPTION\n -----------\n-Provides content or type of objects in the repository. The type\n-is required unless '-t' or '-p' is used to find the object type,\n-or '-s' is used to find the object size.\n+In the first form, provides content or type of objects in the repository. The\n+type is required unless '-t' or '-p' is used to find the object type, or '-s'\n+is used to find the object size.\n+\n+In the second form, a list of object (separated by LFs) is provided on stdin,\n+and the SHA1, type, and size of each object is printed on stdout.\n \n OPTIONS\n -------\n@@ -46,6 +50,10 @@ OPTIONS\n \tor to ask for a \"blob\" with <object> being a tag object that\n \tpoints at it.\n \n+--batch-check::\n+\tPrint the SHA1, type, and size of each object provided on stdin. May not be\n+\tcombined with any other options or arguments.\n+\n OUTPUT\n ------\n If '-t' is specified, one of the <type>.\n@@ -56,9 +64,22 @@ If '-e' is specified, no output.\n \n If '-p' is specified, the contents of <object> are pretty-printed.\n \n-Otherwise the raw (though uncompressed) contents of the <object> will\n-be returned.\n+If <type> is specified, the raw (though uncompressed) contents of the <object>\n+will be returned.\n+\n+If '--batch-check' is specified, output of the following form is printed for\n+each object specified fon stdin:\n+\n+------------\n+<sha1> SP <type> SP <size> LF\n+------------\n+\n+Additionally, output of the following form is printed for each object specified\n+on stdin that does not exist in the repository:\n \n+------------\n+<object> SP missing LF\n+------------\n \n Author\n ------\ndiff --git a/builtin-cat-file.c b/builtin-cat-file.c\nindex a76bb16..832cfd1 100644\n--- a/builtin-cat-file.c\n+++ b/builtin-cat-file.c\n@@ -143,11 +143,48 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)\n \treturn 0;\n }\n \n-static const char cat_file_usage[] = \"git-cat-file [-t|-s|-e|-p|<type>] <sha1>\";\n+static int batch_one_object(const char *obj_name)\n+{\n+\tunsigned char sha1[20];\n+\tenum object_type type;\n+\tunsigned long size;\n+\n+\tif (!obj_name)\n+\t   return 1;\n+\n+\tif (get_sha1(obj_name, sha1)) {\n+\t\tprintf(\"%s missing\\n\", obj_name);\n+\t\treturn 0;\n+\t}\n+\n+\ttype = sha1_object_info(sha1, &size);\n+\tif (type <= 0)\n+\t\treturn 1;\n+\n+\tprintf(\"%s %s %lu\\n\", sha1_to_hex(sha1), typename(type), size);\n+\n+\treturn 0;\n+}\n+\n+static int batch_objects(void)\n+{\n+\tstruct strbuf buf;\n+\n+\tstrbuf_init(&buf, 0);\n+\twhile (strbuf_getline(&buf, stdin, '\\n') != EOF) {\n+\t\tint error = batch_one_object(buf.buf);\n+\t\tif (error)\n+\t\t\treturn error;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static const char cat_file_usage[] = \"git-cat-file [ [-t|-s|-e|-p|<type>] <sha1> | --batch-check < <list_of_sha1s> ]\";\n \n int cmd_cat_file(int argc, const char **argv, const char *prefix)\n {\n-\tint i, opt = 0;\n+\tint i, opt = 0, batch_check = 0;\n \tconst char *exp_type = NULL, *obj_name = NULL;\n \n \tgit_config(git_default_config);\n@@ -155,7 +192,28 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)\n \tfor (i = 1; i < argc; ++i) {\n \t\tconst char *arg = argv[i];\n \n+\t\tif (!strcmp(arg, \"--batch-check\")) {\n+\t\t\tif (opt) {\n+\t\t\t\terror(\"git-cat-file: Can't use --batch-check with -%c\", opt);\n+\t\t\t\tusage(cat_file_usage);\n+\t\t\t} else if (exp_type) {\n+\t\t\t\terror(\"git-cat-file: Can't use --batch-check when a type (\\\"%s\\\") is specified\", exp_type);\n+\t\t\t\tusage(cat_file_usage);\n+\t\t\t} else if (obj_name) {\n+\t\t\t\terror(\"git-cat-file: Can't use --batch-check when an object (\\\"%s\\\") is specified\", obj_name);\n+\t\t\t\tusage(cat_file_usage);\n+\t\t\t}\n+\n+\t\t\tbatch_check = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\tif (!strcmp(arg, \"-t\") || !strcmp(arg, \"-s\") || !strcmp(arg, \"-e\") || !strcmp(arg, \"-p\")) {\n+\t\t\tif (batch_check) {\n+\t\t\t\terror(\"git-cat-file: Can't use %s with --batch-check\", arg);\n+\t\t\t\tusage(cat_file_usage);\n+\t\t\t}\n+\n \t\t\texp_type = arg;\n \t\t\topt = exp_type[1];\n \t\t\tcontinue;\n@@ -165,6 +223,11 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)\n \t\t\tusage(cat_file_usage);\n \n \t\tif (!exp_type) {\n+\t\t\tif (batch_check) {\n+\t\t\t\terror(\"git-cat-file: Can't specify a type (\\\"%s\\\") with --batch-check\", arg);\n+\t\t\t\tusage(cat_file_usage);\n+\t\t\t}\n+\n \t\t\texp_type = arg;\n \t\t\tcontinue;\n \t\t}\n@@ -172,10 +235,17 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)\n \t\tif (obj_name)\n \t\t\tusage(cat_file_usage);\n \n+\t\t// We should have hit one of the earlier if (batch_check) cases before\n+\t\t// getting here.\n+\t\tassert(!batch_check);\n+\n \t\tobj_name = arg;\n \t\tbreak;\n \t}\n \n+\tif (batch_check)\n+\t\treturn batch_objects();\n+\n \tif (!exp_type || !obj_name)\n \t\tusage(cat_file_usage);\n \ndiff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\nindex 15741d9..46b0f54 100755\n--- a/t/t1006-cat-file.sh\n+++ b/t/t1006-cat-file.sh\n@@ -47,6 +47,9 @@ function run_tests()\n     test_expect_success \\\n         \"Pretty content of $type is correct\" \\\n         \"test \\\"\\$(maybe_remove_timestamp '$pretty_content' $no_timestamp)\\\" = \\\"\\$(maybe_remove_timestamp \\\"\\$(git cat-file -p $sha1)\\\" $no_timestamp)\\\"\"\n+    test_expect_success \\\n+        \"--batch-check output of $type is correct\" \\\n+        \"test \\\"$sha1 $type $size\\\" = \\\"\\$(echo_without_newline $sha1 | git cat-file --batch-check)\\\"\"\n }\n \n hello_content=\"Hello World\"\n@@ -98,4 +101,53 @@ test_expect_success \\\n     \"Reach a blob from a tag pointing to it\" \\\n     \"test '$hello_content' = \\\"\\$(git cat-file blob $tag_sha1)\\\"\"\n \n+for opt in t s e p; do\n+    test_expect_success \\\n+        \"Passing -$opt with --batch-check fails\" \\\n+        \"test_must_fail git cat-file --batch-check -$opt $hello_sha1\"\n+\n+    test_expect_success \\\n+        \"Passing --batch-check with -$opt fails\" \\\n+        \"test_must_fail git cat-file -$opt --batch-check $hello_sha1\"\n+done\n+\n+test_expect_success \\\n+    \"Passing <type> with --batch-check fails\" \\\n+    \"test_must_fail git cat-file --batch-check blob $hello_sha1\"\n+\n+test_expect_success \\\n+    \"Passing --batch-check with <type> fails\" \\\n+    \"test_must_fail git cat-file blob --batch-check $hello_sha1\"\n+\n+test_expect_success \\\n+    \"Passing sha1 with --batch-check fails\" \\\n+    \"test_must_fail git cat-file --batch-check $hello_sha1\"\n+\n+test_expect_success \\\n+    \"--batch-check for a non-existent object\" \\\n+    \"test \\\"deadbeef missing\\\" = \\\"\\$(echo_without_newline deadbeef | git cat-file --batch-check)\\\"\"\n+\n+test_expect_success \\\n+    \"--batch-check for an emtpy line\" \\\n+    \"test \\\" missing\\\" = \\\"\\$(printf \\\"\\\\\\\\n\\\" | git cat-file --batch-check)\\\"\"\n+\n+batch_check_input=\"$hello_sha1\n+$tree_sha1\n+$commit_sha1\n+$tag_sha1\n+deadbeef\n+\n+\"\n+\n+batch_check_output=\"$hello_sha1 blob $hello_size\n+$tree_sha1 tree $tree_size\n+$commit_sha1 commit $commit_size\n+$tag_sha1 tag $tag_size\n+deadbeef missing\n+ missing\"\n+\n+test_expect_success \\\n+    \"--batch-check with multiple sha1s gives correct format\" \\\n+    \"test \\\"$batch_check_output\\\" = \\\"\\$(echo_without_newline \\\"$batch_check_input\\\" | git cat-file --batch-check)\\\"\"\n+\n test_done\n-- \n1.5.5.1.152.g9aeb7\n"},{"id":"75093","messageId":"1208978273-98146-6-git-send-email-aroben@apple.com","threadId":"13232","inReplyTo":"1208978273-98146-5-git-send-email-aroben@apple.com","subject":"[PATCH 05/11] git-cat-file: Add --batch option","fromName":"Adam Roben","fromEmail":"aroben@apple.com","sentAt":"2008-04-23T19:17:47Z","receivedAt":"2008-04-23T19:17:47Z","isPatch":true,"sender":{"key":"aroben@apple.com","avatar":"https://gravatar.com/avatar/9d3697e1de53890adf241331f4b970bdd2b18962b2ff0b8028ebb00e085807f8?d=mp&s=160"},"body":"--batch is similar to --batch-check, except that the contents of each object is\nalso printed. The output's form is:\n\n<sha1> SP <type> SP <size> LF\n<contents> LF\n\nSigned-off-by: Adam Roben <aroben@apple.com>\n---\n Documentation/git-cat-file.txt |   18 +++++++++--\n builtin-cat-file.c             |   63 ++++++++++++++++++++++++++++-----------\n t/t1006-cat-file.sh            |   62 ++++++++++++++++++++++++++++-----------\n 3 files changed, 105 insertions(+), 38 deletions(-)\n\ndiff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt\nindex d5821af..f6c394c 100644\n--- a/Documentation/git-cat-file.txt\n+++ b/Documentation/git-cat-file.txt\n@@ -9,7 +9,7 @@ git-cat-file - Provide content or type/size information for repository objects\n SYNOPSIS\n --------\n 'git-cat-file' [-t | -s | -e | -p | <type>] <object>\n-'git-cat-file' --batch-check < <list-of-objects>\n+'git-cat-file' [--batch | --batch-check] < <list-of-objects>\n \n DESCRIPTION\n -----------\n@@ -50,6 +50,10 @@ OPTIONS\n \tor to ask for a \"blob\" with <object> being a tag object that\n \tpoints at it.\n \n+--batch::\n+\tPrint the SHA1, type, size, and contents of each object provided on\n+\tstdin. May not be combined with any other options or arguments.\n+\n --batch-check::\n \tPrint the SHA1, type, and size of each object provided on stdin. May not be\n \tcombined with any other options or arguments.\n@@ -67,6 +71,14 @@ If '-p' is specified, the contents of <object> are pretty-printed.\n If <type> is specified, the raw (though uncompressed) contents of the <object>\n will be returned.\n \n+If '--batch' is specified, output of the following form is printed for each\n+object specified on stdin:\n+\n+------------\n+<sha1> SP <type> SP <size> LF\n+<contents> LF\n+------------\n+\n If '--batch-check' is specified, output of the following form is printed for\n each object specified fon stdin:\n \n@@ -74,8 +86,8 @@ each object specified fon stdin:\n <sha1> SP <type> SP <size> LF\n ------------\n \n-Additionally, output of the following form is printed for each object specified\n-on stdin that does not exist in the repository:\n+For both '--batch' and '--batch-check', output of the following form is printed\n+for each object specified on stdin that does not exist in the repository:\n \n ------------\n <object> SP missing LF\ndiff --git a/builtin-cat-file.c b/builtin-cat-file.c\nindex 832cfd1..827ffcc 100644\n--- a/builtin-cat-file.c\n+++ b/builtin-cat-file.c\n@@ -143,11 +143,12 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name)\n \treturn 0;\n }\n \n-static int batch_one_object(const char *obj_name)\n+static int batch_one_object(const char *obj_name, int print_contents)\n {\n \tunsigned char sha1[20];\n \tenum object_type type;\n \tunsigned long size;\n+\tvoid *contents;\n \n \tif (!obj_name)\n \t   return 1;\n@@ -157,22 +158,33 @@ static int batch_one_object(const char *obj_name)\n \t\treturn 0;\n \t}\n \n-\ttype = sha1_object_info(sha1, &size);\n+\tif (print_contents)\n+\t\tcontents = read_sha1_file(sha1, &type, &size);\n+\telse\n+\t\ttype = sha1_object_info(sha1, &size);\n+\n \tif (type <= 0)\n \t\treturn 1;\n \n \tprintf(\"%s %s %lu\\n\", sha1_to_hex(sha1), typename(type), size);\n+\tfflush(stdout);\n+\n+\tif (print_contents) {\n+\t\twrite_or_die(1, contents, size);\n+\t\tprintf(\"\\n\");\n+\t\tfflush(stdout);\n+\t}\n \n \treturn 0;\n }\n \n-static int batch_objects(void)\n+static int batch_objects(int print_contents)\n {\n \tstruct strbuf buf;\n \n \tstrbuf_init(&buf, 0);\n \twhile (strbuf_getline(&buf, stdin, '\\n') != EOF) {\n-\t\tint error = batch_one_object(buf.buf);\n+\t\tint error = batch_one_object(buf.buf, print_contents);\n \t\tif (error)\n \t\t\treturn error;\n \t}\n@@ -180,37 +192,51 @@ static int batch_objects(void)\n \treturn 0;\n }\n \n-static const char cat_file_usage[] = \"git-cat-file [ [-t|-s|-e|-p|<type>] <sha1> | --batch-check < <list_of_sha1s> ]\";\n+static const char cat_file_usage[] = \"git-cat-file [ [-t|-s|-e|-p|<type>] <sha1> | [--batch|--batch-check] < <list_of_sha1s> ]\";\n \n int cmd_cat_file(int argc, const char **argv, const char *prefix)\n {\n-\tint i, opt = 0, batch_check = 0;\n+\tint i, opt = 0, batch = 0, batch_check = 0;\n \tconst char *exp_type = NULL, *obj_name = NULL;\n \n \tgit_config(git_default_config);\n \n \tfor (i = 1; i < argc; ++i) {\n \t\tconst char *arg = argv[i];\n+\t\tint is_batch = 0, is_batch_check = 0;\n+\n+\t\tis_batch = !strcmp(arg, \"--batch\");\n+\t\tif (!is_batch)\n+\t\t\tis_batch_check = !strcmp(arg, \"--batch-check\");\n \n-\t\tif (!strcmp(arg, \"--batch-check\")) {\n+\t\tif (is_batch || is_batch_check) {\n \t\t\tif (opt) {\n-\t\t\t\terror(\"git-cat-file: Can't use --batch-check with -%c\", opt);\n+\t\t\t\terror(\"git-cat-file: Can't use %s with -%c\", arg, opt);\n \t\t\t\tusage(cat_file_usage);\n \t\t\t} else if (exp_type) {\n-\t\t\t\terror(\"git-cat-file: Can't use --batch-check when a type (\\\"%s\\\") is specified\", exp_type);\n+\t\t\t\terror(\"git-cat-file: Can't use %s when a type (\\\"%s\\\") is specified\", arg, exp_type);\n \t\t\t\tusage(cat_file_usage);\n \t\t\t} else if (obj_name) {\n-\t\t\t\terror(\"git-cat-file: Can't use --batch-check when an object (\\\"%s\\\") is specified\", obj_name);\n+\t\t\t\terror(\"git-cat-file: Can't use %s when an object (\\\"%s\\\") is specified\", arg, obj_name);\n \t\t\t\tusage(cat_file_usage);\n \t\t\t}\n \n-\t\t\tbatch_check = 1;\n+\t\t\tif (is_batch && batch_check || is_batch_check && batch) {\n+\t\t\t\terror(\"git-cat-file: Can't use %s with %s\", arg, is_batch ? \"--batch-check\" : \"--batch\");\n+\t\t\t\tusage(cat_file_usage);\n+\t\t\t}\n+\n+\t\t\tif (is_batch)\n+\t\t\t\tbatch = 1;\n+\t\t\telse\n+\t\t\t\tbatch_check = 1;\n+\n \t\t\tcontinue;\n \t\t}\n \n \t\tif (!strcmp(arg, \"-t\") || !strcmp(arg, \"-s\") || !strcmp(arg, \"-e\") || !strcmp(arg, \"-p\")) {\n-\t\t\tif (batch_check) {\n-\t\t\t\terror(\"git-cat-file: Can't use %s with --batch-check\", arg);\n+\t\t\tif (batch || batch_check) {\n+\t\t\t\terror(\"git-cat-file: Can't use %s with %s\", arg, batch ? \"--batch\" : \"--batch-check\");\n \t\t\t\tusage(cat_file_usage);\n \t\t\t}\n \n@@ -223,8 +249,8 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)\n \t\t\tusage(cat_file_usage);\n \n \t\tif (!exp_type) {\n-\t\t\tif (batch_check) {\n-\t\t\t\terror(\"git-cat-file: Can't specify a type (\\\"%s\\\") with --batch-check\", arg);\n+\t\t\tif (batch || batch_check) {\n+\t\t\t\terror(\"git-cat-file: Can't specify a type (\\\"%s\\\") with %s\", arg, batch ? \"--batch\" : \"--batch-check\");\n \t\t\t\tusage(cat_file_usage);\n \t\t\t}\n \n@@ -235,16 +261,17 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix)\n \t\tif (obj_name)\n \t\t\tusage(cat_file_usage);\n \n-\t\t// We should have hit one of the earlier if (batch_check) cases before\n+\t\t// We should have hit one of the earlier if (batch || batch_check) cases before\n \t\t// getting here.\n+\t\tassert(!batch);\n \t\tassert(!batch_check);\n \n \t\tobj_name = arg;\n \t\tbreak;\n \t}\n \n-\tif (batch_check)\n-\t\treturn batch_objects();\n+\tif (batch || batch_check)\n+\t\treturn batch_objects(batch);\n \n \tif (!exp_type || !obj_name)\n \t\tusage(cat_file_usage);\ndiff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\nindex 46b0f54..acce8c8 100755\n--- a/t/t1006-cat-file.sh\n+++ b/t/t1006-cat-file.sh\n@@ -32,6 +32,9 @@ function run_tests()\n     pretty_content=$5\n     no_timestamp=$6\n \n+    batch_output=\"$sha1 $type $size\n+$content\"\n+\n     test_expect_success \\\n         \"$type exists\" \\\n         \"git cat-file -e $sha1\"\n@@ -47,6 +50,9 @@ function run_tests()\n     test_expect_success \\\n         \"Pretty content of $type is correct\" \\\n         \"test \\\"\\$(maybe_remove_timestamp '$pretty_content' $no_timestamp)\\\" = \\\"\\$(maybe_remove_timestamp \\\"\\$(git cat-file -p $sha1)\\\" $no_timestamp)\\\"\"\n+    test -z \"$content\" || test_expect_success \\\n+        \"--batch output of $type is correct\" \\\n+        \"test \\\"\\$(maybe_remove_timestamp '$batch_output' $no_timestamp)\\\" = \\\"\\$(maybe_remove_timestamp \\\"\\$(echo $sha1 | git cat-file --batch)\\\" $no_timestamp)\\\"\"\n     test_expect_success \\\n         \"--batch-check output of $type is correct\" \\\n         \"test \\\"$sha1 $type $size\\\" = \\\"\\$(echo_without_newline $sha1 | git cat-file --batch-check)\\\"\"\n@@ -101,27 +107,29 @@ test_expect_success \\\n     \"Reach a blob from a tag pointing to it\" \\\n     \"test '$hello_content' = \\\"\\$(git cat-file blob $tag_sha1)\\\"\"\n \n-for opt in t s e p; do\n-    test_expect_success \\\n-        \"Passing -$opt with --batch-check fails\" \\\n-        \"test_must_fail git cat-file --batch-check -$opt $hello_sha1\"\n+for batch in batch batch-check; do\n+    for opt in t s e p; do\n+        test_expect_success \\\n+            \"Passing -$opt with --$batch fails\" \\\n+            \"test_must_fail git cat-file --$batch -$opt $hello_sha1\"\n \n-    test_expect_success \\\n-        \"Passing --batch-check with -$opt fails\" \\\n-        \"test_must_fail git cat-file -$opt --batch-check $hello_sha1\"\n-done\n+        test_expect_success \\\n+            \"Passing --$batch with -$opt fails\" \\\n+            \"test_must_fail git cat-file -$opt --$batch $hello_sha1\"\n+    done\n \n-test_expect_success \\\n-    \"Passing <type> with --batch-check fails\" \\\n-    \"test_must_fail git cat-file --batch-check blob $hello_sha1\"\n+    test_expect_success \\\n+        \"Passing <type> with --$batch fails\" \\\n+        \"test_must_fail git cat-file --$batch blob $hello_sha1\"\n \n-test_expect_success \\\n-    \"Passing --batch-check with <type> fails\" \\\n-    \"test_must_fail git cat-file blob --batch-check $hello_sha1\"\n+    test_expect_success \\\n+        \"Passing --$batch with <type> fails\" \\\n+        \"test_must_fail git cat-file blob --$batch $hello_sha1\"\n \n-test_expect_success \\\n-    \"Passing sha1 with --batch-check fails\" \\\n-    \"test_must_fail git cat-file --batch-check $hello_sha1\"\n+    test_expect_success \\\n+        \"Passing sha1 with --$batch fails\" \\\n+        \"test_must_fail git cat-file --$batch $hello_sha1\"\n+done\n \n test_expect_success \\\n     \"--batch-check for a non-existent object\" \\\n@@ -131,6 +139,26 @@ test_expect_success \\\n     \"--batch-check for an emtpy line\" \\\n     \"test \\\" missing\\\" = \\\"\\$(printf \\\"\\\\\\\\n\\\" | git cat-file --batch-check)\\\"\"\n \n+batch_input=\"$hello_sha1\n+$commit_sha1\n+$tag_sha1\n+deadbeef\n+\n+\"\n+\n+batch_output=\"$hello_sha1 blob $hello_size\n+$hello_content\n+$commit_sha1 commit $commit_size\n+$commit_content\n+$tag_sha1 tag $tag_size\n+$tag_content\n+deadbeef missing\n+ missing\"\n+\n+test_expect_success \\\n+    \"--batch with multiple sha1s gives correct format\" \\\n+    \"test \\\"\\$(maybe_remove_timestamp \\\"$batch_output\\\" 1)\\\" = \\\"\\$(maybe_remove_timestamp \\\"\\$(echo_without_newline \\\"$batch_input\\\" | git cat-file --batch)\\\" 1)\\\"\"\n+\n batch_check_input=\"$hello_sha1\n $tree_sha1\n $commit_sha1\n-- \n1.5.5.1.152.g9aeb7\n"},{"id":"75086","messageId":"1208978273-98146-7-git-send-email-aroben@apple.com","threadId":"13232","inReplyTo":"1208978273-98146-6-git-send-email-aroben@apple.com","subject":"[PATCH 06/11] Move git-hash-object tests from t5303 to t1007","fromName":"Adam Roben","fromEmail":"aroben@apple.com","sentAt":"2008-04-23T19:17:48Z","receivedAt":"2008-04-23T19:17:48Z","isPatch":true,"sender":{"key":"aroben@apple.com","avatar":"https://gravatar.com/avatar/9d3697e1de53890adf241331f4b970bdd2b18962b2ff0b8028ebb00e085807f8?d=mp&s=160"},"body":"This is a more appropriate location according to t/README.\n\nSigned-off-by: Adam Roben <aroben@apple.com>\n---\n t/t1007-hash-object.sh |   35 +++++++++++++++++++++++++++++++++++\n t/t5303-hash-object.sh |   35 -----------------------------------\n 2 files changed, 35 insertions(+), 35 deletions(-)\n create mode 100755 t/t1007-hash-object.sh\n delete mode 100755 t/t5303-hash-object.sh\n\ndiff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh\nnew file mode 100755\nindex 0000000..543c078\n--- /dev/null\n+++ b/t/t1007-hash-object.sh\n@@ -0,0 +1,35 @@\n+#!/bin/sh\n+\n+test_description=git-hash-object\n+\n+. ./test-lib.sh\n+\n+test_expect_success \\\n+    'git hash-object -w --stdin saves the object' \\\n+    'obname=$(echo foo | git hash-object -w --stdin) &&\n+    obpath=$(echo $obname | sed -e \"s/\\(..\\)/\\1\\//\") &&\n+    test -r .git/objects/\"$obpath\" &&\n+    rm -f .git/objects/\"$obpath\"'\n+    \n+test_expect_success \\\n+    'git hash-object --stdin -w saves the object' \\\n+    'obname=$(echo foo | git hash-object --stdin -w) &&\n+    obpath=$(echo $obname | sed -e \"s/\\(..\\)/\\1\\//\") &&\n+    test -r .git/objects/\"$obpath\" &&\n+    rm -f .git/objects/\"$obpath\"'    \n+\n+test_expect_success \\\n+    'git hash-object --stdin file1 <file0 first operates on file0, then file1' \\\n+    'echo foo > file1 &&\n+    obname0=$(echo bar | git hash-object --stdin) &&\n+    obname1=$(git hash-object file1) &&\n+    obname0new=$(echo bar | git hash-object --stdin file1 | sed -n -e 1p) &&\n+    obname1new=$(echo bar | git hash-object --stdin file1 | sed -n -e 2p) &&\n+    test \"$obname0\" = \"$obname0new\" &&\n+    test \"$obname1\" = \"$obname1new\"'\n+\n+test_expect_success \\\n+    'git hash-object refuses multiple --stdin arguments' \\\n+    '! git hash-object --stdin --stdin < file1'\n+\n+test_done\ndiff --git a/t/t5303-hash-object.sh b/t/t5303-hash-object.sh\ndeleted file mode 100755\nindex 543c078..0000000\n--- a/t/t5303-hash-object.sh\n+++ /dev/null\n@@ -1,35 +0,0 @@\n-#!/bin/sh\n-\n-test_description=git-hash-object\n-\n-. ./test-lib.sh\n-\n-test_expect_success \\\n-    'git hash-object -w --stdin saves the object' \\\n-    'obname=$(echo foo | git hash-object -w --stdin) &&\n-    obpath=$(echo $obname | sed -e \"s/\\(..\\)/\\1\\//\") &&\n-    test -r .git/objects/\"$obpath\" &&\n-    rm -f .git/objects/\"$obpath\"'\n-    \n-test_expect_success \\\n-    'git hash-object --stdin -w saves the object' \\\n-    'obname=$(echo foo | git hash-object --stdin -w) &&\n-    obpath=$(echo $obname | sed -e \"s/\\(..\\)/\\1\\//\") &&\n-    test -r .git/objects/\"$obpath\" &&\n-    rm -f .git/objects/\"$obpath\"'    \n-\n-test_expect_success \\\n-    'git hash-object --stdin file1 <file0 first operates on file0, then file1' \\\n-    'echo foo > file1 &&\n-    obname0=$(echo bar | git hash-object --stdin) &&\n-    obname1=$(git hash-object file1) &&\n-    obname0new=$(echo bar | git hash-object --stdin file1 | sed -n -e 1p) &&\n-    obname1new=$(echo bar | git hash-object --stdin file1 | sed -n -e 2p) &&\n-    test \"$obname0\" = \"$obname0new\" &&\n-    test \"$obname1\" = \"$obname1new\"'\n-\n-test_expect_success \\\n-    'git hash-object refuses multiple --stdin arguments' \\\n-    '! git hash-object --stdin --stdin < file1'\n-\n-test_done\n-- \n1.5.5.1.152.g9aeb7\n"},{"id":"75084","messageId":"1208978273-98146-8-git-send-email-aroben@apple.com","threadId":"13232","inReplyTo":"1208978273-98146-7-git-send-email-aroben@apple.com","subject":"[PATCH 07/11] Add more tests for git hash-object","fromName":"Adam Roben","fromEmail":"aroben@apple.com","sentAt":"2008-04-23T19:17:49Z","receivedAt":"2008-04-23T19:17:49Z","isPatch":true,"sender":{"key":"aroben@apple.com","avatar":"https://gravatar.com/avatar/9d3697e1de53890adf241331f4b970bdd2b18962b2ff0b8028ebb00e085807f8?d=mp&s=160"},"body":"\nSigned-off-by: Adam Roben <aroben@apple.com>\n---\nJunio C Hamano <gitster@pobox.com> wrote:\n> > +test_expect_success \\\n> > +    'hash a file' \\\n> > +    \"test $hello_sha1 = \\$(git hash-object hello)\"\n> > +\n> > +test_expect_success \\\n> > +    'hash from stdin' \\\n> > +    \"test $hello_sha1 = \\$(echo '$hello_content' | git hash-object --stdin)\"\n> \n> Needs to make sure no object has been written to the object\n> database at this point?\n> \n> > +test_expect_success \\\n> > +    'hash a file and write to database' \\\n> > +    \"test $hello_sha1 = \\$(git hash-object -w hello)\"\n> \n> ... and make sure the objectis written here?\n> \n> > +test_expect_success \\\n> > +    'hash from stdin and write to database' \\\n> > +    \"test $hello_sha1 = \\$(echo '$hello_content' | git hash-object -w --stdin)\"\n> > +\n> > +test_done\n> \n> ... and/or here?\n\nFixed.\n\n t/t1007-hash-object.sh |  120 ++++++++++++++++++++++++++++++++++++++----------\n 1 files changed, 96 insertions(+), 24 deletions(-)\n\ndiff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh\nindex 543c078..eb54f1f 100755\n--- a/t/t1007-hash-object.sh\n+++ b/t/t1007-hash-object.sh\n@@ -4,32 +4,104 @@ test_description=git-hash-object\n \n . ./test-lib.sh\n \n-test_expect_success \\\n-    'git hash-object -w --stdin saves the object' \\\n-    'obname=$(echo foo | git hash-object -w --stdin) &&\n-    obpath=$(echo $obname | sed -e \"s/\\(..\\)/\\1\\//\") &&\n-    test -r .git/objects/\"$obpath\" &&\n-    rm -f .git/objects/\"$obpath\"'\n-    \n-test_expect_success \\\n-    'git hash-object --stdin -w saves the object' \\\n-    'obname=$(echo foo | git hash-object --stdin -w) &&\n-    obpath=$(echo $obname | sed -e \"s/\\(..\\)/\\1\\//\") &&\n-    test -r .git/objects/\"$obpath\" &&\n-    rm -f .git/objects/\"$obpath\"'    \n+function echo_without_newline()\n+{\n+    echo \"$@\\c\"\n+}\n \n-test_expect_success \\\n-    'git hash-object --stdin file1 <file0 first operates on file0, then file1' \\\n-    'echo foo > file1 &&\n-    obname0=$(echo bar | git hash-object --stdin) &&\n-    obname1=$(git hash-object file1) &&\n-    obname0new=$(echo bar | git hash-object --stdin file1 | sed -n -e 1p) &&\n-    obname1new=$(echo bar | git hash-object --stdin file1 | sed -n -e 2p) &&\n-    test \"$obname0\" = \"$obname0new\" &&\n-    test \"$obname1\" = \"$obname1new\"'\n+function test_blob_does_not_exist()\n+{\n+    test_expect_success \\\n+        \"blob does not exist in database\" \\\n+        \"test_must_fail git cat-file blob $1\"\n+}\n+\n+function test_blob_exists()\n+{\n+    test_expect_success \\\n+        \"blob exists in database\" \\\n+        \"git cat-file blob $1\"\n+}\n+\n+hello_content=\"Hello World\"\n+hello_sha1=5e1c309dae7f45e0f39b1bf3ac3cd9db12e7d689\n+\n+example_content=\"This is an example\"\n+example_sha1=ddd3f836d3e3fbb7ae289aa9ae83536f76956399\n+\n+function setup_repo()\n+{\n+    echo_without_newline \"$hello_content\" > hello\n+    echo_without_newline \"$example_content\" > example\n+}\n+\n+test_repo=test\n+function push_repo()\n+{\n+    test_create_repo $test_repo\n+    cd $test_repo\n+\n+    setup_repo\n+}\n+\n+function pop_repo()\n+{\n+    cd ..\n+    rm -rf $test_repo\n+}\n+\n+setup_repo\n+\n+# Argument checking\n \n test_expect_success \\\n-    'git hash-object refuses multiple --stdin arguments' \\\n-    '! git hash-object --stdin --stdin < file1'\n+    \"multiple '--stdin's are rejected\" \\\n+    \"test_must_fail git hash-object --stdin --stdin < example\"\n+\n+# Behavior\n+\n+push_repo\n+\n+    test_expect_success \\\n+        \"hash a file\" \\\n+        \"test $hello_sha1 = \\$(git hash-object hello)\"\n+\n+    test_blob_does_not_exist $hello_sha1\n+\n+    test_expect_success \\\n+        \"hash from stdin\" \\\n+        \"test $example_sha1 = \\$(git hash-object --stdin < example)\"\n+\n+    test_blob_does_not_exist $example_sha1\n+\n+    test_expect_success \\\n+        \"hash a file and write to database\" \\\n+        \"test $hello_sha1 = \\$(git hash-object -w hello)\"\n+\n+    test_blob_exists $hello_sha1\n+\n+    test_expect_success \\\n+        'git hash-object --stdin file1 <file0 first operates on file0, then file1' \\\n+        'echo foo > file1 &&\n+        obname0=$(echo bar | git hash-object --stdin) &&\n+        obname1=$(git hash-object file1) &&\n+        obname0new=$(echo bar | git hash-object --stdin file1 | sed -n -e 1p) &&\n+        obname1new=$(echo bar | git hash-object --stdin file1 | sed -n -e 2p) &&\n+        test \"$obname0\" = \"$obname0new\" &&\n+        test \"$obname1\" = \"$obname1new\"'\n+\n+pop_repo\n+\n+for args in \"-w --stdin\" \"--stdin -w\"; do\n+    push_repo\n+\n+        test_expect_success \\\n+            \"hash from stdin and write to database ($args)\" \\\n+            \"test $example_sha1 = \\$(git hash-object $args < example)\"\n+\n+        test_blob_exists $example_sha1\n+\n+    pop_repo\n+done\n \n test_done\n-- \n1.5.5.1.152.g9aeb7\n"},{"id":"75089","messageId":"1208978273-98146-9-git-send-email-aroben@apple.com","threadId":"13232","inReplyTo":"1208978273-98146-8-git-send-email-aroben@apple.com","subject":"[PATCH 08/11] git-hash-object: Add --stdin-paths option","fromName":"Adam Roben","fromEmail":"aroben@apple.com","sentAt":"2008-04-23T19:17:50Z","receivedAt":"2008-04-23T19:17:50Z","isPatch":true,"sender":{"key":"aroben@apple.com","avatar":"https://gravatar.com/avatar/9d3697e1de53890adf241331f4b970bdd2b18962b2ff0b8028ebb00e085807f8?d=mp&s=160"},"body":"This allows multiple paths to be specified on stdin.\n\nSigned-off-by: Adam Roben <aroben@apple.com>\n---\n Documentation/git-hash-object.txt |    5 +++-\n hash-object.c                     |   44 ++++++++++++++++++++++++++++++++++++-\n t/t1007-hash-object.sh            |   32 ++++++++++++++++++++++++++\n 3 files changed, 79 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-hash-object.txt b/Documentation/git-hash-object.txt\nindex 33030c0..99a2143 100644\n--- a/Documentation/git-hash-object.txt\n+++ b/Documentation/git-hash-object.txt\n@@ -8,7 +8,7 @@ git-hash-object - Compute object ID and optionally creates a blob from a file\n \n SYNOPSIS\n --------\n-'git-hash-object' [-t <type>] [-w] [--stdin] [--] <file>...\n+'git-hash-object' [-t <type>] [-w] [--stdin | --stdin-paths] [--] <file>...\n \n DESCRIPTION\n -----------\n@@ -32,6 +32,9 @@ OPTIONS\n --stdin::\n \tRead the object from standard input instead of from a file.\n \n+--stdin-paths::\n+\tRead file names from stdin instead of from the command-line.\n+\n Author\n ------\n Written by Junio C Hamano <junkio@cox.net>\ndiff --git a/hash-object.c b/hash-object.c\nindex 61e7160..1b39162 100644\n--- a/hash-object.c\n+++ b/hash-object.c\n@@ -20,6 +20,7 @@ static void hash_object(const char *path, enum object_type type, int write_objec\n \t\t    ? \"Unable to add %s to database\"\n \t\t    : \"Unable to hash %s\", path);\n \tprintf(\"%s\\n\", sha1_to_hex(sha1));\n+\tmaybe_flush_or_die(stdout, \"hash to stdout\");\n }\n \n static void hash_stdin(const char *type, int write_object)\n@@ -30,8 +31,27 @@ static void hash_stdin(const char *type, int write_object)\n \tprintf(\"%s\\n\", sha1_to_hex(sha1));\n }\n \n+static int hash_stdin_paths(const char *type, int write_objects)\n+{\n+\tstruct strbuf buf, nbuf;\n+\n+\tstrbuf_init(&buf, 0);\n+\tstrbuf_init(&nbuf, 0);\n+\twhile (strbuf_getline(&buf, stdin, '\\n') != EOF) {\n+\t\tif (buf.buf[0] == '\"') {\n+\t\t\tstrbuf_reset(&nbuf);\n+\t\t\tif (unquote_c_style(&nbuf, buf.buf, NULL))\n+\t\t\t\tdie(\"line is badly quoted\");\n+\t\t\tstrbuf_swap(&buf, &nbuf);\n+\t\t}\n+\t\thash_object(buf.buf, type_from_string(type), write_objects);\n+\t}\n+\tstrbuf_release(&buf);\n+\tstrbuf_release(&nbuf);\n+}\n+\n static const char hash_object_usage[] =\n-\"git-hash-object [-t <type>] [-w] [--stdin] <file>...\";\n+\"git-hash-object [ [-t <type>] [-w] [--stdin] <file>... | --stdin-paths < <list-of-paths> ]\";\n \n int main(int argc, char **argv)\n {\n@@ -42,6 +62,7 @@ int main(int argc, char **argv)\n \tint prefix_length = -1;\n \tint no_more_flags = 0;\n \tint hashstdin = 0;\n+\tint stdin_paths = 0;\n \n \tgit_config(git_default_config);\n \n@@ -65,7 +86,19 @@ int main(int argc, char **argv)\n \t\t\t}\n \t\t\telse if (!strcmp(argv[i], \"--help\"))\n \t\t\t\tusage(hash_object_usage);\n+\t\t\telse if (!strcmp(argv[i], \"--stdin-paths\")) {\n+\t\t\t\tif (hashstdin) {\n+\t\t\t\t\terror(\"Can't use --stdin-paths with --stdin\");\n+\t\t\t\t\tusage(hash_object_usage);\n+\t\t\t\t}\n+\t\t\t\tstdin_paths = 1;\n+\n+\t\t\t}\n \t\t\telse if (!strcmp(argv[i], \"--stdin\")) {\n+\t\t\t\tif (stdin_paths) {\n+\t\t\t\t\terror(\"Can't use %s with --stdin-paths\", argv[i]);\n+\t\t\t\t\tusage(hash_object_usage);\n+\t\t\t\t}\n \t\t\t\tif (hashstdin)\n \t\t\t\t\tdie(\"Multiple --stdin arguments are not supported\");\n \t\t\t\thashstdin = 1;\n@@ -76,6 +109,11 @@ int main(int argc, char **argv)\n \t\telse {\n \t\t\tconst char *arg = argv[i];\n \n+\t\t\tif (stdin_paths) {\n+\t\t\t\terror(\"Can't specify files (such as \\\"%s\\\") with --stdin-paths\", arg);\n+\t\t\t\tusage(hash_object_usage);\n+\t\t\t}\n+\n \t\t\tif (hashstdin) {\n \t\t\t\thash_stdin(type, write_object);\n \t\t\t\thashstdin = 0;\n@@ -87,6 +125,10 @@ int main(int argc, char **argv)\n \t\t\tno_more_flags = 1;\n \t\t}\n \t}\n+\n+\tif (stdin_paths)\n+\t\treturn hash_stdin_paths(type, write_object);\n+\n \tif (hashstdin)\n \t\thash_stdin(type, write_object);\n \treturn 0;\ndiff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh\nindex eb54f1f..dbb4129 100755\n--- a/t/t1007-hash-object.sh\n+++ b/t/t1007-hash-object.sh\n@@ -58,6 +58,15 @@ test_expect_success \\\n     \"multiple '--stdin's are rejected\" \\\n     \"test_must_fail git hash-object --stdin --stdin < example\"\n \n+test_expect_success \\\n+    \"Can't use --stdin and --stdin-paths together\" \\\n+    \"test_must_fail git hash-object --stdin --stdin-paths &&\n+     test_must_fail git hash-object --stdin-paths --stdin\"\n+\n+test_expect_success \\\n+    \"Can't pass filenames as arguments with --stdin-paths\" \\\n+    \"test_must_fail git hash-object --stdin-paths hello < example\"\n+\n # Behavior\n \n push_repo\n@@ -104,4 +113,27 @@ for args in \"-w --stdin\" \"--stdin -w\"; do\n     pop_repo\n done\n \n+filenames=\"hello\n+example\"\n+\n+sha1s=\"$hello_sha1\n+$example_sha1\"\n+\n+test_expect_success \\\n+    \"hash two files with names on stdin\" \\\n+    \"test \\\"$sha1s\\\" = \\\"\\$(echo_without_newline \\\"$filenames\\\" | git hash-object --stdin-paths)\\\"\"\n+\n+for args in \"-w --stdin-paths\" \"--stdin-paths -w\"; do\n+    push_repo\n+\n+        test_expect_success \\\n+            \"hash two files with names on stdin and write to database ($args)\" \\\n+            \"test \\\"$sha1s\\\" = \\\"\\$(echo_without_newline \\\"$filenames\\\" | git hash-object $args)\\\"\"\n+\n+        test_blob_exists $hello_sha1\n+        test_blob_exists $example_sha1\n+\n+    pop_repo\n+done\n+\n test_done\n-- \n1.5.5.1.152.g9aeb7\n"},{"id":"75083","messageId":"1208978273-98146-10-git-send-email-aroben@apple.com","threadId":"13232","inReplyTo":"1208978273-98146-9-git-send-email-aroben@apple.com","subject":"[PATCH 09/11] Git.pm: Add command_bidi_pipe and command_close_bidi_pipe","fromName":"Adam Roben","fromEmail":"aroben@apple.com","sentAt":"2008-04-23T19:17:51Z","receivedAt":"2008-04-23T19:17:51Z","isPatch":true,"sender":{"key":"aroben@apple.com","avatar":"https://gravatar.com/avatar/9d3697e1de53890adf241331f4b970bdd2b18962b2ff0b8028ebb00e085807f8?d=mp&s=160"},"body":"command_bidi_pipe hands back the stdin and stdout file handles from the\nexecuted command. command_close_bidi_pipe closes these handles and terminates\nthe process.\n\nSigned-off-by: Adam Roben <aroben@apple.com>\n---\n perl/Git.pm |   56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 56 insertions(+), 0 deletions(-)\n\ndiff --git a/perl/Git.pm b/perl/Git.pm\nindex 2e7f896..d766974 100644\n--- a/perl/Git.pm\n+++ b/perl/Git.pm\n@@ -51,6 +51,7 @@ require Exporter;\n # Methods which can be called as standalone functions as well:\n @EXPORT_OK = qw(command command_oneline command_noisy\n                 command_output_pipe command_input_pipe command_close_pipe\n+                command_bidi_pipe command_close_bidi_pipe\n                 version exec_path hash_object git_cmd_try);\n \n \n@@ -92,6 +93,7 @@ increate nonwithstanding).\n use Carp qw(carp croak); # but croak is bad - throw instead\n use Error qw(:try);\n use Cwd qw(abs_path);\n+use IPC::Open2 qw(open2);\n \n }\n \n@@ -375,6 +377,60 @@ sub command_close_pipe {\n \t_cmd_close($fh, $ctx);\n }\n \n+=item command_bidi_pipe ( COMMAND [, ARGUMENTS... ] )\n+\n+Execute the given C<COMMAND> in the same way as command_output_pipe()\n+does but return both an input pipe filehandle and an output pipe filehandle.\n+\n+The function will return return C<($pid, $pipe_in, $pipe_out, $ctx)>.\n+See C<command_close_bidi_pipe()> for details.\n+\n+=cut\n+\n+sub command_bidi_pipe {\n+\tmy ($pid, $in, $out);\n+\t$pid = open2($in, $out, 'git', @_);\n+\treturn ($pid, $in, $out, join(' ', @_));\n+}\n+\n+=item command_close_bidi_pipe ( PID, PIPE_IN, PIPE_OUT [, CTX] )\n+\n+Close the C<PIPE_IN> and C<PIPE_OUT> as returned from C<command_bidi_pipe()>,\n+checking whether the command finished successfully. The optional C<CTX>\n+argument is required if you want to see the command name in the error message,\n+and it is the fourth value returned by C<command_bidi_pipe()>.  The call idiom\n+is:\n+\n+\tmy ($pid, $in, $out, $ctx) = $r->command_bidi_pipe('cat-file --batch-check');\n+\tprint \"000000000\\n\" $out;\n+\twhile (<$in>) { ... }\n+\t$r->command_close_bidi_pipe($pid, $in, $out, $ctx);\n+\n+Note that you should not rely on whatever actually is in C<CTX>;\n+currently it is simply the command name but in future the context might\n+have more complicated structure.\n+\n+=cut\n+\n+sub command_close_bidi_pipe {\n+\tmy ($pid, $in, $out, $ctx) = @_;\n+\tforeach my $fh ($in, $out) {\n+\t\tunless (close $fh) {\n+\t\t\tif ($!) {\n+\t\t\t\tcarp \"error closing pipe: $!\";\n+\t\t\t} elsif ($? >> 8) {\n+\t\t\t\tthrow Git::Error::Command($ctx, $? >>8);\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\twaitpid $pid, 0;\n+\n+\tif ($? >> 8) {\n+\t\tthrow Git::Error::Command($ctx, $? >>8);\n+\t}\n+}\n+\n \n =item command_noisy ( COMMAND [, ARGUMENTS... ] )\n \n-- \n1.5.5.1.152.g9aeb7\n"},{"id":"75090","messageId":"1208978273-98146-11-git-send-email-aroben@apple.com","threadId":"13232","inReplyTo":"1208978273-98146-10-git-send-email-aroben@apple.com","subject":"[PATCH 10/11] Git.pm: Add hash_and_insert_object and cat_blob","fromName":"Adam Roben","fromEmail":"aroben@apple.com","sentAt":"2008-04-23T19:17:52Z","receivedAt":"2008-04-23T19:17:52Z","isPatch":true,"sender":{"key":"aroben@apple.com","avatar":"https://gravatar.com/avatar/9d3697e1de53890adf241331f4b970bdd2b18962b2ff0b8028ebb00e085807f8?d=mp&s=160"},"body":"These functions are more efficient ways of executing `git hash-object -w` and\n`git cat-file blob` when you are dealing with many files/objects.\n\nSigned-off-by: Adam Roben <aroben@apple.com>\n---\nEric Wong <normalperson@yhbt.net> wrote:\n> > diff --git a/perl/Git.pm b/perl/Git.pm\n> > index 46c5d10..f23edef 100644\n> > --- a/perl/Git.pm\n> > +++ b/perl/Git.pm\n> > @@ -39,6 +39,9 @@ $VERSION = '0.01';\n> >    my $lastrev = $repo->command_oneline( [ 'rev-list', '--all' ],\n> >                                          STDERR => 0 );\n> >  \n> > +  my $sha1 = $repo->hash_and_insert_object('file.txt');\n> > +  my $contents = $repo->cat_blob($sha1);\n> \n> I missed this the first time around.  But I'd rather be able to pass a\n> file handle to cat_blob for writing, instead of returning a potentially\n> huge string in memory.\n\nFixed.\n\n> > @@ -675,6 +677,93 @@ sub hash_object {\n> >  }\n> >  \n> >  \n> > +=item hash_and_insert_object ( FILENAME )\n> > +\n> > +Compute the SHA1 object id of the given C<FILENAME> and add the object to the\n> > +object database.\n> > +\n> > +The function returns the SHA1 hash.\n> > +\n> > +=cut\n> > +\n> > +# TODO: Support for passing FILEHANDLE instead of FILENAME\n> \n> Filenames are fine for this input since they (are/should be) generated\n> by File::Temp and not from an untrusted repo.\n> \n> We should, however assert that the caller of this function\n> isn't using a stupid filename with \"\\n\" in it.\n\nFixed.\n\n> > +sub hash_and_insert_object {\n> > +   my ($self, $filename) = @_;\n> > +\n> > +   $self->_open_hash_and_insert_object_if_needed();\n> > +   my ($in, $out) = ($self->{hash_object_in}, $self->{hash_object_out});\n> > +\n> > +   print $out $filename, \"\\n\";\n> > +   chomp(my $hash = <$in>);\n> > +   return $hash;\n> > +}\n> > +\n> > +sub _open_hash_and_insert_object_if_needed {\n> > +   my ($self) = @_;\n> > +\n> > +   return if defined($self->{hash_object_pid});\n> > +\n> > +   ($self->{hash_object_pid}, $self->{hash_object_in},\n> > +    $self->{hash_object_out}, $self->{hash_object_ctx}) =\n> > +           command_bidi_pipe(qw(hash-object -w --stdin-paths));\n> > +}\n> > +\n> > +sub _close_hash_and_insert_object {\n> > +   my ($self) = @_;\n> > +\n> > +   return unless defined($self->{hash_object_pid});\n> > +\n> > +   my @vars = map { 'hash_object' . $_ } qw(pid in out ctx);\n> \n> It looks like you're missing a '_' in there.\n\nFixed.\n\n> > +=item cat_blob ( SHA1 )\n> > +\n> > +Returns the contents of the blob identified by C<SHA1>.\n> > +\n> > +=cut\n> > +\n> > +sub cat_blob {\n> > +   my ($self, $sha1) = @_;\n> > +\n> > +   $self->_open_cat_blob_if_needed();\n> > +   my ($in, $out) = ($self->{cat_blob_in}, $self->{cat_blob_out});\n> > +\n> > +   print $out $sha1, \"\\n\";\n> > +   chomp(my $size = <$in>);\n> > +\n> > +   my $blob;\n> > +   my $result = read($in, $blob, $size);\n> > +   defined $result or carp $!;\n> > +\n> > +   # Skip past the trailing newline.\n> > +   read($in, my $newline, 1);\n> > +\n> > +   return $blob;\n> > +}\n> \n> However, I'd very much like to be able to pass a file handle to this\n> function.  This should read()/print() to a file handle passed to it in a\n> loop rather than slurping all of $size at once, since the files we're\n> receiving can be huge.\n\nFixed.\n\n> I'd also be happier if we checked that we actually read $size bytes in\n> the loop, and that $newline is actually \"\\n\" to safeguard against bugs\n> in cat-blob.\n\nFixed.\n\n> > +sub _open_cat_blob_if_needed {\n> > +   my ($self) = @_;\n> > +\n> > +   return if defined($self->{cat_blob_pid});\n> > +\n> > +   ($self->{cat_blob_pid}, $self->{cat_blob_in},\n> > +    $self->{cat_blob_out}, $self->{cat_blob_ctx}) =\n> > +           command_bidi_pipe(qw(cat-file blob --stdin));\n> > +}\n> > +\n> > +sub _close_cat_blob {\n> > +   my ($self) = @_;\n> > +\n> > +   return unless defined($self->{cat_blob_pid});\n> > +\n> > +   my @vars = map { 'cat_blob' . $_ } qw(pid in out ctx);\n> \n> It looks like you're missing a '_' here, too.\n\nFixed.\n\n> One more nit, I'm a bit paranoid, but I personally like to die/croak if\n> the result of every print()/syswrite() to make sure the pipe we're\n> writing to didn't die or if there were other error indicators.\n\nFixed.\n\n perl/Git.pm |  152 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 files changed, 150 insertions(+), 2 deletions(-)\n\ndiff --git a/perl/Git.pm b/perl/Git.pm\nindex d766974..6ba8ee5 100644\n--- a/perl/Git.pm\n+++ b/perl/Git.pm\n@@ -39,6 +39,10 @@ $VERSION = '0.01';\n   my $lastrev = $repo->command_oneline( [ 'rev-list', '--all' ],\n                                         STDERR => 0 );\n \n+  my $sha1 = $repo->hash_and_insert_object('file.txt');\n+  my $tempfile = tempfile();\n+  my $size = $repo->cat_blob($sha1, $tempfile);\n+\n =cut\n \n \n@@ -218,7 +222,6 @@ sub repository {\n \tbless $self, $class;\n }\n \n-\n =back\n \n =head1 METHODS\n@@ -734,6 +737,147 @@ sub hash_object {\n }\n \n \n+=item hash_and_insert_object ( FILENAME )\n+\n+Compute the SHA1 object id of the given C<FILENAME> and add the object to the\n+object database.\n+\n+The function returns the SHA1 hash.\n+\n+=cut\n+\n+# TODO: Support for passing FILEHANDLE instead of FILENAME\n+sub hash_and_insert_object {\n+\tmy ($self, $filename) = @_;\n+\n+\tcarp \"Bad filename \\\"$filename\\\"\" if $filename =~ /[\\r\\n]/;\n+\n+\t$self->_open_hash_and_insert_object_if_needed();\n+\tmy ($in, $out) = ($self->{hash_object_in}, $self->{hash_object_out});\n+\n+\tunless (print $out $filename, \"\\n\") {\n+\t\t$self->_close_hash_and_insert_object();\n+\t\tthrow Error::Simple(\"out pipe went bad\");\n+\t}\n+\n+\tchomp(my $hash = <$in>);\n+\tunless (defined($hash)) {\n+\t\t$self->_close_hash_and_insert_object();\n+\t\tthrow Error::Simple(\"in pipe went bad\");\n+\t}\n+\n+\treturn $hash;\n+}\n+\n+sub _open_hash_and_insert_object_if_needed {\n+\tmy ($self) = @_;\n+\n+\treturn if defined($self->{hash_object_pid});\n+\n+\t($self->{hash_object_pid}, $self->{hash_object_in},\n+\t $self->{hash_object_out}, $self->{hash_object_ctx}) =\n+\t\tcommand_bidi_pipe(qw(hash-object -w --stdin-paths));\n+}\n+\n+sub _close_hash_and_insert_object {\n+\tmy ($self) = @_;\n+\n+\treturn unless defined($self->{hash_object_pid});\n+\n+\tmy @vars = map { 'hash_object_' . $_ } qw(pid in out ctx);\n+\n+\tcommand_close_bidi_pipe($self->{@vars});\n+\tdelete $self->{@vars};\n+}\n+\n+=item cat_blob ( SHA1, FILEHANDLE )\n+\n+Prints the contents of the blob identified by C<SHA1> to C<FILEHANDLE> and\n+returns the number of bytes printed.\n+\n+=cut\n+\n+sub cat_blob {\n+\tmy ($self, $sha1, $fh) = @_;\n+\n+\t$self->_open_cat_blob_if_needed();\n+\tmy ($in, $out) = ($self->{cat_blob_in}, $self->{cat_blob_out});\n+\n+\tunless (print $out $sha1, \"\\n\") {\n+\t\t$self->_close_cat_blob();\n+\t\tthrow Error::Simple(\"out pipe went bad\");\n+\t}\n+\n+\tmy $description = <$in>;\n+\tif ($description =~ / missing$/) {\n+\t\tcarp \"$sha1 doesn't exist in the repository\";\n+\t\treturn 0;\n+\t}\n+\n+\tif ($description !~ /^[0-9a-fA-F]{40} \\S+ (\\d+)$/) {\n+\t\tcarp \"Unexpected result returned from git cat-file\";\n+\t\treturn 0;\n+\t}\n+\n+\tmy $size = $1;\n+\n+\tmy $blob;\n+\tmy $bytesRead = 0;\n+\n+\twhile (1) {\n+\t\tmy $bytesLeft = $size - $bytesRead;\n+\t\tlast unless $bytesLeft;\n+\n+\t\tmy $bytesToRead = $bytesLeft < 1024 ? $bytesLeft : 1024;\n+\t\tmy $read = read($in, $blob, $bytesToRead, $bytesRead);\n+\t\tunless (defined($read)) {\n+\t\t\t$self->_close_cat_blob();\n+\t\t\tthrow Error::Simple(\"in pipe went bad\");\n+\t\t}\n+\n+\t\t$bytesRead += $read;\n+\t}\n+\n+\t# Skip past the trailing newline.\n+\tmy $newline;\n+\tmy $read = read($in, $newline, 1);\n+\tunless (defined($read)) {\n+\t\t$self->_close_cat_blob();\n+\t\tthrow Error::Simple(\"in pipe went bad\");\n+\t}\n+\tunless ($read == 1 && $newline eq \"\\n\") {\n+\t\t$self->_close_cat_blob();\n+\t\tthrow Error::Simple(\"didn't find newline after blob\");\n+\t}\n+\n+\tunless (print $fh $blob) {\n+\t\t$self->_close_cat_blob();\n+\t\tthrow Error::Simple(\"couldn't write to passed in filehandle\");\n+\t}\n+\n+\treturn $size;\n+}\n+\n+sub _open_cat_blob_if_needed {\n+\tmy ($self) = @_;\n+\n+\treturn if defined($self->{cat_blob_pid});\n+\n+\t($self->{cat_blob_pid}, $self->{cat_blob_in},\n+\t $self->{cat_blob_out}, $self->{cat_blob_ctx}) =\n+\t\tcommand_bidi_pipe(qw(cat-file --batch));\n+}\n+\n+sub _close_cat_blob {\n+\tmy ($self) = @_;\n+\n+\treturn unless defined($self->{cat_blob_pid});\n+\n+\tmy @vars = map { 'cat_blob_' . $_ } qw(pid in out ctx);\n+\n+\tcommand_close_bidi_pipe($self->{@vars});\n+\tdelete $self->{@vars};\n+}\n \n =back\n \n@@ -951,7 +1095,11 @@ sub _cmd_close {\n }\n \n \n-sub DESTROY { }\n+sub DESTROY {\n+\tmy ($self) = @_;\n+\t$self->_close_hash_and_insert_object();\n+\t$self->_close_cat_blob();\n+}\n \n \n # Pipe implementation for ActiveState Perl.\n-- \n1.5.5.1.152.g9aeb7\n"},{"id":"75092","messageId":"1208978273-98146-12-git-send-email-aroben@apple.com","threadId":"13232","inReplyTo":"1208978273-98146-11-git-send-email-aroben@apple.com","subject":"[PATCH 11/11] git-svn: Speed up fetch","fromName":"Adam Roben","fromEmail":"aroben@apple.com","sentAt":"2008-04-23T19:17:53Z","receivedAt":"2008-04-23T19:17:53Z","isPatch":true,"sender":{"key":"aroben@apple.com","avatar":"https://gravatar.com/avatar/9d3697e1de53890adf241331f4b970bdd2b18962b2ff0b8028ebb00e085807f8?d=mp&s=160"},"body":"We were spending a lot of time forking/execing git-cat-file and\ngit-hash-object. We now maintain a global Git repository object in order to use\nGit.pm's more efficient hash_and_insert_object and cat_blob methods.\n\nSigned-off-by: Adam Roben <aroben@apple.com>\n---\n git-svn.perl |   42 ++++++++++++++++++++----------------------\n 1 files changed, 20 insertions(+), 22 deletions(-)\n\ndiff --git a/git-svn.perl b/git-svn.perl\nindex b864b54..5ef9d23 100755\n--- a/git-svn.perl\n+++ b/git-svn.perl\n@@ -4,7 +4,7 @@\n use warnings;\n use strict;\n use vars qw/\t$AUTHOR $VERSION\n-\t\t$sha1 $sha1_short $_revision\n+\t\t$sha1 $sha1_short $_revision $_repository\n \t\t$_q $_authors %users/;\n $AUTHOR = 'Eric Wong <normalperson@yhbt.net>';\n $VERSION = '@@GIT_VERSION@@';\n@@ -220,6 +220,7 @@ unless ($cmd && $cmd =~ /(?:clone|init|multi-init)$/) {\n \t\t}\n \t\t$ENV{GIT_DIR} = $git_dir;\n \t}\n+\t$_repository = Git->repository(Repository => $ENV{GIT_DIR});\n }\n \n my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd);\n@@ -301,6 +302,7 @@ sub do_git_init_db {\n \t\t\t}\n \t\t}\n \t\tcommand_noisy(@init_db);\n+\t\t$_repository = Git->repository(Repository => \".git\");\n \t}\n \tmy $set;\n \tmy $pfx = \"svn-remote.$Git::SVN::default_repo_id\";\n@@ -317,6 +319,7 @@ sub init_subdir {\n \tmkpath([$repo_path]) unless -d $repo_path;\n \tchdir $repo_path or die \"Couldn't chdir to $repo_path: $!\\n\";\n \t$ENV{GIT_DIR} = '.git';\n+\t$_repository = Git->repository(Repository => $ENV{GIT_DIR});\n }\n \n sub cmd_clone {\n@@ -3010,6 +3013,7 @@ use vars qw/@ISA/;\n use strict;\n use warnings;\n use Carp qw/croak/;\n+use File::Temp qw/tempfile/;\n use IO::File qw//;\n \n # file baton members: path, mode_a, mode_b, pool, fh, blob, base\n@@ -3165,14 +3169,9 @@ sub apply_textdelta {\n \tmy $base = IO::File->new_tmpfile;\n \t$base->autoflush(1);\n \tif ($fb->{blob}) {\n-\t\tdefined (my $pid = fork) or croak $!;\n-\t\tif (!$pid) {\n-\t\t\topen STDOUT, '>&', $base or croak $!;\n-\t\t\tprint STDOUT 'link ' if ($fb->{mode_a} == 120000);\n-\t\t\texec qw/git-cat-file blob/, $fb->{blob} or croak $!;\n-\t\t}\n-\t\twaitpid $pid, 0;\n-\t\tcroak $? if $?;\n+\t\tprint $base 'link ' if ($fb->{mode_a} == 120000);\n+\t\tmy $size = $::_repository->cat_blob($fb->{blob}, $base);\n+\t\tdie \"Failed to read object $fb->{blob}\" unless $size;\n \n \t\tif (defined $exp) {\n \t\t\tseek $base, 0, 0 or croak $!;\n@@ -3213,14 +3212,18 @@ sub close_file {\n \t\t\t\tsysseek($fh, 0, 0) or croak $!;\n \t\t\t}\n \t\t}\n-\t\tdefined(my $pid = open my $out,'-|') or die \"Can't fork: $!\\n\";\n-\t\tif (!$pid) {\n-\t\t\topen STDIN, '<&', $fh or croak $!;\n-\t\t\texec qw/git-hash-object -w --stdin/ or croak $!;\n+\n+\t\tmy ($tmp_fh, $tmp_filename) = File::Temp::tempfile(UNLINK => 1);\n+\t\tmy $result;\n+\t\twhile ($result = sysread($fh, my $string, 1024)) {\n+\t\t\tsyswrite($tmp_fh, $string, $result);\n \t\t}\n-\t\tchomp($hash = do { local $/; <$out> });\n-\t\tclose $out or croak $!;\n+\t\tdefined $result or croak $!;\n+\t\tclose $tmp_fh or croak $!;\n+\n \t\tclose $fh or croak $!;\n+\n+\t\t$hash = $::_repository->hash_and_insert_object($tmp_filename);\n \t\t$hash =~ /^[a-f\\d]{40}$/ or die \"not a sha1: $hash\\n\";\n \t\tclose $fb->{base} or croak $!;\n \t} else {\n@@ -3546,13 +3549,8 @@ sub chg_file {\n \t} elsif ($m->{mode_a} =~ /^120/ && $m->{mode_b} !~ /^120/) {\n \t\t$self->change_file_prop($fbat,'svn:special',undef);\n \t}\n-\tdefined(my $pid = fork) or croak $!;\n-\tif (!$pid) {\n-\t\topen STDOUT, '>&', $fh or croak $!;\n-\t\texec qw/git-cat-file blob/, $m->{sha1_b} or croak $!;\n-\t}\n-\twaitpid $pid, 0;\n-\tcroak $? if $?;\n+\tmy $size = $::_repository->cat_blob($m->{sha1_b}, $fh);\n+\tcroak \"Failed to read object $m->{sha1_b}\" unless $size;\n \t$fh->flush == 0 or croak $!;\n \tseek $fh, 0, 0 or croak $!;\n \n-- \n1.5.5.1.152.g9aeb7\n"},{"id":"75094","messageId":"480F8BB5.2040605@apple.com","threadId":"13232","inReplyTo":"1208978273-98146-1-git-send-email-aroben@apple.com","subject":"Re: Speed up git-svn fetch","fromName":"Adam Roben","fromEmail":"aroben@apple.com","sentAt":"2008-04-23T19:19:17Z","receivedAt":"2008-04-23T19:19:17Z","isPatch":false,"sender":{"key":"aroben@apple.com","avatar":"https://gravatar.com/avatar/9d3697e1de53890adf241331f4b970bdd2b18962b2ff0b8028ebb00e085807f8?d=mp&s=160"},"body":"Adam Roben wrote:\n> I first sent this patch series 6 months ago today [1], then resent it after\n> some comments from Junio, Johannes, and Brian [2].\n\nHere are those links:\n\n[1] http://thread.gmane.org/gmane.comp.version-control.git/62098/focus=62102\n[2] http://thread.gmane.org/gmane.comp.version-control.git/62295/focus=62298\n\n-Adam\n"},{"id":"75173","messageId":"20080425065614.GA5758@untitled","threadId":"13232","inReplyTo":"1208978273-98146-2-git-send-email-aroben@apple.com","subject":"Re: [PATCH 01/11] Add tests for git cat-file","fromName":"Eric Wong","fromEmail":"normalperson@yhbt.net","sentAt":"2008-04-25T06:56:29Z","receivedAt":"2008-04-25T06:56:29Z","isPatch":true,"sender":{"key":"e@80x24.org","avatar":null},"body":"Adam Roben <aroben@apple.com> wrote:\n> \n> Signed-off-by: Adam Roben <aroben@apple.com>\n> ---\n>  t/t1006-cat-file.sh |  101 +++++++++++++++++++++++++++++++++++++++++++++++++++\n>  1 files changed, 101 insertions(+), 0 deletions(-)\n>  create mode 100755 t/t1006-cat-file.sh\n> \n> diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\n> new file mode 100755\n> index 0000000..15741d9\n> --- /dev/null\n> +++ b/t/t1006-cat-file.sh\n> @@ -0,0 +1,101 @@\n> +#!/bin/sh\n> +\n> +test_description='git cat-file'\n> +\n> +. ./test-lib.sh\n> +\n> +function echo_without_newline()\n\nThe \"function \" keyword is a bashism and not needed, this breaks\nmy test run with dash as /bin/sh (same thing in t1007).\n\n> +{\n> +    echo \"$@\\c\"\n\nI guess we have different bash versions/options, because this breaks for\nme in bash (3.1dfsg-8 from Debian etch).  It would need -e to handle to\nhandle escape sequence, but that's a bashism, too.\n\nUse printf \"$@\" here instead.\n\nSo yes, this test was broken in both dash and bash for me without the\nabove fixes.\n\n-- \nEric Wong\n"},{"id":"75174","messageId":"20080425071540.GA3589@untitled","threadId":"13232","inReplyTo":"480F8BB5.2040605@apple.com","subject":"Re: Speed up git-svn fetch","fromName":"Eric Wong","fromEmail":"normalperson@yhbt.net","sentAt":"2008-04-25T07:15:40Z","receivedAt":"2008-04-25T07:15:40Z","isPatch":false,"sender":{"key":"e@80x24.org","avatar":null},"body":"Adam Roben <aroben@apple.com> wrote:\n> Adam Roben wrote:\n> >I first sent this patch series 6 months ago today [1], then resent it after\n> >some comments from Junio, Johannes, and Brian [2].\n> \n> Here are those links:\n> \n> [1] http://thread.gmane.org/gmane.comp.version-control.git/62098/focus=62102\n> [2] http://thread.gmane.org/gmane.comp.version-control.git/62295/focus=62298\n\nThanks for following up on this, the Perl bits look good to me[1].\n\nI commented in a separate email about the shell incompatibilities\nI experienced.\n\nI still the following gcc warnings when building it:\n\n> hash-object.c: In function 'hash_stdin_paths':\n> hash-object.c:43: warning: implicit declaration of function 'unquote_c_style'\n\nTrivial fix:\n--- a/hash-object.c\n+++ b/hash-object.c\n@@ -6,6 +6,7 @@\n  */\n #include \"cache.h\"\n #include \"blob.h\"\n+#include \"quote.h\"\n\n> hash-object.c:51: warning: control reaches end of non-void function\n\nThis can probably just be a void.\n\n> builtin-cat-file.c: In function 'cmd_cat_file':\n> builtin-cat-file.c:224: warning: suggest parentheses around && within ||\n\nUgh, you added long (>80 char) lines to this and I'm having trouble\nfollowing it.  I believe the git (like Linux) coding style calls for 80\nchar lines unless there is really no other way[2].  This is also a\nproblem for me in some of the shell tests, too.\n\n> builtin-cat-file.c:151: warning: 'contents' may be used uninitialized in this function\n\ngcc isn't smart here.\n\n\n[1] - disclaimer, I'm not in my best mental state at this point in the\nnight/morning so maybe some things have slipped :)\n\n[2] - Looking at your email address, I notice you work for a company that\npushes widescreen monitors, but I remain firmly on the side of dead tree\npublishers whom I believe got line-wrapping right centuries ago :)\n\n-- \nEric Wong\n"},{"id":"75187","messageId":"7vve25q0ao.fsf@gitster.siamese.dyndns.org","threadId":"13232","inReplyTo":"1208978273-98146-2-git-send-email-aroben@apple.com","subject":"Re: [PATCH 01/11] Add tests for git cat-file","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-04-25T18:03:43Z","receivedAt":"2008-04-25T18:03:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Adam Roben <aroben@apple.com> writes:\n\n> Signed-off-by: Adam Roben <aroben@apple.com>\n> ---\n>  t/t1006-cat-file.sh |  101 +++++++++++++++++++++++++++++++++++++++++++++++++++\n>  1 files changed, 101 insertions(+), 0 deletions(-)\n>  create mode 100755 t/t1006-cat-file.sh\n>\n> diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\n> new file mode 100755\n> index 0000000..15741d9\n> --- /dev/null\n> +++ b/t/t1006-cat-file.sh\n> @@ -0,0 +1,101 @@\n> +#!/bin/sh\n> +\n> +test_description='git cat-file'\n> +\n> +. ./test-lib.sh\n> +\n> +function echo_without_newline()\n> +{\n> +    echo \"$@\\c\"\n> +}\n\n\"function \" noiseword is unnecessary and unportable.\n\necho \"\\c\" is unportable.  Traditional trick to do this is to pipe it to tr\nto strip \"\\010\".\n\nWhen you see \"<anything>$@<anything>\" in shell scripts, you usually do not\nmean it, but instead mean \"<anything>$*<anything>\".  It does not make\ndifference if you always feed a single parameter, but it is a good habit\nto get into.\n\n> +function maybe_remove_timestamp()\n> +{\n> +    if test -z \"$2\"; then\n> +        echo_without_newline \"$1\"\n> +    else\n> +        echo_without_newline \"$1\" | sed -e 's/ [0-9]\\{10\\} [+-][0-9]\\{4\\}$//'\n\nWe tend to avoid \\{num\\| for portability.  Do you really need them here?\n\n> +function run_tests()\n> +{\n> +    type=$1\n> +    sha1=$2\n> +    size=$3\n> +    content=$4\n> +    pretty_content=$5\n> +    no_timestamp=$6\n> +\n> +    test_expect_success \\\n> +        \"$type exists\" \\\n> +        \"git cat-file -e $sha1\"\n> +    test_expect_success \\\n> +        \"Type of $type is correct\" \\\n> +        \"test $type = \\\"\\$(git cat-file -t $sha1)\\\"\"\n\nIt is quite hard to read with backslashes inside dq.  Please use single\nquote pairs without excess backslashes when possible.\n"},{"id":"75186","messageId":"7vod7xq0a6.fsf@gitster.siamese.dyndns.org","threadId":"13232","inReplyTo":"1208978273-98146-4-git-send-email-aroben@apple.com","subject":"Re: [PATCH 03/11] git-cat-file: Make option parsing a little more flexible","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-04-25T18:04:01Z","receivedAt":"2008-04-25T18:04:01Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Adam Roben <aroben@apple.com> writes:\n\n> This will make it easier to add newer options later.\n\nHmm.  Makes one wonder \"why not parse-options\"?...\n"},{"id":"75188","messageId":"7vk5ilq05l.fsf@gitster.siamese.dyndns.org","threadId":"13232","inReplyTo":"20080425065614.GA5758@untitled","subject":"Re: [PATCH 01/11] Add tests for git cat-file","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-04-25T18:06:46Z","receivedAt":"2008-04-25T18:06:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Eric Wong <normalperson@yhbt.net> writes:\n\n> Adam Roben <aroben@apple.com> wrote:\n>> \n>> Signed-off-by: Adam Roben <aroben@apple.com>\n>> ---\n>>  t/t1006-cat-file.sh |  101 +++++++++++++++++++++++++++++++++++++++++++++++++++\n>>  1 files changed, 101 insertions(+), 0 deletions(-)\n>>  create mode 100755 t/t1006-cat-file.sh\n>> \n>> diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh\n>> new file mode 100755\n>> index 0000000..15741d9\n>> --- /dev/null\n>> +++ b/t/t1006-cat-file.sh\n>> @@ -0,0 +1,101 @@\n>> +#!/bin/sh\n>> +\n>> +test_description='git cat-file'\n>> +\n>> +. ./test-lib.sh\n>> +\n>> +function echo_without_newline()\n>\n> The \"function \" keyword is a bashism and not needed, this breaks\n> my test run with dash as /bin/sh (same thing in t1007).\n>\n>> +{\n>> +    echo \"$@\\c\"\n>\n> I guess we have different bash versions/options, because this breaks for\n> me in bash (3.1dfsg-8 from Debian etch).  It would need -e to handle to\n> handle escape sequence, but that's a bashism, too.\n>\n> Use printf \"$@\" here instead.\n\nLooking at the callers, I do not think you want that.  I would suggest\nsomething defensive like:\n\n\tprintf '%s' \"$*\"\n"},{"id":"76180","messageId":"7v63tshr2z.fsf@gitster.siamese.dyndns.org","threadId":"13232","inReplyTo":"7vve25q0ao.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH 01/11] Add tests for git cat-file","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-05-06T06:41:40Z","receivedAt":"2008-05-06T06:41:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Adam Roben <aroben@apple.com> writes:\n>\n>> Signed-off-by: Adam Roben <aroben@apple.com>\n>> ---\n>>  t/t1006-cat-file.sh |  101 +++++++++++++++++++++++++++++++++++++++++++++++++++\n\nI generally do not fix other people's mess, but every once in a while I\ntry to be nice, so I've fixed up various portability glitches in this test\nscript file and queued the early part to 'pu'.\n\nHowever the t1007 test that is added later in the series have the same\nkind of breakage.  I'll discard the rest of the series for now, but\nhopefully the interdiff between the submitted and committed t1006 would\nserve as a template to fix t1007.\n"}]}