{"thread":{"id":"36807","subject":"[PATCH v2 1/4] replace: add --graft option","startedAt":"2014-06-01T15:10:33Z","lastAt":"2014-06-02T19:16:02Z","messageCount":7,"participants":["Christian Couder","Eric Sunshine"],"isPatch":true,"patchVersion":2,"patchTotal":4},"messages":[{"id":"243094","messageId":"20140601150409.15428.27017.chriscool@tuxfamily.org","threadId":"36807","inReplyTo":null,"subject":"[PATCH v2 0/4] Add --graft option to git replace","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-06-01T15:10:33Z","receivedAt":"2014-06-01T15:10:33Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"Here is a small patch 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 v1 are the following thanks to Eric,\nJunio and Peff:\n\n- change commit message of patch 1/4\n- added patch 4/4 that was previously sent separately\n- in patch 4/4 rename the existing grafts file\n  into grafts.bak\n\nChristian Couder (4):\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\n Documentation/git-replace.txt             | 10 ++++\n builtin/replace.c                         | 84 ++++++++++++++++++++++++++++++-\n contrib/convert-grafts-to-replace-refs.sh | 29 +++++++++++\n t/t6050-replace.sh                        | 12 +++++\n 4 files changed, 134 insertions(+), 1 deletion(-)\n create mode 100755 contrib/convert-grafts-to-replace-refs.sh\n\n-- \n2.0.0.rc0.40.gd30ccc4\n"},{"id":"243093","messageId":"20140601151038.15428.99129.chriscool@tuxfamily.org","threadId":"36807","inReplyTo":"20140601150409.15428.27017.chriscool@tuxfamily.org","subject":"[PATCH v2 1/4] replace: add --graft option","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-06-01T15:10:34Z","receivedAt":"2014-06-01T15:10:34Z","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>\n---\n builtin/replace.c | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 83 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 1bb491d..9d1e82f 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,76 @@ 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 int read_sha1_commit(const unsigned char *sha1, struct strbuf *dst)\n+{\n+\tvoid *buf;\n+\tenum object_type type;\n+\tunsigned long size;\n+\n+\tbuf = read_sha1_file(sha1, &type, &size);\n+\tif (!buf)\n+\t\treturn error(_(\"cannot read object %s\"), sha1_to_hex(sha1));\n+\tif (type != OBJ_COMMIT) {\n+\t\tfree(buf);\n+\t\treturn error(_(\"object %s is not a commit\"), sha1_to_hex(sha1));\n+\t}\n+\tstrbuf_attach(dst, buf, size, size + 1);\n+\treturn 0;\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+\tstruct strbuf new_parents = STRBUF_INIT;\n+\tconst char *parent_start, *parent_end;\n+\tint i;\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+\tif (read_sha1_commit(old, &buf))\n+\t\tdie(_(\"Invalid commit: '%s'\"), old_ref);\n+\n+\t/* make sure the commit is not corrupt */\n+\tif (parse_commit_buffer(commit, buf.buf, buf.len))\n+\t\tdie(_(\"Could not parse commit: '%s'\"), old_ref);\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 = 1; 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+\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(&new_parents);\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 +374,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 +398,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 +424,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.rc0.40.gd30ccc4\n"},{"id":"243096","messageId":"20140601151038.15428.5445.chriscool@tuxfamily.org","threadId":"36807","inReplyTo":"20140601150409.15428.27017.chriscool@tuxfamily.org","subject":"[PATCH v2 2/4] replace: add test for --graft","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-06-01T15:10:35Z","receivedAt":"2014-06-01T15:10:35Z","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 | 12 ++++++++++++\n 1 file changed, 12 insertions(+)\n\ndiff --git a/t/t6050-replace.sh b/t/t6050-replace.sh\nindex 68b3cb2..ca45a84 100755\n--- a/t/t6050-replace.sh\n+++ b/t/t6050-replace.sh\n@@ -351,4 +351,16 @@ 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+\tgit cat-file -p $HASH5 | test_must_fail grep parent &&\n+\ttest_must_fail git replace --graft $HASH5 $HASH4 $HASH3 &&\n+\tgit replace --force -g $HASH5 $HASH4 $HASH3 &&\n+\tgit cat-file -p $HASH5 | grep \"parent $HASH4\" &&\n+\tgit cat-file -p $HASH5 | grep \"parent $HASH3\" &&\n+\tgit replace -d $HASH5\n+'\n+\n test_done\n-- \n2.0.0.rc0.40.gd30ccc4\n"},{"id":"243097","messageId":"20140601151038.15428.11090.chriscool@tuxfamily.org","threadId":"36807","inReplyTo":"20140601150409.15428.27017.chriscool@tuxfamily.org","subject":"[PATCH v2 3/4] Documentation: replace: add --graft option","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-06-01T15:10:36Z","receivedAt":"2014-06-01T15:10:36Z","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 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.rc0.40.gd30ccc4\n"},{"id":"243095","messageId":"20140601151038.15428.20661.chriscool@tuxfamily.org","threadId":"36807","inReplyTo":"20140601150409.15428.27017.chriscool@tuxfamily.org","subject":"[PATCH v2 4/4] contrib: add convert-grafts-to-replace-refs.sh","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-06-01T15:10:37Z","receivedAt":"2014-06-01T15:10:37Z","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>\n---\n Documentation/git-replace.txt             |  4 +++-\n contrib/convert-grafts-to-replace-refs.sh | 29 +++++++++++++++++++++++++++++\n 2 files changed, 32 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..7718a53\n--- /dev/null\n+++ b/contrib/convert-grafts-to-replace-refs.sh\n@@ -0,0 +1,29 @@\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+die () {\n+\techo >&2 \"$@\"\n+\texit 1\n+}\n+\n+GRAFTS_FILE=\"${GIT_DIR:-.git}/info/grafts\"\n+\n+test -f \"$GRAFTS_FILE\" || die \"Could not find graft file: '$GRAFTS_FILE'\"\n+\n+grep '^[^# ]' \"$GRAFTS_FILE\" | while read definition\n+do\n+\ttest -n \"$definition\" && {\n+\t\techo \"Converting: $definition\"\n+\t\tgit replace --graft $definition ||\n+\t\t\tdie \"Convertion failed for: $definition\"\n+\t}\n+done\n+\n+mv \"$GRAFTS_FILE\" \"$GRAFTS_FILE.bak\" ||\n+\tdie \"Could not mv '$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.rc0.40.gd30ccc4\n"},{"id":"243109","messageId":"CAPig+cTjJHRDQFckLqRCW0xHkMFLVbuYGrLCWHXZQoFsYY5WwA@mail.gmail.com","threadId":"36807","inReplyTo":"20140601151038.15428.20661.chriscool@tuxfamily.org","subject":"Re: [PATCH v2 4/4] contrib: add convert-grafts-to-replace-refs.sh","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2014-06-01T19:42:25Z","receivedAt":"2014-06-01T19:42:25Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sun, Jun 1, 2014 at 11:10 AM, Christian Couder\n<chriscool@tuxfamily.org> wrote:\n> This patch adds into contrib/ an example script to convert\n> grafts from an existing grafts file into replace refs using\n> the new --graft option of \"git replace\".\n>\n> While at it let's mention this new script in the\n> \"git replace\" documentation for the --graft option.\n>\n> Signed-off-by: Christian Couder <chriscool@tuxfamily.org>\n> ---\n> diff --git a/contrib/convert-grafts-to-replace-refs.sh b/contrib/convert-grafts-to-replace-refs.sh\n> new file mode 100755\n> index 0000000..7718a53\n> --- /dev/null\n> +++ b/contrib/convert-grafts-to-replace-refs.sh\n> @@ -0,0 +1,29 @@\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> +die () {\n> +       echo >&2 \"$@\"\n> +       exit 1\n> +}\n> +\n> +GRAFTS_FILE=\"${GIT_DIR:-.git}/info/grafts\"\n> +\n> +test -f \"$GRAFTS_FILE\" || die \"Could not find graft file: '$GRAFTS_FILE'\"\n> +\n> +grep '^[^# ]' \"$GRAFTS_FILE\" | while read definition\n> +do\n> +       test -n \"$definition\" && {\n> +               echo \"Converting: $definition\"\n> +               git replace --graft $definition ||\n> +                       die \"Convertion failed for: $definition\"\n\ns/Convertion/Conversion/  [1]\n\n[1]: http://git.661346.n2.nabble.com/Re-PATCH-contrib-add-convert-grafts-to-replace-refs-sh-tp7611822.html\n\n> +       }\n> +done\n> +\n> +mv \"$GRAFTS_FILE\" \"$GRAFTS_FILE.bak\" ||\n> +       die \"Could not mv '$GRAFTS_FILE' to '$GRAFTS_FILE.bak'\"\n\n\"Could not rename...\" might be a bit more friendly to non-Unixy folk.\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> --\n> 2.0.0.rc0.40.gd30ccc4\n"},{"id":"243146","messageId":"20140602.211602.2254580887425712957.chriscool@tuxfamily.org","threadId":"36807","inReplyTo":"CAPig+cTjJHRDQFckLqRCW0xHkMFLVbuYGrLCWHXZQoFsYY5WwA@mail.gmail.com","subject":"Re: [PATCH v2 4/4] contrib: add convert-grafts-to-replace-refs.sh","fromName":"Christian Couder","fromEmail":"chriscool@tuxfamily.org","sentAt":"2014-06-02T19:16:02Z","receivedAt":"2014-06-02T19:16:02Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"From: Eric Sunshine <sunshine@sunshineco.com>\n\n> On Sun, Jun 1, 2014 at 11:10 AM, Christian Couder\n> <chriscool@tuxfamily.org> wrote:\n>>\n>> +test -f \"$GRAFTS_FILE\" || die \"Could not find graft file: '$GRAFTS_FILE'\"\n>> +\n>> +grep '^[^# ]' \"$GRAFTS_FILE\" | while read definition\n>> +do\n>> +       test -n \"$definition\" && {\n>> +               echo \"Converting: $definition\"\n>> +               git replace --graft $definition ||\n>> +                       die \"Convertion failed for: $definition\"\n> \n> s/Convertion/Conversion/  [1]\n> \n> [1]: http://git.661346.n2.nabble.com/Re-PATCH-contrib-add-convert-grafts-to-replace-refs-sh-tp7611822.html\n\nOoops, sorry I forgot this.\n \n>> +       }\n>> +done\n>> +\n>> +mv \"$GRAFTS_FILE\" \"$GRAFTS_FILE.bak\" ||\n>> +       die \"Could not mv '$GRAFTS_FILE' to '$GRAFTS_FILE.bak'\"\n> \n> \"Could not rename...\" might be a bit more friendly to non-Unixy folk.\n\nOk, I will use \"rename\".\n\nThanks,\nChristian.\n"}]}