{"thread":{"id":"36509","subject":"[PATCH v6 1/5] patch-id: make it stable against hunk reordering","startedAt":"2014-04-27T18:15:44Z","lastAt":"2014-05-06T18:20:51Z","messageCount":8,"participants":["Michael S. Tsirkin","Junio C Hamano","Jonathan Nieder"],"isPatch":true,"patchVersion":6,"patchTotal":5},"messages":[{"id":"239808","messageId":"1398622524-26207-1-git-send-email-mst@redhat.com","threadId":"36509","inReplyTo":null,"subject":"[PATCH v6 1/5] patch-id: make it stable against hunk reordering","fromName":"Michael S. Tsirkin","fromEmail":"mst@redhat.com","sentAt":"2014-04-27T18:15:44Z","receivedAt":"2014-04-27T18:15:44Z","isPatch":true,"sender":{"key":"mst@kernel.org","avatar":null},"body":"Patch id changes if users reorder file diffs that make up a patch.\n\nAs the result is functionally equivalent, a different patch id is\nsurprising to many users.\nIn particular, reordering files using diff -O is helpful to make patches\nmore readable (e.g. API header diff before implementation diff).\n\nAdd an option to change patch-id behaviour making it stable against\nthese kinds of patch change:\ncalculate SHA1 hash for each hunk separately and sum all hashes\n(using a symmetrical sum) to get patch id\n\nWe use a 20byte sum and not xor - since xor would give 0 output\nfor patches that have two identical diffs, which isn't all that\nunlikely (e.g. append the same line in two places).\n\nThe new behaviour is enabled\n- when patchid.stable is true\n- when --stable flag is present\n\nUsing a new flag --unstable or setting patchid.stable to false force\nthe historical behaviour.\n\nIn the documentation, clarify that patch ID can now be a sum of hashes,\nnot a hash.\nDocument how command line and config options affect the\nbehaviour.\n\nSigned-off-by: Michael S. Tsirkin <mst@redhat.com>\n---\n builtin/patch-id.c             | 74 +++++++++++++++++++++++++++++++++---------\n Documentation/git-patch-id.txt | 37 ++++++++++++++++++---\n 2 files changed, 91 insertions(+), 20 deletions(-)\n\ndiff --git a/builtin/patch-id.c b/builtin/patch-id.c\nindex 3cfe02d..77db873 100644\n--- a/builtin/patch-id.c\n+++ b/builtin/patch-id.c\n@@ -1,17 +1,14 @@\n #include \"builtin.h\"\n \n-static void flush_current_id(int patchlen, unsigned char *id, git_SHA_CTX *c)\n+static void flush_current_id(int patchlen, unsigned char *id, unsigned char *result)\n {\n-\tunsigned char result[20];\n \tchar name[50];\n \n \tif (!patchlen)\n \t\treturn;\n \n-\tgit_SHA1_Final(result, c);\n \tmemcpy(name, sha1_to_hex(id), 41);\n \tprintf(\"%s %s\\n\", sha1_to_hex(result), name);\n-\tgit_SHA1_Init(c);\n }\n \n static int remove_space(char *line)\n@@ -56,10 +53,31 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after)\n \treturn 1;\n }\n \n-static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx, struct strbuf *line_buf)\n+static void flush_one_hunk(unsigned char *result, git_SHA_CTX *ctx)\n+{\n+\tunsigned char hash[20];\n+\tunsigned short carry = 0;\n+\tint i;\n+\n+\tgit_SHA1_Final(hash, ctx);\n+\tgit_SHA1_Init(ctx);\n+\t/* 20-byte sum, with carry */\n+\tfor (i = 0; i < 20; ++i) {\n+\t\tcarry += result[i] + hash[i];\n+\t\tresult[i] = carry;\n+\t\tcarry >>= 8;\n+\t}\n+}\n+\n+static int get_one_patchid(unsigned char *next_sha1, unsigned char *result,\n+\t\t\t   struct strbuf *line_buf, int stable)\n {\n \tint patchlen = 0, found_next = 0;\n \tint before = -1, after = -1;\n+\tgit_SHA_CTX ctx;\n+\n+\tgit_SHA1_Init(&ctx);\n+\thashclr(result);\n \n \twhile (strbuf_getwholeline(line_buf, stdin, '\\n') != EOF) {\n \t\tchar *line = line_buf->buf;\n@@ -107,6 +125,8 @@ static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx, struct st\n \t\t\t\tbreak;\n \n \t\t\t/* Else we're parsing another header.  */\n+\t\t\tif (stable)\n+\t\t\t\tflush_one_hunk(result, &ctx);\n \t\t\tbefore = after = -1;\n \t\t}\n \n@@ -119,39 +139,63 @@ static int get_one_patchid(unsigned char *next_sha1, git_SHA_CTX *ctx, struct st\n \t\t/* Compute the sha without whitespace */\n \t\tlen = remove_space(line);\n \t\tpatchlen += len;\n-\t\tgit_SHA1_Update(ctx, line, len);\n+\t\tgit_SHA1_Update(&ctx, line, len);\n \t}\n \n \tif (!found_next)\n \t\thashclr(next_sha1);\n \n+\tflush_one_hunk(result, &ctx);\n+\n \treturn patchlen;\n }\n \n-static void generate_id_list(void)\n+static void generate_id_list(int stable)\n {\n-\tunsigned char sha1[20], n[20];\n-\tgit_SHA_CTX ctx;\n+\tunsigned char sha1[20], n[20], result[20];\n \tint patchlen;\n \tstruct strbuf line_buf = STRBUF_INIT;\n \n-\tgit_SHA1_Init(&ctx);\n \thashclr(sha1);\n \twhile (!feof(stdin)) {\n-\t\tpatchlen = get_one_patchid(n, &ctx, &line_buf);\n-\t\tflush_current_id(patchlen, sha1, &ctx);\n+\t\tpatchlen = get_one_patchid(n, result, &line_buf, stable);\n+\t\tflush_current_id(patchlen, sha1, result);\n \t\thashcpy(sha1, n);\n \t}\n \tstrbuf_release(&line_buf);\n }\n \n-static const char patch_id_usage[] = \"git patch-id < patch\";\n+static const char patch_id_usage[] = \"git patch-id [--stable | --unstable] < patch\";\n+\n+static int git_patch_id_config(const char *var, const char *value, void *cb)\n+{\n+\tint *stable = cb;\n+\n+\tif (!strcmp(var, \"patchid.stable\")) {\n+\t\t*stable = git_config_bool(var, value);\n+\t\treturn 0;\n+\t}\n+\n+\treturn git_default_config(var, value, cb);\n+}\n \n int cmd_patch_id(int argc, const char **argv, const char *prefix)\n {\n-\tif (argc != 1)\n+\tint stable = -1;\n+\n+\tgit_config(git_patch_id_config, &stable);\n+\n+\t/* If nothing is set, default to unstable. */\n+\tif (stable < 0)\n+\t\tstable = 0;\n+\n+\tif (argc == 2 && !strcmp(argv[1], \"--stable\"))\n+\t\tstable = 1;\n+\telse if (argc == 2 && !strcmp(argv[1], \"--unstable\"))\n+\t\tstable = 0;\n+\telse if (argc != 1)\n \t\tusage(patch_id_usage);\n \n-\tgenerate_id_list();\n+\tgenerate_id_list(stable);\n \treturn 0;\n }\ndiff --git a/Documentation/git-patch-id.txt b/Documentation/git-patch-id.txt\nindex 312c3b1..fa562d3 100644\n--- a/Documentation/git-patch-id.txt\n+++ b/Documentation/git-patch-id.txt\n@@ -8,14 +8,14 @@ git-patch-id - Compute unique ID for a patch\n SYNOPSIS\n --------\n [verse]\n-'git patch-id' < <patch>\n+'git patch-id' [--stable | --unstable] < <patch>\n \n DESCRIPTION\n -----------\n-A \"patch ID\" is nothing but a SHA-1 of the diff associated with a patch, with\n-whitespace and line numbers ignored.  As such, it's \"reasonably stable\", but at\n-the same time also reasonably unique, i.e., two patches that have the same \"patch\n-ID\" are almost guaranteed to be the same thing.\n+A \"patch ID\" is nothing but a sum of SHA-1 of the file diffs associated with a\n+patch, with whitespace and line numbers ignored.  As such, it's \"reasonably\n+stable\", but at the same time also reasonably unique, i.e., two patches that\n+have the same \"patch ID\" are almost guaranteed to be the same thing.\n \n IOW, you can use this thing to look for likely duplicate commits.\n \n@@ -27,6 +27,33 @@ This can be used to make a mapping from patch ID to commit ID.\n \n OPTIONS\n -------\n+\n+--stable::\n+\tUse a \"stable\" sum of hashes as the patch ID. With this option:\n+\t - Reordering file diffs that make up a patch does not affect the ID.\n+\t   In particular, two patches produced by comparing the same two trees\n+\t   with two different settings for \"-O<orderfile>\" result in the same\n+\t   patch ID signature, thereby allowing the computed result to be used\n+\t   as a key to index some meta-information about the change between\n+\t   the two trees;\n+\t \n+\t - Result is different from the value produced by git 1.9 and older\n+\t   or produced when an \"unstable\" hash (see --unstable below) is\n+\t   configured - even when used on a diff output taken without any use\n+\t   of \"-O<orderfile>\", thereby making existing databases storing such\n+\t   \"unstable\" or historical patch-ids unusable.\n+\n+\tThis is the default if patchid.stable is set to true.\n+\n+--unstable::\n+\tUse an \"unstable\" hash as the patch ID. With this option,\n+\tthe result produced is compatible with the patch-id value produced\n+\tby git 1.9 and older.  Users with pre-existing databases storing\n+\tpatch-ids produced by git 1.9 and older (who do not deal with reordered\n+\tpatches) may want to use this option.\n+\n+\tThis is the default.\n+\n <patch>::\n \tThe diff to create the ID of.\n \n-- \nMST\n"},{"id":"239809","messageId":"1398622524-26207-2-git-send-email-mst@redhat.com","threadId":"36509","inReplyTo":"1398622524-26207-1-git-send-email-mst@redhat.com","subject":"[PATCH v6 2/5] test: add test_write_lines helper","fromName":"Michael S. Tsirkin","fromEmail":"mst@redhat.com","sentAt":"2014-04-27T18:15:47Z","receivedAt":"2014-04-27T18:15:47Z","isPatch":true,"sender":{"key":"mst@kernel.org","avatar":null},"body":"API and implementation as suggested by Junio.\n\nSigned-off-by: Michael S. Tsirkin <mst@redhat.com>\n---\n t/README                | 22 ++++++++++++++++++++++\n t/test-lib-functions.sh |  5 +++++\n 2 files changed, 27 insertions(+)\n\ndiff --git a/t/README b/t/README\nindex caeeb9d..2d6232f 100644\n--- a/t/README\n+++ b/t/README\n@@ -596,6 +596,28 @@ library for your script to use.\n \t\t...\n \t'\n \n+ - test_write_lines <text>\n+\n+   Split <text> to white-space separated words and write it out on standard\n+   output, one word per line.\n+   Useful to prepare multi-line files in a compact form.\n+\n+   Example:\n+\n+\ttest_write_lines \"a b c d e f g\" >foo\n+\n+   Is a more compact equivalent of:\n+\tcat >foo <<-EOF\n+\ta\n+\tb\n+\tc\n+\td\n+\te\n+\tf\n+\tg\n+\tEOF\n+\n+\n  - test_pause\n \n \tThis command is useful for writing and debugging tests and must be\ndiff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh\nindex 158e10a..f581535 100644\n--- a/t/test-lib-functions.sh\n+++ b/t/test-lib-functions.sh\n@@ -717,6 +717,11 @@ test_ln_s_add () {\n \tfi\n }\n \n+# This function writes out its parameters, one per line\n+test_write_lines () {\n+\tprintf \"%s\\n\" \"$@\"\n+}\n+\n perl () {\n \tcommand \"$PERL_PATH\" \"$@\"\n }\n-- \nMST\n"},{"id":"239812","messageId":"1398622524-26207-3-git-send-email-mst@redhat.com","threadId":"36509","inReplyTo":"1398622524-26207-1-git-send-email-mst@redhat.com","subject":"[PATCH v6 3/5] patch-id-test: test stable and unstable behaviour","fromName":"Michael S. Tsirkin","fromEmail":"mst@redhat.com","sentAt":"2014-04-27T18:15:51Z","receivedAt":"2014-04-27T18:15:51Z","isPatch":true,"sender":{"key":"mst@kernel.org","avatar":null},"body":"Verify that patch ID supports an algorithm\nthat is stable against diff split and reordering.\n\nSigned-off-by: Michael S. Tsirkin <mst@redhat.com>\n---\n t/t4204-patch-id.sh | 102 ++++++++++++++++++++++++++++++++++++++++++++++------\n 1 file changed, 91 insertions(+), 11 deletions(-)\n\ndiff --git a/t/t4204-patch-id.sh b/t/t4204-patch-id.sh\nindex d2c930d..7732370 100755\n--- a/t/t4204-patch-id.sh\n+++ b/t/t4204-patch-id.sh\n@@ -5,27 +5,44 @@ test_description='git patch-id'\n . ./test-lib.sh\n \n test_expect_success 'setup' '\n-\ttest_commit initial foo a &&\n-\ttest_commit first foo b &&\n-\tgit checkout -b same HEAD^ &&\n-\ttest_commit same-msg foo b &&\n-\tgit checkout -b notsame HEAD^ &&\n-\ttest_commit notsame-msg foo c\n+\tas=\"a a a a a a a a\" && # eight a\n+\ttest_write_lines $as >foo &&\n+\ttest_write_lines $as >bar &&\n+\tgit add foo bar &&\n+\tgit commit -a -m initial &&\n+\ttest_write_lines $as b >foo &&\n+\ttest_write_lines $as b >bar &&\n+\tgit commit -a -m first &&\n+\tgit checkout -b same master &&\n+\tgit commit --amend -m same-msg &&\n+\tgit checkout -b notsame master &&\n+\techo c >foo &&\n+\techo c >bar &&\n+\tgit commit --amend -a -m notsame-msg &&\n+\ttest_write_lines bar foo >bar-then-foo &&\n+\ttest_write_lines foo bar >foo-then-bar\n '\n \n test_expect_success 'patch-id output is well-formed' '\n-\tgit log -p -1 | git patch-id > output &&\n+\tgit log -p -1 | git patch-id >output &&\n \tgrep \"^[a-f0-9]\\{40\\} $(git rev-parse HEAD)$\" output\n '\n \n+#calculate patch id. Make sure output is not empty.\n calc_patch_id () {\n-\tgit patch-id |\n-\t\tsed \"s# .*##\" > patch-id_\"$1\"\n+\tname=\"$1\"\n+\tshift\n+\tgit patch-id \"$@\" |\n+\tsed \"s/ .*//\" >patch-id_\"$name\" &&\n+\ttest_line_count -gt 0 patch-id_\"$name\"\n+}\n+\n+get_top_diff () {\n+\tgit log -p -1 \"$@\" -O bar-then-foo --\n }\n \n get_patch_id () {\n-\tgit log -p -1 \"$1\" | git patch-id |\n-\t\tsed \"s# .*##\" > patch-id_\"$1\"\n+\tget_top_diff \"$1\" | calc_patch_id \"$@\"\n }\n \n test_expect_success 'patch-id detects equality' '\n@@ -56,6 +73,69 @@ test_expect_success 'whitespace is irrelevant in footer' '\n \ttest_cmp patch-id_master patch-id_same\n '\n \n+cmp_patch_id () {\n+\tif\n+\t\ttest \"$1\" = \"relevant\"\n+\tthen\n+\t\t! test_cmp patch-id_\"$2\" patch-id_\"$3\"\n+\telse\n+\t\ttest_cmp patch-id_\"$2\" patch-id_\"$3\"\n+\tfi\n+}\n+\n+test_patch_id_file_order () {\n+\trelevant=\"$1\"\n+\tshift\n+\tname=\"order-${1}-$relevant\"\n+\tshift\n+\tget_top_diff \"master\" | calc_patch_id \"$name\" \"$@\" &&\n+\tgit checkout same &&\n+\tgit format-patch -1 --stdout -O foo-then-bar |\n+\t\tcalc_patch_id \"ordered-$name\" \"$@\" &&\n+\tcmp_patch_id $relevant \"$name\" \"ordered-$name\"\n+\t\t\n+}\n+\n+# combined test for options: add more tests here to make them\n+# run with all options\n+test_patch_id () {\n+\ttest_patch_id_file_order \"$@\"\n+}\n+\n+# small tests with detailed diagnostic for basic options.\n+test_expect_success 'file order is irrelevant with --stable' '\n+\ttest_patch_id_file_order irrelevant --stable --stable\n+'\n+\n+test_expect_success 'file order is relevant with --unstable' '\n+\ttest_patch_id_file_order relevant --unstable --unstable\n+'\n+\n+#Now test various option combinations.\n+test_expect_success 'default is unstable' '\n+\ttest_patch_id relevant default\n+'\n+\n+test_expect_success 'patchid.stable = true is stable' '\n+\ttest_config patchid.stable true &&\n+\ttest_patch_id irrelevant patchid.stable=true\n+'\n+\n+test_expect_success 'patchid.stable = false is unstable' '\n+\ttest_config patchid.stable false &&\n+\ttest_patch_id relevant patchid.stable=false\n+'\n+\n+test_expect_success '--unstable overrides patchid.stable = true' '\n+\ttest_config patchid.stable true &&\n+\ttest_patch_id relevant patchid.stable=true--unstable --unstable\n+'\n+\n+test_expect_success '--stable overrides patchid.stable = false' '\n+\ttest_config patchid.stable false &&\n+\ttest_patch_id irrelevant patchid.stable=false--stable --stable\n+'\n+\n test_expect_success 'patch-id supports git-format-patch MIME output' '\n \tget_patch_id master &&\n \tgit checkout same &&\n-- \nMST\n"},{"id":"239811","messageId":"1398622524-26207-4-git-send-email-mst@redhat.com","threadId":"36509","inReplyTo":"1398622524-26207-1-git-send-email-mst@redhat.com","subject":"[PATCH v6 4/5] patch-id: change default to stable","fromName":"Michael S. Tsirkin","fromEmail":"mst@redhat.com","sentAt":"2014-04-27T18:15:54Z","receivedAt":"2014-04-27T18:15:54Z","isPatch":true,"sender":{"key":"mst@kernel.org","avatar":null},"body":"--stable has been the default in 'next' for a few weeks with no ill\neffects.\nChange the default to that so that users don't have to remember to\nenable it.\n\nUpdate documentation to match behaviour change.\n\nSigned-off-by: Michael S. Tsirkin <mst@redhat.com>\n---\n builtin/patch-id.c             | 4 ++--\n Documentation/git-patch-id.txt | 4 ++--\n 2 files changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/patch-id.c b/builtin/patch-id.c\nindex 77db873..e11a6a7 100644\n--- a/builtin/patch-id.c\n+++ b/builtin/patch-id.c\n@@ -185,9 +185,9 @@ int cmd_patch_id(int argc, const char **argv, const char *prefix)\n \n \tgit_config(git_patch_id_config, &stable);\n \n-\t/* If nothing is set, default to unstable. */\n+\t/* If nothing is set, default to stable. */\n \tif (stable < 0)\n-\t\tstable = 0;\n+\t\tstable = 1;\n \n \tif (argc == 2 && !strcmp(argv[1], \"--stable\"))\n \t\tstable = 1;\ndiff --git a/Documentation/git-patch-id.txt b/Documentation/git-patch-id.txt\nindex fa562d3..1e2ca75 100644\n--- a/Documentation/git-patch-id.txt\n+++ b/Documentation/git-patch-id.txt\n@@ -43,7 +43,7 @@ OPTIONS\n \t   of \"-O<orderfile>\", thereby making existing databases storing such\n \t   \"unstable\" or historical patch-ids unusable.\n \n-\tThis is the default if patchid.stable is set to true.\n+\tThis is the default.\n \n --unstable::\n \tUse an \"unstable\" hash as the patch ID. With this option,\n@@ -52,7 +52,7 @@ OPTIONS\n \tpatch-ids produced by git 1.9 and older (who do not deal with reordered\n \tpatches) may want to use this option.\n \n-\tThis is the default.\n+\tThis is the default if patchid.stable is set to false.\n \n <patch>::\n \tThe diff to create the ID of.\n-- \nMST\n"},{"id":"239810","messageId":"1398622524-26207-5-git-send-email-mst@redhat.com","threadId":"36509","inReplyTo":"1398622524-26207-1-git-send-email-mst@redhat.com","subject":"[PATCH v6 5/5] t4204-patch-id.sh: default is now stable","fromName":"Michael S. Tsirkin","fromEmail":"mst@redhat.com","sentAt":"2014-04-27T18:15:59Z","receivedAt":"2014-04-27T18:15:59Z","isPatch":true,"sender":{"key":"mst@kernel.org","avatar":null},"body":"update test to match behaviour change\n\nSigned-off-by: Michael S. Tsirkin <mst@redhat.com>\n---\n t/t4204-patch-id.sh | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/t/t4204-patch-id.sh b/t/t4204-patch-id.sh\nindex 7732370..a8b0c2c 100755\n--- a/t/t4204-patch-id.sh\n+++ b/t/t4204-patch-id.sh\n@@ -112,8 +112,8 @@ test_expect_success 'file order is relevant with --unstable' '\n '\n \n #Now test various option combinations.\n-test_expect_success 'default is unstable' '\n-\ttest_patch_id relevant default\n+test_expect_success 'default is stable' '\n+\ttest_patch_id irrelevant default\n '\n \n test_expect_success 'patchid.stable = true is stable' '\n-- \nMST\n"},{"id":"240206","messageId":"xmqqr44fizdd.fsf@gitster.dls.corp.google.com","threadId":"36509","inReplyTo":"1398622524-26207-1-git-send-email-mst@redhat.com","subject":"Re: [PATCH v6 1/5] patch-id: make it stable against hunk reordering","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-04-29T21:45:18Z","receivedAt":"2014-04-29T21:45:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thanks.\n\nI'll revert the merge of the previous round to 'next' and then queue\nthis series instead.\n"},{"id":"240766","messageId":"20140505235143.GU9218@google.com","threadId":"36509","inReplyTo":"1398622524-26207-2-git-send-email-mst@redhat.com","subject":"[PATCH] test doc: test_write_lines does not split its arguments","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2014-05-05T23:51:43Z","receivedAt":"2014-05-05T23:51:43Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"test_write_lines carefully quotes its arguments as \"$@\", so\n\n\ttest_write_lines \"a b\" c\n\nwrites two lines as requested, not three.\n\nSigned-off-by: Jonathan Nieder <jrnieder@gmail.com>\n---\nHi,\n\nMichael S. Tsirkin wrote:\n\n> +++ b/t/README\n> @@ -596,6 +596,28 @@ library for your script to use.\n> +\ttest_write_lines \"a b c d e f g\" >foo\n> +\n> +   Is a more compact equivalent of:\n> +\tcat >foo <<-EOF\n> +\ta\n> +\tb\n[...]\n> +++ b/t/test-lib-functions.sh\n> @@ -717,6 +717,11 @@ test_ln_s_add () {\n>  \tfi\n>  }\n>  \n> +# This function writes out its parameters, one per line\n> +test_write_lines () {\n> +\tprintf \"%s\\n\" \"$@\"\n> +}\n\nHow about this patch?\n\nThanks,\nJonathan\n\n t/README | 7 +++----\n 1 file changed, 3 insertions(+), 4 deletions(-)\n\ndiff --git a/t/README b/t/README\nindex 2d6232f..8a9d499 100644\n--- a/t/README\n+++ b/t/README\n@@ -596,15 +596,14 @@ library for your script to use.\n \t\t...\n \t'\n \n- - test_write_lines <text>\n+ - test_write_lines <lines>\n \n-   Split <text> to white-space separated words and write it out on standard\n-   output, one word per line.\n+   Write <lines> on standard output, one line per argument.\n    Useful to prepare multi-line files in a compact form.\n \n    Example:\n \n-\ttest_write_lines \"a b c d e f g\" >foo\n+\ttest_write_lines a b c d e f g >foo\n \n    Is a more compact equivalent of:\n \tcat >foo <<-EOF\n-- \n1.9.1.423.g4596e3a\n"},{"id":"240796","messageId":"20140506182051.GA10333@redhat.com","threadId":"36509","inReplyTo":"20140505235143.GU9218@google.com","subject":"Re: [PATCH] test doc: test_write_lines does not split its arguments","fromName":"Michael S. Tsirkin","fromEmail":"mst@redhat.com","sentAt":"2014-05-06T18:20:51Z","receivedAt":"2014-05-06T18:20:51Z","isPatch":true,"sender":{"key":"mst@kernel.org","avatar":null},"body":"On Mon, May 05, 2014 at 04:51:43PM -0700, Jonathan Nieder wrote:\n> test_write_lines carefully quotes its arguments as \"$@\", so\n> \n> \ttest_write_lines \"a b\" c\n> \n> writes two lines as requested, not three.\n> \n> Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>\n\nAcked-by: Michael S. Tsirkin <mst@redhat.com>\n\n> ---\n> Hi,\n> \n> Michael S. Tsirkin wrote:\n> \n> > +++ b/t/README\n> > @@ -596,6 +596,28 @@ library for your script to use.\n> > +\ttest_write_lines \"a b c d e f g\" >foo\n> > +\n> > +   Is a more compact equivalent of:\n> > +\tcat >foo <<-EOF\n> > +\ta\n> > +\tb\n> [...]\n> > +++ b/t/test-lib-functions.sh\n> > @@ -717,6 +717,11 @@ test_ln_s_add () {\n> >  \tfi\n> >  }\n> >  \n> > +# This function writes out its parameters, one per line\n> > +test_write_lines () {\n> > +\tprintf \"%s\\n\" \"$@\"\n> > +}\n> \n> How about this patch?\n> \n> Thanks,\n> Jonathan\n> \n>  t/README | 7 +++----\n>  1 file changed, 3 insertions(+), 4 deletions(-)\n> \n> diff --git a/t/README b/t/README\n> index 2d6232f..8a9d499 100644\n> --- a/t/README\n> +++ b/t/README\n> @@ -596,15 +596,14 @@ library for your script to use.\n>  \t\t...\n>  \t'\n>  \n> - - test_write_lines <text>\n> + - test_write_lines <lines>\n>  \n> -   Split <text> to white-space separated words and write it out on standard\n> -   output, one word per line.\n> +   Write <lines> on standard output, one line per argument.\n>     Useful to prepare multi-line files in a compact form.\n>  \n>     Example:\n>  \n> -\ttest_write_lines \"a b c d e f g\" >foo\n> +\ttest_write_lines a b c d e f g >foo\n>  \n>     Is a more compact equivalent of:\n>  \tcat >foo <<-EOF\n> -- \n> 1.9.1.423.g4596e3a\n"}]}