{"thread":{"id":"37171","subject":"[PATCH v7 5/9] contrib: add convert-grafts-to-replace-refs.sh","startedAt":"2014-07-19T15:01:06Z","lastAt":"2014-07-21T20:09:59Z","messageCount":11,"participants":["Christian Couder","Junio C Hamano"],"isPatch":true,"patchVersion":7,"patchTotal":9},"messages":[{"id":"246374","messageId":"20140719145951.9564.61331.chriscool@tuxfamily.org","threadId":"37171","inReplyTo":null,"subject":"[PATCH v7 0/9] Add --graft option to git replace","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-07-19T15:01:06Z","receivedAt":"2014-07-19T15:01:06Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Here is a small series to implement:\n\n        git replace [-f] --graft <commit> [<parent>...]\n\nThis patch series goes on top of the patch series that\nimplements --edit.\n\nThe changes since v6, thanks to Junio, are:\n\n- the patch that adds for_each_mergetag() is not part of the\n  series any more; it is managed by Junio in another dedicated\n  series\n\n- replace_parents() iterates over all the argv it is passed,\n  not just from argv[1] to argv[argc - 1], in patch 2/9\n\n- in t6050-replace.sh, commit_buffer_contains_parents() has\n  been simplified, in patch 3/9\n\n- in t6050-replace.sh, there is now a space between function\n  names and (), in patch 3/9\n\n- in t6050-replace.sh, there has been a refactoring regarding\n  how commit parenthood is tested, in patchs 3/9 and 7/9; we\n  only use commit_has_parents() that now tests everything\n\n- in t6050-replace.sh, commit_buffer_contains_parents()\n  compares the 'expected' and 'actual' files in the right\n  order, in patch 3/9\n\nWe still error if the replacement commit that would be created\nwould be the same as the existing one, as we already do when\nusing --edit. If people care, I suggest they send a patch to\nchange both --graft and --edit at the same time. \n\nChristian Couder (9):\n  replace: cleanup redirection style in tests\n  replace: add --graft option\n  replace: add test for --graft\n  Documentation: replace: add --graft option\n  contrib: add convert-grafts-to-replace-refs.sh\n  replace: remove signature when using --graft\n  replace: add test for --graft with signed commit\n  replace: check mergetags when using --graft\n  replace: add test for --graft with a mergetag\n\n Documentation/git-replace.txt             |  10 +++\n builtin/replace.c                         | 126 ++++++++++++++++++++++++++-\n commit.c                                  |  34 ++++++++\n commit.h                                  |   2 +\n contrib/convert-grafts-to-replace-refs.sh |  28 ++++++\n t/t6050-replace.sh                        | 139 ++++++++++++++++++++++++------\n 6 files changed, 313 insertions(+), 26 deletions(-)\n create mode 100755 contrib/convert-grafts-to-replace-refs.sh\n\n-- \n2.0.0.421.g786a89d.dirty\n"},{"id":"246380","messageId":"20140719150116.9564.84555.chriscool@tuxfamily.org","threadId":"37171","inReplyTo":"20140719145951.9564.61331.chriscool@tuxfamily.org","subject":"[PATCH v7 1/9] replace: cleanup redirection style in tests","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-07-19T15:01:07Z","receivedAt":"2014-07-19T15:01:07Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n t/t6050-replace.sh | 48 ++++++++++++++++++++++++------------------------\n 1 file changed, 24 insertions(+), 24 deletions(-)\n\ndiff --git a/t/t6050-replace.sh b/t/t6050-replace.sh\nindex 68b3cb2..fb07ad2 100755\n--- a/t/t6050-replace.sh\n+++ b/t/t6050-replace.sh\n@@ -27,36 +27,36 @@ HASH6=\n HASH7=\n \n test_expect_success 'set up buggy branch' '\n-     echo \"line 1\" >> hello &&\n-     echo \"line 2\" >> hello &&\n-     echo \"line 3\" >> hello &&\n-     echo \"line 4\" >> hello &&\n+     echo \"line 1\" >>hello &&\n+     echo \"line 2\" >>hello &&\n+     echo \"line 3\" >>hello &&\n+     echo \"line 4\" >>hello &&\n      add_and_commit_file hello \"4 lines\" &&\n      HASH1=$(git rev-parse --verify HEAD) &&\n-     echo \"line BUG\" >> hello &&\n-     echo \"line 6\" >> hello &&\n-     echo \"line 7\" >> hello &&\n-     echo \"line 8\" >> hello &&\n+     echo \"line BUG\" >>hello &&\n+     echo \"line 6\" >>hello &&\n+     echo \"line 7\" >>hello &&\n+     echo \"line 8\" >>hello &&\n      add_and_commit_file hello \"4 more lines with a BUG\" &&\n      HASH2=$(git rev-parse --verify HEAD) &&\n-     echo \"line 9\" >> hello &&\n-     echo \"line 10\" >> hello &&\n+     echo \"line 9\" >>hello &&\n+     echo \"line 10\" >>hello &&\n      add_and_commit_file hello \"2 more lines\" &&\n      HASH3=$(git rev-parse --verify HEAD) &&\n-     echo \"line 11\" >> hello &&\n+     echo \"line 11\" >>hello &&\n      add_and_commit_file hello \"1 more line\" &&\n      HASH4=$(git rev-parse --verify HEAD) &&\n-     sed -e \"s/BUG/5/\" hello > hello.new &&\n+     sed -e \"s/BUG/5/\" hello >hello.new &&\n      mv hello.new hello &&\n      add_and_commit_file hello \"BUG fixed\" &&\n      HASH5=$(git rev-parse --verify HEAD) &&\n-     echo \"line 12\" >> hello &&\n-     echo \"line 13\" >> hello &&\n+     echo \"line 12\" >>hello &&\n+     echo \"line 13\" >>hello &&\n      add_and_commit_file hello \"2 more lines\" &&\n      HASH6=$(git rev-parse --verify HEAD) &&\n-     echo \"line 14\" >> hello &&\n-     echo \"line 15\" >> hello &&\n-     echo \"line 16\" >> hello &&\n+     echo \"line 14\" >>hello &&\n+     echo \"line 15\" >>hello &&\n+     echo \"line 16\" >>hello &&\n      add_and_commit_file hello \"again 3 more lines\" &&\n      HASH7=$(git rev-parse --verify HEAD)\n '\n@@ -95,7 +95,7 @@ test_expect_success 'tag replaced commit' '\n '\n \n test_expect_success '\"git fsck\" works' '\n-     git fsck master > fsck_master.out &&\n+     git fsck master >fsck_master.out &&\n      grep \"dangling commit $R\" fsck_master.out &&\n      grep \"dangling tag $(cat .git/refs/tags/mytag)\" fsck_master.out &&\n      test -z \"$(git fsck)\"\n@@ -217,14 +217,14 @@ test_expect_success 'fetch branch with replacement' '\n      (\n \t  cd clone_dir &&\n \t  git fetch origin refs/heads/tofetch:refs/heads/parallel3 &&\n-\t  git log --pretty=oneline parallel3 > output.txt &&\n+\t  git log --pretty=oneline parallel3 >output.txt &&\n \t  ! grep $PARA3 output.txt &&\n-\t  git show $PARA3 > para3.txt &&\n+\t  git show $PARA3 >para3.txt &&\n \t  grep \"A U Thor\" para3.txt &&\n \t  git fetch origin \"refs/replace/*:refs/replace/*\" &&\n-\t  git log --pretty=oneline parallel3 > output.txt &&\n+\t  git log --pretty=oneline parallel3 >output.txt &&\n \t  grep $PARA3 output.txt &&\n-\t  git show $PARA3 > para3.txt &&\n+\t  git show $PARA3 >para3.txt &&\n \t  grep \"O Thor\" para3.txt\n      )\n '\n@@ -302,7 +302,7 @@ test_expect_success 'test --format medium' '\n \t\techo \"$PARA3 -> $S\" &&\n \t\techo \"$MYTAG -> $HASH1\"\n \t} | sort >expected &&\n-\tgit replace -l --format medium | sort > actual &&\n+\tgit replace -l --format medium | sort >actual &&\n \ttest_cmp expected actual\n '\n \n@@ -314,7 +314,7 @@ test_expect_success 'test --format long' '\n \t\techo \"$PARA3 (commit) -> $S (commit)\" &&\n \t\techo \"$MYTAG (tag) -> $HASH1 (commit)\"\n \t} | sort >expected &&\n-\tgit replace --format=long | sort > actual &&\n+\tgit replace --format=long | sort >actual &&\n \ttest_cmp expected actual\n '\n \n-- \n2.0.0.421.g786a89d.dirty\n"},{"id":"246381","messageId":"20140719150116.9564.16480.chriscool@tuxfamily.org","threadId":"37171","inReplyTo":"20140719145951.9564.61331.chriscool@tuxfamily.org","subject":"[PATCH v7 2/9] replace: add --graft option","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-07-19T15:01:08Z","receivedAt":"2014-07-19T15:01:08Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"The usage string for this option is:\n\ngit replace [-f] --graft <commit> [<parent>...]\n\nFirst we create a new commit that is the same as <commit>\nexcept that its parents are [<parents>...]\n\nThen we create a replace ref that replace <commit> with\nthe commit we just created.\n\nWith this new option, it should be straightforward to\nconvert grafts to replace refs.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/replace.c | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 73 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 1bb491d..7459359 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -17,6 +17,7 @@\n static const char * const git_replace_usage[] = {\n \tN_(\"git replace [-f] <object> <replacement>\"),\n \tN_(\"git replace [-f] --edit <object>\"),\n+\tN_(\"git replace [-f] --graft <commit> [<parent>...]\"),\n \tN_(\"git replace -d <object>...\"),\n \tN_(\"git replace [--format=<format>] [-l [<pattern>]]\"),\n \tNULL\n@@ -294,6 +295,66 @@ static int edit_and_replace(const char *object_ref, int force)\n \treturn replace_object_sha1(object_ref, old, \"replacement\", new, force);\n }\n \n+static void replace_parents(struct strbuf *buf, int argc, const char **argv)\n+{\n+\tstruct strbuf new_parents = STRBUF_INIT;\n+\tconst char *parent_start, *parent_end;\n+\tint i;\n+\n+\t/* find existing parents */\n+\tparent_start = buf->buf;\n+\tparent_start += 46; /* \"tree \" + \"hex sha1\" + \"\\n\" */\n+\tparent_end = parent_start;\n+\n+\twhile (starts_with(parent_end, \"parent \"))\n+\t\tparent_end += 48; /* \"parent \" + \"hex sha1\" + \"\\n\" */\n+\n+\t/* prepare new parents */\n+\tfor (i = 0; i < argc; i++) {\n+\t\tunsigned char sha1[20];\n+\t\tif (get_sha1(argv[i], sha1) < 0)\n+\t\t\tdie(_(\"Not a valid object name: '%s'\"), argv[i]);\n+\t\tlookup_commit_or_die(sha1, argv[i]);\n+\t\tstrbuf_addf(&new_parents, \"parent %s\\n\", sha1_to_hex(sha1));\n+\t}\n+\n+\t/* replace existing parents with new ones */\n+\tstrbuf_splice(buf, parent_start - buf->buf, parent_end - parent_start,\n+\t\t      new_parents.buf, new_parents.len);\n+\n+\tstrbuf_release(&new_parents);\n+}\n+\n+static int create_graft(int argc, const char **argv, int force)\n+{\n+\tunsigned char old[20], new[20];\n+\tconst char *old_ref = argv[0];\n+\tstruct commit *commit;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tconst char *buffer;\n+\tunsigned long size;\n+\n+\tif (get_sha1(old_ref, old) < 0)\n+\t\tdie(_(\"Not a valid object name: '%s'\"), old_ref);\n+\tcommit = lookup_commit_or_die(old, old_ref);\n+\n+\tbuffer = get_commit_buffer(commit, &size);\n+\tstrbuf_add(&buf, buffer, size);\n+\tunuse_commit_buffer(commit, buffer);\n+\n+\treplace_parents(&buf, argc - 1, &argv[1]);\n+\n+\tif (write_sha1_file(buf.buf, buf.len, commit_type, new))\n+\t\tdie(_(\"could not write replacement commit for: '%s'\"), old_ref);\n+\n+\tstrbuf_release(&buf);\n+\n+\tif (!hashcmp(old, new))\n+\t\treturn error(\"new commit is the same as the old one: '%s'\", sha1_to_hex(old));\n+\n+\treturn replace_object_sha1(old_ref, old, \"replacement\", new, force);\n+}\n+\n int cmd_replace(int argc, const char **argv, const char *prefix)\n {\n \tint force = 0;\n@@ -303,12 +364,14 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n \t\tMODE_LIST,\n \t\tMODE_DELETE,\n \t\tMODE_EDIT,\n+\t\tMODE_GRAFT,\n \t\tMODE_REPLACE\n \t} cmdmode = MODE_UNSPECIFIED;\n \tstruct option options[] = {\n \t\tOPT_CMDMODE('l', \"list\", &cmdmode, N_(\"list replace refs\"), MODE_LIST),\n \t\tOPT_CMDMODE('d', \"delete\", &cmdmode, N_(\"delete replace refs\"), MODE_DELETE),\n \t\tOPT_CMDMODE('e', \"edit\", &cmdmode, N_(\"edit existing object\"), MODE_EDIT),\n+\t\tOPT_CMDMODE('g', \"graft\", &cmdmode, N_(\"change a commit's parents\"), MODE_GRAFT),\n \t\tOPT_BOOL('f', \"force\", &force, N_(\"replace the ref if it exists\")),\n \t\tOPT_STRING(0, \"format\", &format, N_(\"format\"), N_(\"use this format\")),\n \t\tOPT_END()\n@@ -325,7 +388,10 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n \t\tusage_msg_opt(\"--format cannot be used when not listing\",\n \t\t\t      git_replace_usage, options);\n \n-\tif (force && cmdmode != MODE_REPLACE && cmdmode != MODE_EDIT)\n+\tif (force &&\n+\t    cmdmode != MODE_REPLACE &&\n+\t    cmdmode != MODE_EDIT &&\n+\t    cmdmode != MODE_GRAFT)\n \t\tusage_msg_opt(\"-f only makes sense when writing a replacement\",\n \t\t\t      git_replace_usage, options);\n \n@@ -348,6 +414,12 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n \t\t\t\t      git_replace_usage, options);\n \t\treturn edit_and_replace(argv[0], force);\n \n+\tcase MODE_GRAFT:\n+\t\tif (argc < 1)\n+\t\t\tusage_msg_opt(\"-g needs at least one argument\",\n+\t\t\t\t      git_replace_usage, options);\n+\t\treturn create_graft(argc, argv, force);\n+\n \tcase MODE_LIST:\n \t\tif (argc > 1)\n \t\t\tusage_msg_opt(\"only one pattern can be given with -l\",\n-- \n2.0.0.421.g786a89d.dirty\n"},{"id":"246377","messageId":"20140719150116.9564.48264.chriscool@tuxfamily.org","threadId":"37171","inReplyTo":"20140719145951.9564.61331.chriscool@tuxfamily.org","subject":"[PATCH v7 3/9] replace: add test for --graft","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-07-19T15:01:09Z","receivedAt":"2014-07-19T15:01:09Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n t/t6050-replace.sh | 45 ++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 44 insertions(+), 1 deletion(-)\n\ndiff --git a/t/t6050-replace.sh b/t/t6050-replace.sh\nindex fb07ad2..f854dae 100755\n--- a/t/t6050-replace.sh\n+++ b/t/t6050-replace.sh\n@@ -8,7 +8,7 @@ exec </dev/null\n \n . ./test-lib.sh\n \n-add_and_commit_file()\n+add_and_commit_file ()\n {\n     _file=\"$1\"\n     _msg=\"$2\"\n@@ -18,6 +18,38 @@ add_and_commit_file()\n     git commit --quiet -m \"$_file: $_msg\"\n }\n \n+commit_buffer_contains_parents ()\n+{\n+    git cat-file commit \"$1\" >payload &&\n+    sed -n -e '/^$/q' -e '/^parent /p' <payload >actual &&\n+    shift &&\n+    for _parent\n+    do\n+\techo \"parent $_parent\"\n+    done >expected &&\n+    test_cmp expected actual\n+}\n+\n+commit_peeling_shows_parents ()\n+{\n+    _parent_number=1\n+    _commit=\"$1\"\n+    shift &&\n+    for _parent\n+    do\n+\t_found=$(git rev-parse --verify $_commit^$_parent_number) || return 1\n+\ttest \"$_found\" = \"$_parent\" || return 1\n+\t_parent_number=$(( $_parent_number + 1 ))\n+    done &&\n+    test_must_fail git rev-parse --verify $_commit^$_parent_number\n+}\n+\n+commit_has_parents ()\n+{\n+    commit_buffer_contains_parents \"$@\" &&\n+    commit_peeling_shows_parents \"$@\"\n+}\n+\n HASH1=\n HASH2=\n HASH3=\n@@ -351,4 +383,15 @@ test_expect_success 'replace ref cleanup' '\n \ttest -z \"$(git replace)\"\n '\n \n+test_expect_success '--graft with and without already replaced object' '\n+\ttest $(git log --oneline | wc -l) = 7 &&\n+\tgit replace --graft $HASH5 &&\n+\ttest $(git log --oneline | wc -l) = 3 &&\n+\tcommit_has_parents $HASH5 &&\n+\ttest_must_fail git replace --graft $HASH5 $HASH4 $HASH3 &&\n+\tgit replace --force -g $HASH5 $HASH4 $HASH3 &&\n+\tcommit_has_parents $HASH5 $HASH4 $HASH3 &&\n+\tgit replace -d $HASH5\n+'\n+\n test_done\n-- \n2.0.0.421.g786a89d.dirty\n"},{"id":"246376","messageId":"20140719150116.9564.70642.chriscool@tuxfamily.org","threadId":"37171","inReplyTo":"20140719145951.9564.61331.chriscool@tuxfamily.org","subject":"[PATCH v7 4/9] Documentation: replace: add --graft option","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-07-19T15:01:10Z","receivedAt":"2014-07-19T15:01:10Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/git-replace.txt | 8 ++++++++\n 1 file changed, 8 insertions(+)\n\ndiff --git a/Documentation/git-replace.txt b/Documentation/git-replace.txt\nindex 61461b9..491875e 100644\n--- a/Documentation/git-replace.txt\n+++ b/Documentation/git-replace.txt\n@@ -10,6 +10,7 @@ SYNOPSIS\n [verse]\n 'git replace' [-f] <object> <replacement>\n 'git replace' [-f] --edit <object>\n+'git replace' [-f] --graft <commit> [<parent>...]\n 'git replace' -d <object>...\n 'git replace' [--format=<format>] [-l [<pattern>]]\n \n@@ -73,6 +74,13 @@ OPTIONS\n \tnewly created object. See linkgit:git-var[1] for details about\n \thow the editor will be chosen.\n \n+--graft <commit> [<parent>...]::\n+\tCreate a graft commit. A new commit is created with the same\n+\tcontent as <commit> except that its parents will be\n+\t[<parent>...] instead of <commit>'s parents. A replacement ref\n+\tis then created to replace <commit> with the newly created\n+\tcommit.\n+\n -l <pattern>::\n --list <pattern>::\n \tList replace refs for objects that match the given pattern (or\n-- \n2.0.0.421.g786a89d.dirty\n"},{"id":"246373","messageId":"20140719150116.9564.16002.chriscool@tuxfamily.org","threadId":"37171","inReplyTo":"20140719145951.9564.61331.chriscool@tuxfamily.org","subject":"[PATCH v7 5/9] contrib: add convert-grafts-to-replace-refs.sh","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-07-19T15:01:11Z","receivedAt":"2014-07-19T15:01:11Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"This patch adds into contrib/ an example script to convert\ngrafts from an existing grafts file into replace refs using\nthe new --graft option of \"git replace\".\n\nWhile at it let's mention this new script in the\n\"git replace\" documentation for the --graft option.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/git-replace.txt             |  4 +++-\n contrib/convert-grafts-to-replace-refs.sh | 28 ++++++++++++++++++++++++++++\n 2 files changed, 31 insertions(+), 1 deletion(-)\n create mode 100755 contrib/convert-grafts-to-replace-refs.sh\n\ndiff --git a/Documentation/git-replace.txt b/Documentation/git-replace.txt\nindex 491875e..e1be2e6 100644\n--- a/Documentation/git-replace.txt\n+++ b/Documentation/git-replace.txt\n@@ -79,7 +79,9 @@ OPTIONS\n \tcontent as <commit> except that its parents will be\n \t[<parent>...] instead of <commit>'s parents. A replacement ref\n \tis then created to replace <commit> with the newly created\n-\tcommit.\n+\tcommit. See contrib/convert-grafts-to-replace-refs.sh for an\n+\texample script based on this option that can convert grafts to\n+\treplace refs.\n \n -l <pattern>::\n --list <pattern>::\ndiff --git a/contrib/convert-grafts-to-replace-refs.sh b/contrib/convert-grafts-to-replace-refs.sh\nnew file mode 100755\nindex 0000000..0cbc917\n--- /dev/null\n+++ b/contrib/convert-grafts-to-replace-refs.sh\n@@ -0,0 +1,28 @@\n+#!/bin/sh\n+\n+# You should execute this script in the repository where you\n+# want to convert grafts to replace refs.\n+\n+GRAFTS_FILE=\"${GIT_DIR:-.git}/info/grafts\"\n+\n+. $(git --exec-path)/git-sh-setup\n+\n+test -f \"$GRAFTS_FILE\" || die \"Could not find graft file: '$GRAFTS_FILE'\"\n+\n+grep '^[^# ]' \"$GRAFTS_FILE\" |\n+while read definition\n+do\n+\tif test -n \"$definition\"\n+\tthen\n+\t\techo \"Converting: $definition\"\n+\t\tgit replace --graft $definition ||\n+\t\t\tdie \"Conversion failed for: $definition\"\n+\tfi\n+done\n+\n+mv \"$GRAFTS_FILE\" \"$GRAFTS_FILE.bak\" ||\n+\tdie \"Could not rename '$GRAFTS_FILE' to '$GRAFTS_FILE.bak'\"\n+\n+echo \"Success!\"\n+echo \"All the grafts in '$GRAFTS_FILE' have been converted to replace refs!\"\n+echo \"The grafts file '$GRAFTS_FILE' has been renamed: '$GRAFTS_FILE.bak'\"\n-- \n2.0.0.421.g786a89d.dirty\n"},{"id":"246382","messageId":"20140719150116.9564.7190.chriscool@tuxfamily.org","threadId":"37171","inReplyTo":"20140719145951.9564.61331.chriscool@tuxfamily.org","subject":"[PATCH v7 6/9] replace: remove signature when using --graft","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-07-19T15:01:12Z","receivedAt":"2014-07-19T15:01:12Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"It could be misleading to keep a signature in a\nreplacement commit, so let's remove it.\n\nNote that there should probably be a way to sign\nthe replacement commit created when using --graft,\nbut this can be dealt with in another commit or\npatch series.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replace.c |  5 +++++\n commit.c          | 34 ++++++++++++++++++++++++++++++++++\n commit.h          |  2 ++\n 3 files changed, 41 insertions(+)\n\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 7459359..52f73ce 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -344,6 +344,11 @@ static int create_graft(int argc, const char **argv, int force)\n \n \treplace_parents(&buf, argc - 1, &argv[1]);\n \n+\tif (remove_signature(&buf)) {\n+\t\twarning(_(\"the original commit '%s' has a gpg signature.\"), old_ref);\n+\t\twarning(_(\"the signature will be removed in the replacement commit!\"));\n+\t}\n+\n \tif (write_sha1_file(buf.buf, buf.len, commit_type, new))\n \t\tdie(_(\"could not write replacement commit for: '%s'\"), old_ref);\n \ndiff --git a/commit.c b/commit.c\nindex 0fd04ae..27c3c05 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -1177,6 +1177,40 @@ int parse_signed_commit(const struct commit *commit,\n \treturn saw_signature;\n }\n \n+int remove_signature(struct strbuf *buf)\n+{\n+\tconst char *line = buf->buf;\n+\tconst char *tail = buf->buf + buf->len;\n+\tint in_signature = 0;\n+\tconst char *sig_start = NULL;\n+\tconst char *sig_end = NULL;\n+\n+\twhile (line < tail) {\n+\t\tconst char *next = memchr(line, '\\n', tail - line);\n+\t\tnext = next ? next + 1 : tail;\n+\n+\t\tif (in_signature && line[0] == ' ')\n+\t\t\tsig_end = next;\n+\t\telse if (starts_with(line, gpg_sig_header) &&\n+\t\t\t line[gpg_sig_header_len] == ' ') {\n+\t\t\tsig_start = line;\n+\t\t\tsig_end = next;\n+\t\t\tin_signature = 1;\n+\t\t} else {\n+\t\t\tif (*line == '\\n')\n+\t\t\t\t/* dump the whole remainder of the buffer */\n+\t\t\t\tnext = tail;\n+\t\t\tin_signature = 0;\n+\t\t}\n+\t\tline = next;\n+\t}\n+\n+\tif (sig_start)\n+\t\tstrbuf_remove(buf, sig_start - buf->buf, sig_end - sig_start);\n+\n+\treturn sig_start != NULL;\n+}\n+\n static void handle_signed_tag(struct commit *parent, struct commit_extra_header ***tail)\n {\n \tstruct merge_remote_desc *desc;\ndiff --git a/commit.h b/commit.h\nindex b695aa4..c81ba85 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -332,6 +332,8 @@ struct commit *get_merge_parent(const char *name);\n \n extern int parse_signed_commit(const struct commit *commit,\n \t\t\t       struct strbuf *message, struct strbuf *signature);\n+extern int remove_signature(struct strbuf *buf);\n+\n extern void print_commit_list(struct commit_list *list,\n \t\t\t      const char *format_cur,\n \t\t\t      const char *format_last);\n-- \n2.0.0.421.g786a89d.dirty\n"},{"id":"246379","messageId":"20140719150116.9564.96311.chriscool@tuxfamily.org","threadId":"37171","inReplyTo":"20140719145951.9564.61331.chriscool@tuxfamily.org","subject":"[PATCH v7 7/9] replace: add test for --graft with signed commit","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-07-19T15:01:13Z","receivedAt":"2014-07-19T15:01:13Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n t/t6050-replace.sh | 24 ++++++++++++++++++++++++\n 1 file changed, 24 insertions(+)\n\ndiff --git a/t/t6050-replace.sh b/t/t6050-replace.sh\nindex f854dae..cebab63 100755\n--- a/t/t6050-replace.sh\n+++ b/t/t6050-replace.sh\n@@ -7,6 +7,7 @@ test_description='Tests replace refs functionality'\n exec </dev/null\n \n . ./test-lib.sh\n+. \"$TEST_DIRECTORY/lib-gpg.sh\"\n \n add_and_commit_file ()\n {\n@@ -394,4 +395,27 @@ test_expect_success '--graft with and without already replaced object' '\n \tgit replace -d $HASH5\n '\n \n+test_expect_success GPG 'set up a signed commit' '\n+\techo \"line 17\" >>hello &&\n+\techo \"line 18\" >>hello &&\n+\tgit add hello &&\n+\ttest_tick &&\n+\tgit commit --quiet -S -m \"hello: 2 more lines in a signed commit\" &&\n+\tHASH8=$(git rev-parse --verify HEAD) &&\n+\tgit verify-commit $HASH8\n+'\n+\n+test_expect_success GPG '--graft with a signed commit' '\n+\tgit cat-file commit $HASH8 >orig &&\n+\tgit replace --graft $HASH8 &&\n+\tgit cat-file commit $HASH8 >repl &&\n+\tcommit_has_parents $HASH8 &&\n+\ttest_must_fail git verify-commit $HASH8 &&\n+\tsed -n -e \"/^tree /p\" -e \"/^author /p\" -e \"/^committer /p\" orig >expected &&\n+\techo >>expected &&\n+\tsed -e \"/^$/q\" repl >actual &&\n+\ttest_cmp expected actual &&\n+\tgit replace -d $HASH8\n+'\n+\n test_done\n-- \n2.0.0.421.g786a89d.dirty\n"},{"id":"246378","messageId":"20140719150116.9564.55503.chriscool@tuxfamily.org","threadId":"37171","inReplyTo":"20140719145951.9564.61331.chriscool@tuxfamily.org","subject":"[PATCH v7 8/9] replace: check mergetags when using --graft","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-07-19T15:01:14Z","receivedAt":"2014-07-19T15:01:14Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"When using --graft, with a mergetag in the original\ncommit, we should check that the commit pointed to by\nthe mergetag is still a parent of then new commit we\ncreate, otherwise the mergetag could be misleading.\n\nIf the commit pointed to by the mergetag is no more\na parent of the new commit, we could remove the\nmergetag, but in this case there is a good chance\nthat the title or other elements of the commit might\nalso be misleading. So let's just error out and\nsuggest to use --edit instead on the commit.\n\nSigned-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n builtin/replace.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 47 insertions(+)\n\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 52f73ce..d29026f 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -13,6 +13,7 @@\n #include \"refs.h\"\n #include \"parse-options.h\"\n #include \"run-command.h\"\n+#include \"tag.h\"\n \n static const char * const git_replace_usage[] = {\n \tN_(\"git replace [-f] <object> <replacement>\"),\n@@ -325,6 +326,50 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv)\n \tstrbuf_release(&new_parents);\n }\n \n+struct check_mergetag_data {\n+\tint argc;\n+\tconst char **argv;\n+};\n+\n+static void check_one_mergetag(struct commit *commit,\n+\t\t\t       struct commit_extra_header *extra,\n+\t\t\t       void *data)\n+{\n+\tstruct check_mergetag_data *mergetag_data = (struct check_mergetag_data *)data;\n+\tconst char *ref = mergetag_data->argv[0];\n+\tunsigned char tag_sha1[20];\n+\tstruct tag *tag;\n+\tint i;\n+\n+\thash_sha1_file(extra->value, extra->len, typename(OBJ_TAG), tag_sha1);\n+\ttag = lookup_tag(tag_sha1);\n+\tif (!tag)\n+\t\tdie(_(\"bad mergetag in commit '%s'\"), ref);\n+\tif (parse_tag_buffer(tag, extra->value, extra->len))\n+\t\tdie(_(\"malformed mergetag in commit '%s'\"), ref);\n+\n+\t/* iterate over new parents */\n+\tfor (i = 1; i < mergetag_data->argc; i++) {\n+\t\tunsigned char sha1[20];\n+\t\tif (get_sha1(mergetag_data->argv[i], sha1) < 0)\n+\t\t\tdie(_(\"Not a valid object name: '%s'\"), mergetag_data->argv[i]);\n+\t\tif (!hashcmp(tag->tagged->sha1, sha1))\n+\t\t\treturn; /* found */\n+\t}\n+\n+\tdie(_(\"original commit '%s' contains mergetag '%s' that is discarded; \"\n+\t      \"use --edit instead of --graft\"), ref, sha1_to_hex(tag_sha1));\n+}\n+\n+static void check_mergetags(struct commit *commit, int argc, const char **argv)\n+{\n+\tstruct check_mergetag_data mergetag_data;\n+\n+\tmergetag_data.argc = argc;\n+\tmergetag_data.argv = argv;\n+\tfor_each_mergetag(check_one_mergetag, commit, &mergetag_data);\n+}\n+\n static int create_graft(int argc, const char **argv, int force)\n {\n \tunsigned char old[20], new[20];\n@@ -349,6 +394,8 @@ static int create_graft(int argc, const char **argv, int force)\n \t\twarning(_(\"the signature will be removed in the replacement commit!\"));\n \t}\n \n+\tcheck_mergetags(commit, argc, argv);\n+\n \tif (write_sha1_file(buf.buf, buf.len, commit_type, new))\n \t\tdie(_(\"could not write replacement commit for: '%s'\"), old_ref);\n \n-- \n2.0.0.421.g786a89d.dirty\n"},{"id":"246375","messageId":"20140719150116.9564.10746.chriscool@tuxfamily.org","threadId":"37171","inReplyTo":"20140719145951.9564.61331.chriscool@tuxfamily.org","subject":"[PATCH v7 9/9] replace: add test for --graft with a mergetag","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-07-19T15:01:15Z","receivedAt":"2014-07-19T15:01:15Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n---\n t/t6050-replace.sh | 22 ++++++++++++++++++++++\n 1 file changed, 22 insertions(+)\n\ndiff --git a/t/t6050-replace.sh b/t/t6050-replace.sh\nindex cebab63..4d5a25e 100755\n--- a/t/t6050-replace.sh\n+++ b/t/t6050-replace.sh\n@@ -418,4 +418,26 @@ test_expect_success GPG '--graft with a signed commit' '\n \tgit replace -d $HASH8\n '\n \n+test_expect_success GPG 'set up a merge commit with a mergetag' '\n+\tgit reset --hard HEAD &&\n+\tgit checkout -b test_branch HEAD~2 &&\n+\techo \"line 1 from test branch\" >>hello &&\n+\techo \"line 2 from test branch\" >>hello &&\n+\tgit add hello &&\n+\ttest_tick &&\n+\tgit commit -m \"hello: 2 more lines from a test branch\" &&\n+\tHASH9=$(git rev-parse --verify HEAD) &&\n+\tgit tag -s -m \"tag for testing with a mergetag\" test_tag HEAD &&\n+\tgit checkout master &&\n+\tgit merge -s ours test_tag &&\n+\tHASH10=$(git rev-parse --verify HEAD) &&\n+\tgit cat-file commit $HASH10 | grep \"^mergetag object\"\n+'\n+\n+test_expect_success GPG '--graft on a commit with a mergetag' '\n+\ttest_must_fail git replace --graft $HASH10 $HASH8^1 &&\n+\tgit replace --graft $HASH10 $HASH8^1 $HASH9 &&\n+\tgit replace -d $HASH10\n+'\n+\n test_done\n-- \n2.0.0.421.g786a89d.dirty\n"},{"id":"246479","messageId":"xmqqfvhuiico.fsf@gitster.dls.corp.google.com","threadId":"37171","inReplyTo":"20140719145951.9564.61331.chriscool@tuxfamily.org","subject":"Re: [PATCH v7 0/9] Add --graft option to git replace","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-07-21T20:09:59Z","receivedAt":"2014-07-21T20:09:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thanks, will replace and let's move it to 'next' this week.\n"}]}