{"thread":{"id":"48278","subject":"[PATCH] Deprecate support for .git/info/grafts","startedAt":"2018-04-13T11:12:02Z","lastAt":"2018-11-28T09:03:20Z","messageCount":115,"participants":["Johannes Schindelin","Stefan Beller","Eric Sunshine","Christian Couder","Junio C Hamano","Jakub Narebski","SZEDER Gábor","Philip Oakley","Jakub Narębski","Ævar Arnfjörð Bjarmason"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"344600","messageId":"0f0942043678fe76f8d654306482ee26fac643f0.1523617836.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":null,"subject":"[PATCH] Deprecate support for .git/info/grafts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-13T11:11:37Z","receivedAt":"2018-04-13T11:12:02Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The grafts feature was a convenient way to \"stich together\" ancient\nhistory to the fresh start of linux.git.\n\nIts implementation is, however, not up to Git's standards, as there are\ntoo many ways where it can lead to surprising and unwelcome behavior.\n\nFor example, when pushing from a repository with active grafts, it is\npossible to miss commits that have been \"grafted out\", resulting in a\nbroken state on the other side.\n\nAlso, the grafts feature is limited to \"rewriting\" commits' list of\nparents, it cannot replace anything else.\n\nThe much younger feature implemented as `git replace` set out to remedy\nthose limitations and dangerous bugs.\n\nSeeing as `git replace` is pretty mature by now, it is time to deprecate\nsupport for the graft file, and to retire it eventually.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n advice.c                  | 2 ++\n advice.h                  | 1 +\n commit.c                  | 9 +++++++++\n t/t6001-rev-list-graft.sh | 9 +++++++++\n 4 files changed, 21 insertions(+)\n\ndiff --git a/advice.c b/advice.c\nindex 406efc183ba..4411704fd45 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -19,6 +19,7 @@ int advice_rm_hints = 1;\n int advice_add_embedded_repo = 1;\n int advice_ignored_hook = 1;\n int advice_waiting_for_editor = 1;\n+int advice_graft_file_deprecated = 1;\n \n static struct {\n \tconst char *name;\n@@ -42,6 +43,7 @@ static struct {\n \t{ \"addembeddedrepo\", &advice_add_embedded_repo },\n \t{ \"ignoredhook\", &advice_ignored_hook },\n \t{ \"waitingforeditor\", &advice_waiting_for_editor },\n+\t{ \"graftfiledeprecated\", &advice_graft_file_deprecated },\n \n \t/* make this an alias for backward compatibility */\n \t{ \"pushnonfastforward\", &advice_push_update_rejected }\ndiff --git a/advice.h b/advice.h\nindex 70568fa7922..9f5064e82a8 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -21,6 +21,7 @@ extern int advice_rm_hints;\n extern int advice_add_embedded_repo;\n extern int advice_ignored_hook;\n extern int advice_waiting_for_editor;\n+extern int advice_graft_file_deprecated;\n \n int git_default_advice_config(const char *var, const char *value);\n __attribute__((format (printf, 1, 2)))\ndiff --git a/commit.c b/commit.c\nindex ca474a7c112..a96b0a27154 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -12,6 +12,7 @@\n #include \"prio-queue.h\"\n #include \"sha1-lookup.h\"\n #include \"wt-status.h\"\n+#include \"advice.h\"\n \n static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);\n \n@@ -176,6 +177,14 @@ static int read_graft_file(const char *graft_file)\n \tstruct strbuf buf = STRBUF_INIT;\n \tif (!fp)\n \t\treturn -1;\n+\tif (advice_graft_file_deprecated)\n+\t\tadvise(_(\"Support for <GIT_DIR>/info/grafts is deprecated\\n\"\n+\t\t\t \"and will be removed in a future Git version.\\n\"\n+\t\t\t \"\\n\"\n+\t\t\t \"Please use \\\"git replace --graft [...]\\\" instead.\\n\"\n+\t\t\t \"\\n\"\n+\t\t\t \"Turn this message off by running\\n\"\n+\t\t\t \"\\\"git config advice.graftFileDeprecated false\\\"\"));\n \twhile (!strbuf_getwholeline(&buf, fp, '\\n')) {\n \t\t/* The format is just \"Commit Parent1 Parent2 ...\\n\" */\n \t\tstruct commit_graft *graft = read_graft_line(&buf);\ndiff --git a/t/t6001-rev-list-graft.sh b/t/t6001-rev-list-graft.sh\nindex 05ddc69cf2a..7504ba47511 100755\n--- a/t/t6001-rev-list-graft.sh\n+++ b/t/t6001-rev-list-graft.sh\n@@ -110,4 +110,13 @@ do\n \t\"\n \n done\n+\n+test_expect_success 'show advice that grafts are deprecated' '\n+\tgit show HEAD 2>err &&\n+\ttest_i18ngrep \"git replace\" err &&\n+\ttest_config advice.graftFileDeprecated false &&\n+\tgit show HEAD 2>err &&\n+\ttest_i18ngrep ! \"git replace\" err\n+'\n+\n test_done\n\nbase-commit: fe0a9eaf31dd0c349ae4308498c33a5c3794b293\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\nPublished-As: https://github.com/dscho/git/releases/tag/deprecate-grafts-v1\nFetch-It-Via: git fetch https://github.com/dscho/git deprecate-grafts-v1\n"},{"id":"344624","messageId":"CAGZ79kbEec_U5wL_aOS5O_3+ZOshGwARALpjz801h-fRx-zotA@mail.gmail.com","threadId":"48278","inReplyTo":"0f0942043678fe76f8d654306482ee26fac643f0.1523617836.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH] Deprecate support for .git/info/grafts","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-04-13T18:22:23Z","receivedAt":"2018-04-13T18:22:29Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"Hi Johannes,\n\nOn Fri, Apr 13, 2018 at 4:11 AM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> The grafts feature was a convenient way to \"stich together\" ancient\n> history to the fresh start of linux.git.\n\nDid you mean: stitch?\n\n> Its implementation is, however, not up to Git's standards, as there are\n> too many ways where it can lead to surprising and unwelcome behavior.\n>\n> For example, when pushing from a repository with active grafts, it is\n> possible to miss commits that have been \"grafted out\", resulting in a\n> broken state on the other side.\n>\n> Also, the grafts feature is limited to \"rewriting\" commits' list of\n> parents, it cannot replace anything else.\n>\n> The much younger feature implemented as `git replace` set out to remedy\n> those limitations and dangerous bugs.\n>\n> Seeing as `git replace` is pretty mature by now, it is time to deprecate\n> support for the graft file, and to retire it eventually.\n\nIt seems that the maturity needed for this commit was reached in\n4228e8bc98 (replace: add --graft option, 2014-07-19)\n\nReviewed-by: Stefan Beller <sbeller@google.com>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n\n>                 return -1;\n> +       if (advice_graft_file_deprecated)\n> +               advise(_(\"Support for <GIT_DIR>/info/grafts is deprecated\\n\"\n> +                        \"and will be removed in a future Git version.\\n\"\n> +                        \"\\n\"\n> +                        \"Please use \\\"git replace --graft [...]\\\" instead.\\n\"\n> +                        \"\\n\"\n> +                        \"Turn this message off by running\\n\"\n> +                        \"\\\"git config advice.graftFileDeprecated false\\\"\"));\n\nSo the user would have to run:\n\n  for line in <GIT_DIR>/info/grafts:\n      git replace --graft $line\n      # The order in the grafts file is the same as the arguments,\n      # but we'd have to pass each as its own argument\n  rm <GIT_DIR>/info/grafts\n\nI wonder if we want to offer a migration tool or just leave it\nat this hint.\n\nThanks,\nStefan\n"},{"id":"344625","messageId":"CAPig+cREYENM7Y8SqtG2ket9+oJ6w0OU1_Qa8f-+O_yDyH=Aww@mail.gmail.com","threadId":"48278","inReplyTo":"0f0942043678fe76f8d654306482ee26fac643f0.1523617836.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH] Deprecate support for .git/info/grafts","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-04-13T18:57:36Z","receivedAt":"2018-04-13T18:57:42Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Fri, Apr 13, 2018 at 7:11 AM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> The grafts feature was a convenient way to \"stich together\" ancient\n> history to the fresh start of linux.git.\n> [...]\n> The much younger feature implemented as `git replace` set out to remedy\n> those limitations and dangerous bugs.\n>\n> Seeing as `git replace` is pretty mature by now, it is time to deprecate\n> support for the graft file, and to retire it eventually.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  advice.c                  | 2 ++\n>  advice.h                  | 1 +\n>  commit.c                  | 9 +++++++++\n>  t/t6001-rev-list-graft.sh | 9 +++++++++\n>  4 files changed, 21 insertions(+)\n\nPerhaps, as part of this deprecation, the example in\nDocumentation/git-filter-branch.txt should be updated to suggest\ngit-replace instead of .git/info/grafts.\n\nMaybe, also, Documentation/shallow.txt should talk about replace-refs\nrather than .git/info/grafts.\n"},{"id":"344654","messageId":"nycvar.QRO.7.76.6.1804132339130.234@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"48278","inReplyTo":"CAGZ79kbEec_U5wL_aOS5O_3+ZOshGwARALpjz801h-fRx-zotA@mail.gmail.com","subject":"Re: [PATCH] Deprecate support for .git/info/grafts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-13T22:35:39Z","receivedAt":"2018-04-13T22:35:52Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Fri, 13 Apr 2018, Stefan Beller wrote:\n\n> On Fri, Apr 13, 2018 at 4:11 AM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > The grafts feature was a convenient way to \"stich together\" ancient\n> > history to the fresh start of linux.git.\n> \n> Did you mean: stitch?\n\nYes ;-)\n\n> > Its implementation is, however, not up to Git's standards, as there are\n> > too many ways where it can lead to surprising and unwelcome behavior.\n> >\n> > For example, when pushing from a repository with active grafts, it is\n> > possible to miss commits that have been \"grafted out\", resulting in a\n> > broken state on the other side.\n> >\n> > Also, the grafts feature is limited to \"rewriting\" commits' list of\n> > parents, it cannot replace anything else.\n> >\n> > The much younger feature implemented as `git replace` set out to remedy\n> > those limitations and dangerous bugs.\n> >\n> > Seeing as `git replace` is pretty mature by now, it is time to deprecate\n> > support for the graft file, and to retire it eventually.\n> \n> It seems that the maturity needed for this commit was reached in\n> 4228e8bc98 (replace: add --graft option, 2014-07-19)\n\nRight. I'll add that to the commit message.\n\n> Reviewed-by: Stefan Beller <sbeller@google.com>\n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> \n> >                 return -1;\n> > +       if (advice_graft_file_deprecated)\n> > +               advise(_(\"Support for <GIT_DIR>/info/grafts is deprecated\\n\"\n> > +                        \"and will be removed in a future Git version.\\n\"\n> > +                        \"\\n\"\n> > +                        \"Please use \\\"git replace --graft [...]\\\" instead.\\n\"\n> > +                        \"\\n\"\n> > +                        \"Turn this message off by running\\n\"\n> > +                        \"\\\"git config advice.graftFileDeprecated false\\\"\"));\n> \n> So the user would have to run:\n> \n>   for line in <GIT_DIR>/info/grafts:\n>       git replace --graft $line\n>       # The order in the grafts file is the same as the arguments,\n>       # but we'd have to pass each as its own argument\n>   rm <GIT_DIR>/info/grafts\n> \n> I wonder if we want to offer a migration tool or just leave it\n> at this hint.\n\nThere is contrib/convert-grafts-to-replace-refs.sh.\n\nI wonder whether we have to care enough to implement a `git replace\n--convert-graft-file`...\n\nCiao,\nDscho\n"},{"id":"344655","messageId":"CAGZ79kawFhCuDbAndg2oU87jFT-PhpLBMoEBumN=J_DfGK=v9A@mail.gmail.com","threadId":"48278","inReplyTo":"nycvar.QRO.7.76.6.1804132339130.234@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH] Deprecate support for .git/info/grafts","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-04-13T22:45:16Z","receivedAt":"2018-04-13T22:45:21Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"Hi Johannes,\n\nOn Fri, Apr 13, 2018 at 3:35 PM, Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n> Hi Stefan,\n\n>> I wonder if we want to offer a migration tool or just leave it\n>> at this hint.\n>\n> There is contrib/convert-grafts-to-replace-refs.sh.\n\nOh cool! I wonder if we want to expose it more such that people\ndiscover it.\n\n> I wonder whether we have to care enough to implement a `git replace\n> --convert-graft-file`...\n\nI don't think so.\n"},{"id":"345068","messageId":"nycvar.QRO.7.76.6.1804190944470.4241@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"48278","inReplyTo":"CAPig+cREYENM7Y8SqtG2ket9+oJ6w0OU1_Qa8f-+O_yDyH=Aww@mail.gmail.com","subject":"Re: [PATCH] Deprecate support for .git/info/grafts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-19T08:14:10Z","receivedAt":"2018-04-19T08:14:37Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Eric,\n\nOn Fri, 13 Apr 2018, Eric Sunshine wrote:\n\n> On Fri, Apr 13, 2018 at 7:11 AM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > The grafts feature was a convenient way to \"stich together\" ancient\n> > history to the fresh start of linux.git.\n> > [...]\n> > The much younger feature implemented as `git replace` set out to remedy\n> > those limitations and dangerous bugs.\n> >\n> > Seeing as `git replace` is pretty mature by now, it is time to deprecate\n> > support for the graft file, and to retire it eventually.\n> >\n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> >  advice.c                  | 2 ++\n> >  advice.h                  | 1 +\n> >  commit.c                  | 9 +++++++++\n> >  t/t6001-rev-list-graft.sh | 9 +++++++++\n> >  4 files changed, 21 insertions(+)\n> \n> Perhaps, as part of this deprecation, the example in\n> Documentation/git-filter-branch.txt should be updated to suggest\n> git-replace instead of .git/info/grafts.\n\nGood point!\n\n> Maybe, also, Documentation/shallow.txt should talk about replace-refs\n> rather than .git/info/grafts.\n\nSure. Internally, of course, \"shallow\" is still handled very much like the\ngraft file. In that sense, it is probably a good thing to keep referring\nto the graft file in technical/shallow *in addition* to replace refs.\n\nThe reason why the graft file should be deprecated does not apply to\nthe shallow file at all, either: the entire set of problems with grafts\ncomes from the fact that you can *replace* the parents *with other\nparents*. That allows for pushing corrupt history to public repositories\nIIRC. The same problems do not arise with the shallow feature, where you\ncan *cut* history, but not replace it.\n\nThere is a notable difference between shallow commits and replace refs\nwhich I did not think about earlier (it actually only came up in testing):\nwe currently disallow completely to replace merge commits when they\ncontain mergetags, i.e. entries in the commit header that record the hash\nof the *tag* that was merged (if any). That includes the case where you\nwant to replace the commit with a root commit, as would be needed for\nshallow commits.\n\nHowever, there are more reasons not to conflate the shallow commits with\nreplaced commits: by nature, the \"shallow\" attribute is a lot more\nvolatile than the \"replaced\" one, as we want to keep it easy to deepen the\nhistory (or to \"unshallow\" it).\n\nCiao,\nDscho\n"},{"id":"345069","messageId":"nycvar.QRO.7.76.6.1804191014430.4241@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"48278","inReplyTo":"CAGZ79kawFhCuDbAndg2oU87jFT-PhpLBMoEBumN=J_DfGK=v9A@mail.gmail.com","subject":"Re: [PATCH] Deprecate support for .git/info/grafts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-19T08:15:43Z","receivedAt":"2018-04-19T08:16:09Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Fri, 13 Apr 2018, Stefan Beller wrote:\n\n> On Fri, Apr 13, 2018 at 3:35 PM, Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> \n> >> I wonder if we want to offer a migration tool or just leave it at\n> >> this hint.\n> >\n> > There is contrib/convert-grafts-to-replace-refs.sh.\n> \n> Oh cool! I wonder if we want to expose it more such that people\n> discover it.\n\nNah.\n\n> > I wonder whether we have to care enough to implement a `git replace\n> > --convert-graft-file`...\n> \n> I don't think so.\n\nAfter reflecting about this in the back of my mind, I actually do. So I\nimplemented it.\n\nCiao,\nDscho\n"},{"id":"345070","messageId":"cover.1524125760.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"0f0942043678fe76f8d654306482ee26fac643f0.1523617836.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 0/7] Deprecate .git/info/grafts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-19T08:17:09Z","receivedAt":"2018-04-19T08:17:36Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"It is fragile, as there is no way for the revision machinery to say \"but\nnow I want to traverse the graph ignoring the graft file\" e.g. when\npushing commits to a remote repository (which, as a consequence, can\nmiss commits).\n\nAnd we already have a better solution with `git replace --graft <comit>\n[<parent>...]`.\n\nChanges since v1:\n\n- Fixed typo (stich -> stitch).\n\n- Added reference in the commit message to the patch where `git replace`\n  reached feature parity with the graft file.\n\n- Added the --convert-graft-file option to `git replace` (with tests).\n\n- Adjusted the advice to suggest --convert-graft-file instead.\n\n- Adjusted the documentation of filter-branch and technical/shallow to\n  reflect the fact that grafts are deprecated.\n\n\nJohannes Schindelin (7):\n  replace: \"libify\" create_graft()\n  replace: introduce --convert-graft-file\n  Add a test for `git replace --convert-graft-file`\n  Deprecate support for .git/info/grafts\n  filter-branch: stop suggesting to use grafts\n  technical/shallow: describe the relationship with replace refs\n  technical/shallow: describe why shallow cannot use replace refs\n\n Documentation/git-filter-branch.txt |  2 +-\n Documentation/git-replace.txt       | 11 +++--\n Documentation/technical/shallow.txt | 24 +++++++----\n advice.c                            |  2 +\n advice.h                            |  1 +\n builtin/replace.c                   | 63 ++++++++++++++++++++++++++++-\n commit.c                            | 10 +++++\n t/t6001-rev-list-graft.sh           |  9 +++++\n t/t6050-replace.sh                  | 20 +++++++++\n 9 files changed, 129 insertions(+), 13 deletions(-)\n\n\nbase-commit: fe0a9eaf31dd0c349ae4308498c33a5c3794b293\nPublished-As: https://github.com/dscho/git/releases/tag/deprecate-grafts-v2\nFetch-It-Via: git fetch https://github.com/dscho/git deprecate-grafts-v2\n\nInterdiff vs v1:\n diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt\n index b634043183b..1d4d2f86045 100644\n --- a/Documentation/git-filter-branch.txt\n +++ b/Documentation/git-filter-branch.txt\n @@ -288,7 +288,7 @@ git filter-branch --parent-filter \\\n  or even simpler:\n  \n  -----------------------------------------------\n -echo \"$commit-id $graft-id\" >> .git/info/grafts\n +git replace --graft $commit-id $graft-id\n  git filter-branch $graft-id..HEAD\n  -----------------------------------------------\n  \n diff --git a/Documentation/git-replace.txt b/Documentation/git-replace.txt\n index e5c57ae6ef4..4dc0686f7d6 100644\n --- a/Documentation/git-replace.txt\n +++ b/Documentation/git-replace.txt\n @@ -11,6 +11,7 @@ SYNOPSIS\n  'git replace' [-f] <object> <replacement>\n  'git replace' [-f] --edit <object>\n  'git replace' [-f] --graft <commit> [<parent>...]\n +'git replace' [-f] --convert-graft-file\n  'git replace' -d <object>...\n  'git replace' [--format=<format>] [-l [<pattern>]]\n  \n @@ -87,9 +88,13 @@ 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. 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 +\tcommit. Use `--convert-graft-file` to convert a\n +\t`$GIT_DIR/info/grafts` file use replace refs instead.\n +\n +--convert-graft-file::\n +\tCreates graft commits for all entries in `$GIT_DIR/info/grafts`\n +\tand deletes that file upon success. The purpose is to help users\n +\twith transitioning off of the now-deprecated graft file.\n  \n  -l <pattern>::\n  --list <pattern>::\n diff --git a/Documentation/technical/shallow.txt b/Documentation/technical/shallow.txt\n index 5183b154229..cb79181c2bb 100644\n --- a/Documentation/technical/shallow.txt\n +++ b/Documentation/technical/shallow.txt\n @@ -9,19 +9,29 @@ these commits have no parents.\n  *********************************************************\n  \n  The basic idea is to write the SHA-1s of shallow commits into\n -$GIT_DIR/shallow, and handle its contents like the contents\n -of $GIT_DIR/info/grafts (with the difference that shallow\n -cannot contain parent information).\n +$GIT_DIR/shallow, and handle its contents similar to replace\n +refs (with the difference that shallow does not actually\n +create those replace refs) and very much like the deprecated\n +graft file (with the difference that shallow commits will\n +always have their parents grafted away, not replaced by\n +different parents).\n  \n -This information is stored in a new file instead of grafts, or\n -even the config, since the user should not touch that file\n -at all (even throughout development of the shallow clone, it\n -was never manually edited!).\n +This information is stored in a special-purpose file because the\n +user should not touch that file at all (even throughout\n +development of the shallow clone, it was never manually\n +edited!).\n  \n  Each line contains exactly one SHA-1. When read, a commit_graft\n  will be constructed, which has nr_parent < 0 to make it easier\n  to discern from user provided grafts.\n  \n +Note that the shallow feature could not be changed easily to\n +use replace refs: a commit containing a `mergetag` is not allowed\n +to be replaced, not even by a root commit. Such a commit can be\n +made shallow, though. Also, having a `shallow` file explicitly\n +listing all the commits made shallow makes it a *lot* easier to\n +do shallow-specific things such as to deepen the history.\n +\n  Since fsck-objects relies on the library to read the objects,\n  it honours shallow commits automatically.\n  \n diff --git a/builtin/replace.c b/builtin/replace.c\n index 935647be6bd..4cdc00a96df 100644\n --- a/builtin/replace.c\n +++ b/builtin/replace.c\n @@ -20,6 +20,7 @@ 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 [-f] --convert-graft-file\"),\n  \tN_(\"git replace -d <object>...\"),\n  \tN_(\"git replace [--format=<format>] [-l [<pattern>]]\"),\n  \tNULL\n @@ -395,7 +396,9 @@ static int create_graft(int argc, const char **argv, int force)\n  \n  \tif (get_oid(old_ref, &old_oid) < 0)\n  \t\tdie(_(\"Not a valid object name: '%s'\"), old_ref);\n -\tcommit = lookup_commit_or_die(&old_oid, old_ref);\n +\tcommit = lookup_commit_reference(&old_oid);\n +\tif (!commit)\n +\t\treturn error(_(\"could not parse %s\"), old_ref);\n  \n  \tbuffer = get_commit_buffer(commit, &size);\n  \tstrbuf_add(&buf, buffer, size);\n @@ -421,6 +424,53 @@ static int create_graft(int argc, const char **argv, int force)\n  \treturn replace_object_oid(old_ref, &old_oid, \"replacement\", &new_oid, force);\n  }\n  \n +static int convert_graft_file(int force)\n +{\n +\tconst char *graft_file = get_graft_file();\n +\tFILE *fp = fopen_or_warn(graft_file, \"r\");\n +\tstruct strbuf buf = STRBUF_INIT, err = STRBUF_INIT;\n +\tstruct argv_array args = ARGV_ARRAY_INIT;\n +\n +\tif (!fp)\n +\t\treturn -1;\n +\n +\twhile (strbuf_getline(&buf, fp) != EOF) {\n +\t\tint i = 0, j;\n +\n +\t\twhile (i != buf.len) {\n +\t\t\tchar save;\n +\n +\t\t\tfor (j = i; j < buf.len && !isspace(buf.buf[j]); j++)\n +\t\t\t\t; /* look further */\n +\t\t\tsave = buf.buf[j];\n +\t\t\tbuf.buf[j] = '\\0';\n +\t\t\targv_array_push(&args, buf.buf + i);\n +\t\t\tbuf.buf[j] = save;\n +\n +\t\t\twhile (j < buf.len && isspace(buf.buf[j]))\n +\t\t\t\tj++;\n +\t\t\ti = j;\n +\t\t}\n +\n +\t\tif (create_graft(args.argc, args.argv, force))\n +\t\t\tstrbuf_addf(&err, \"\\n\\t%s\", buf.buf);\n +\n +\t\targv_array_clear(&args);\n +\t\tstrbuf_reset(&buf);\n +\t}\n +\n +\tstrbuf_release(&buf);\n +\targv_array_clear(&args);\n +\n +\tif (!err.len)\n +\t\treturn unlink_or_warn(graft_file);\n +\n +\twarning(_(\"could not convert the following graft(s):\\n%s\"), err.buf);\n +\tstrbuf_release(&err);\n +\n +\treturn -1;\n +}\n +\n  int cmd_replace(int argc, const char **argv, const char *prefix)\n  {\n  \tint force = 0;\n @@ -432,6 +482,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n  \t\tMODE_DELETE,\n  \t\tMODE_EDIT,\n  \t\tMODE_GRAFT,\n +\t\tMODE_CONVERT_GRAFT_FILE,\n  \t\tMODE_REPLACE\n  \t} cmdmode = MODE_UNSPECIFIED;\n  \tstruct option options[] = {\n @@ -439,6 +490,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\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_CMDMODE(0, \"convert-graft-file\", &cmdmode, N_(\"convert existing graft file\"), MODE_CONVERT_GRAFT_FILE),\n  \t\tOPT_BOOL_F('f', \"force\", &force, N_(\"replace the ref if it exists\"),\n  \t\t\t   PARSE_OPT_NOCOMPLETE),\n  \t\tOPT_BOOL(0, \"raw\", &raw, N_(\"do not pretty-print contents for --edit\")),\n @@ -461,7 +513,8 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n  \tif (force &&\n  \t    cmdmode != MODE_REPLACE &&\n  \t    cmdmode != MODE_EDIT &&\n -\t    cmdmode != MODE_GRAFT)\n +\t    cmdmode != MODE_GRAFT &&\n +\t    cmdmode != MODE_CONVERT_GRAFT_FILE)\n  \t\tusage_msg_opt(\"-f only makes sense when writing a replacement\",\n  \t\t\t      git_replace_usage, options);\n  \n @@ -494,6 +547,12 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n  \t\t\t\t      git_replace_usage, options);\n  \t\treturn create_graft(argc, argv, force);\n  \n +\tcase MODE_CONVERT_GRAFT_FILE:\n +\t\tif (argc != 0)\n +\t\t\tusage_msg_opt(\"--convert-graft-file takes no argument\",\n +\t\t\t\t      git_replace_usage, options);\n +\t\treturn !!convert_graft_file(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 diff --git a/commit.c b/commit.c\n index a96b0a27154..1a5e8777617 100644\n --- a/commit.c\n +++ b/commit.c\n @@ -181,7 +181,8 @@ static int read_graft_file(const char *graft_file)\n  \t\tadvise(_(\"Support for <GIT_DIR>/info/grafts is deprecated\\n\"\n  \t\t\t \"and will be removed in a future Git version.\\n\"\n  \t\t\t \"\\n\"\n -\t\t\t \"Please use \\\"git replace --graft [...]\\\" instead.\\n\"\n +\t\t\t \"Please use \\\"git replace --convert-graft-file\\\"\\n\"\n +\t\t\t \"to convert the grafts into replace refs.\\n\"\n  \t\t\t \"\\n\"\n  \t\t\t \"Turn this message off by running\\n\"\n  \t\t\t \"\\\"git config advice.graftFileDeprecated false\\\"\"));\n diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh\n index c630aba657e..77ded6df653 100755\n --- a/t/t6050-replace.sh\n +++ b/t/t6050-replace.sh\n @@ -444,4 +444,24 @@ test_expect_success GPG '--graft on a commit with a mergetag' '\n  \tgit replace -d $HASH10\n  '\n  \n +test_expect_success '--convert-graft-file' '\n +\t: add and convert graft file &&\n +\tprintf \"%s\\n%s %s\\n%s\\n\" \\\n +\t\t$(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \\\n +\t\t>.git/info/grafts &&\n +\tgit replace --convert-graft-file &&\n +\ttest_path_is_missing .git/info/grafts &&\n +\n +\t: verify that the history is now \"grafted\" &&\n +\tgit rev-list HEAD >out &&\n +\ttest_line_count = 4 out &&\n +\n +\t: create invalid graft file and verify that it is not deleted &&\n +\ttest_when_finished \"rm -f .git/info/grafts\" &&\n +\techo $EMPTY_BLOB $EMPTY_TREE >.git/info/grafts &&\n +\ttest_must_fail git replace --convert-graft-file 2>err &&\n +\tgrep \"$EMPTY_BLOB $EMPTY_TREE\" err &&\n +\tgrep \"$EMPTY_BLOB $EMPTY_TREE\" .git/info/grafts\n +'\n +\n  test_done\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n"},{"id":"345071","messageId":"1f1c64314088383570dd2232e08a88c8df3298f5.1524125760.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524125760.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 1/7] replace: \"libify\" create_graft()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-19T08:17:31Z","receivedAt":"2018-04-19T08:18:00Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"It is quite convenient to simply die() in builtins, in the absence of\nproper exception handling, because it allows us to just go belly up\nwithout having to implement error handling chains.\n\nOf course, for reusable library functions, this is a big no-no, so we\n(try to) restrict the usage of die() to one-shot commands, i.e. places\nwhere we know that the caller does not want to, say, give the user a\nuseful high-level error message, i.e. a message that the function calling\ndie() could not possibly know.\n\nThe problem with this reasoning is that sooner or later, pretty much all\nuseful functions will *need* to be libified: the more useful a function,\nthe more likely it is to be called from a call chain where the outer\nfunction implements a high-level operation that needs to provide\nadditional advice to the user in case of failure.\n\nThis is the case here: the create_graft() function is useful enough to be\ncalled in a loop, say, in the upcoming patch to convert a graft file in\none fell swoop. Therefore, this function must not be allowed to die(), nor\nany of its callees.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/replace.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 935647be6bd..43264f0998e 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -395,7 +395,9 @@ static int create_graft(int argc, const char **argv, int force)\n \n \tif (get_oid(old_ref, &old_oid) < 0)\n \t\tdie(_(\"Not a valid object name: '%s'\"), old_ref);\n-\tcommit = lookup_commit_or_die(&old_oid, old_ref);\n+\tcommit = lookup_commit_reference(&old_oid);\n+\tif (!commit)\n+\t\treturn error(_(\"could not parse %s\"), old_ref);\n \n \tbuffer = get_commit_buffer(commit, &size);\n \tstrbuf_add(&buf, buffer, size);\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"345072","messageId":"cecb82973f80fdd2db940410db2a7c3b23107c4b.1524125760.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524125760.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 2/7] replace: introduce --convert-graft-file","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-19T08:17:50Z","receivedAt":"2018-04-19T08:18:16Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This option is intended to help with the transition away from the\nnow-deprecated graft file.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-replace.txt | 11 +++++--\n builtin/replace.c             | 59 ++++++++++++++++++++++++++++++++++-\n 2 files changed, 66 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-replace.txt b/Documentation/git-replace.txt\nindex e5c57ae6ef4..4dc0686f7d6 100644\n--- a/Documentation/git-replace.txt\n+++ b/Documentation/git-replace.txt\n@@ -11,6 +11,7 @@ SYNOPSIS\n 'git replace' [-f] <object> <replacement>\n 'git replace' [-f] --edit <object>\n 'git replace' [-f] --graft <commit> [<parent>...]\n+'git replace' [-f] --convert-graft-file\n 'git replace' -d <object>...\n 'git replace' [--format=<format>] [-l [<pattern>]]\n \n@@ -87,9 +88,13 @@ 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. 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+\tcommit. Use `--convert-graft-file` to convert a\n+\t`$GIT_DIR/info/grafts` file use replace refs instead.\n+\n+--convert-graft-file::\n+\tCreates graft commits for all entries in `$GIT_DIR/info/grafts`\n+\tand deletes that file upon success. The purpose is to help users\n+\twith transitioning off of the now-deprecated graft file.\n \n -l <pattern>::\n --list <pattern>::\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 43264f0998e..4cdc00a96df 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -20,6 +20,7 @@ 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 [-f] --convert-graft-file\"),\n \tN_(\"git replace -d <object>...\"),\n \tN_(\"git replace [--format=<format>] [-l [<pattern>]]\"),\n \tNULL\n@@ -423,6 +424,53 @@ static int create_graft(int argc, const char **argv, int force)\n \treturn replace_object_oid(old_ref, &old_oid, \"replacement\", &new_oid, force);\n }\n \n+static int convert_graft_file(int force)\n+{\n+\tconst char *graft_file = get_graft_file();\n+\tFILE *fp = fopen_or_warn(graft_file, \"r\");\n+\tstruct strbuf buf = STRBUF_INIT, err = STRBUF_INIT;\n+\tstruct argv_array args = ARGV_ARRAY_INIT;\n+\n+\tif (!fp)\n+\t\treturn -1;\n+\n+\twhile (strbuf_getline(&buf, fp) != EOF) {\n+\t\tint i = 0, j;\n+\n+\t\twhile (i != buf.len) {\n+\t\t\tchar save;\n+\n+\t\t\tfor (j = i; j < buf.len && !isspace(buf.buf[j]); j++)\n+\t\t\t\t; /* look further */\n+\t\t\tsave = buf.buf[j];\n+\t\t\tbuf.buf[j] = '\\0';\n+\t\t\targv_array_push(&args, buf.buf + i);\n+\t\t\tbuf.buf[j] = save;\n+\n+\t\t\twhile (j < buf.len && isspace(buf.buf[j]))\n+\t\t\t\tj++;\n+\t\t\ti = j;\n+\t\t}\n+\n+\t\tif (create_graft(args.argc, args.argv, force))\n+\t\t\tstrbuf_addf(&err, \"\\n\\t%s\", buf.buf);\n+\n+\t\targv_array_clear(&args);\n+\t\tstrbuf_reset(&buf);\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\targv_array_clear(&args);\n+\n+\tif (!err.len)\n+\t\treturn unlink_or_warn(graft_file);\n+\n+\twarning(_(\"could not convert the following graft(s):\\n%s\"), err.buf);\n+\tstrbuf_release(&err);\n+\n+\treturn -1;\n+}\n+\n int cmd_replace(int argc, const char **argv, const char *prefix)\n {\n \tint force = 0;\n@@ -434,6 +482,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n \t\tMODE_DELETE,\n \t\tMODE_EDIT,\n \t\tMODE_GRAFT,\n+\t\tMODE_CONVERT_GRAFT_FILE,\n \t\tMODE_REPLACE\n \t} cmdmode = MODE_UNSPECIFIED;\n \tstruct option options[] = {\n@@ -441,6 +490,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\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_CMDMODE(0, \"convert-graft-file\", &cmdmode, N_(\"convert existing graft file\"), MODE_CONVERT_GRAFT_FILE),\n \t\tOPT_BOOL_F('f', \"force\", &force, N_(\"replace the ref if it exists\"),\n \t\t\t   PARSE_OPT_NOCOMPLETE),\n \t\tOPT_BOOL(0, \"raw\", &raw, N_(\"do not pretty-print contents for --edit\")),\n@@ -463,7 +513,8 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n \tif (force &&\n \t    cmdmode != MODE_REPLACE &&\n \t    cmdmode != MODE_EDIT &&\n-\t    cmdmode != MODE_GRAFT)\n+\t    cmdmode != MODE_GRAFT &&\n+\t    cmdmode != MODE_CONVERT_GRAFT_FILE)\n \t\tusage_msg_opt(\"-f only makes sense when writing a replacement\",\n \t\t\t      git_replace_usage, options);\n \n@@ -496,6 +547,12 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n \t\t\t\t      git_replace_usage, options);\n \t\treturn create_graft(argc, argv, force);\n \n+\tcase MODE_CONVERT_GRAFT_FILE:\n+\t\tif (argc != 0)\n+\t\t\tusage_msg_opt(\"--convert-graft-file takes no argument\",\n+\t\t\t\t      git_replace_usage, options);\n+\t\treturn !!convert_graft_file(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.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"345073","messageId":"cf8169c80f08612185f81927ee6ff22e087b2835.1524125760.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524125760.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 3/7] Add a test for `git replace --convert-graft-file`","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-19T08:18:10Z","receivedAt":"2018-04-19T08:18:35Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The proof, as the saying goes, lies in the pudding. So here is a\nregression test that not only demonstrates what the option is supposed to\naccomplish, but also demonstrates that it does accomplish it.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/t6050-replace.sh | 20 ++++++++++++++++++++\n 1 file changed, 20 insertions(+)\n\ndiff --git a/t/t6050-replace.sh b/t/t6050-replace.sh\nindex c630aba657e..77ded6df653 100755\n--- a/t/t6050-replace.sh\n+++ b/t/t6050-replace.sh\n@@ -444,4 +444,24 @@ test_expect_success GPG '--graft on a commit with a mergetag' '\n \tgit replace -d $HASH10\n '\n \n+test_expect_success '--convert-graft-file' '\n+\t: add and convert graft file &&\n+\tprintf \"%s\\n%s %s\\n%s\\n\" \\\n+\t\t$(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \\\n+\t\t>.git/info/grafts &&\n+\tgit replace --convert-graft-file &&\n+\ttest_path_is_missing .git/info/grafts &&\n+\n+\t: verify that the history is now \"grafted\" &&\n+\tgit rev-list HEAD >out &&\n+\ttest_line_count = 4 out &&\n+\n+\t: create invalid graft file and verify that it is not deleted &&\n+\ttest_when_finished \"rm -f .git/info/grafts\" &&\n+\techo $EMPTY_BLOB $EMPTY_TREE >.git/info/grafts &&\n+\ttest_must_fail git replace --convert-graft-file 2>err &&\n+\tgrep \"$EMPTY_BLOB $EMPTY_TREE\" err &&\n+\tgrep \"$EMPTY_BLOB $EMPTY_TREE\" .git/info/grafts\n+'\n+\n test_done\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"345074","messageId":"36c8de0c9c4484ec44cc5af8f8ac752ef77bc960.1524125760.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524125760.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 4/7] Deprecate support for .git/info/grafts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-19T08:18:30Z","receivedAt":"2018-04-19T08:18:58Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The grafts feature was a convenient way to \"stitch together\" ancient\nhistory to the fresh start of linux.git.\n\nIts implementation is, however, not up to Git's standards, as there are\ntoo many ways where it can lead to surprising and unwelcome behavior.\n\nFor example, when pushing from a repository with active grafts, it is\npossible to miss commits that have been \"grafted out\", resulting in a\nbroken state on the other side.\n\nAlso, the grafts feature is limited to \"rewriting\" commits' list of\nparents, it cannot replace anything else.\n\nThe much younger feature implemented as `git replace` set out to remedy\nthose limitations and dangerous bugs.\n\nSeeing as `git replace` is pretty mature by now (since 4228e8bc98\n(replace: add --graft option, 2014-07-19) it can perform the graft\nfile's duties), it is time to deprecate support for the graft file, and\nto retire it eventually.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nReviewed-by: Stefan Beller <sbeller@google.com>\n---\n advice.c                  |  2 ++\n advice.h                  |  1 +\n commit.c                  | 10 ++++++++++\n t/t6001-rev-list-graft.sh |  9 +++++++++\n 4 files changed, 22 insertions(+)\n\ndiff --git a/advice.c b/advice.c\nindex 406efc183ba..4411704fd45 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -19,6 +19,7 @@ int advice_rm_hints = 1;\n int advice_add_embedded_repo = 1;\n int advice_ignored_hook = 1;\n int advice_waiting_for_editor = 1;\n+int advice_graft_file_deprecated = 1;\n \n static struct {\n \tconst char *name;\n@@ -42,6 +43,7 @@ static struct {\n \t{ \"addembeddedrepo\", &advice_add_embedded_repo },\n \t{ \"ignoredhook\", &advice_ignored_hook },\n \t{ \"waitingforeditor\", &advice_waiting_for_editor },\n+\t{ \"graftfiledeprecated\", &advice_graft_file_deprecated },\n \n \t/* make this an alias for backward compatibility */\n \t{ \"pushnonfastforward\", &advice_push_update_rejected }\ndiff --git a/advice.h b/advice.h\nindex 70568fa7922..9f5064e82a8 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -21,6 +21,7 @@ extern int advice_rm_hints;\n extern int advice_add_embedded_repo;\n extern int advice_ignored_hook;\n extern int advice_waiting_for_editor;\n+extern int advice_graft_file_deprecated;\n \n int git_default_advice_config(const char *var, const char *value);\n __attribute__((format (printf, 1, 2)))\ndiff --git a/commit.c b/commit.c\nindex ca474a7c112..1a5e8777617 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -12,6 +12,7 @@\n #include \"prio-queue.h\"\n #include \"sha1-lookup.h\"\n #include \"wt-status.h\"\n+#include \"advice.h\"\n \n static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);\n \n@@ -176,6 +177,15 @@ static int read_graft_file(const char *graft_file)\n \tstruct strbuf buf = STRBUF_INIT;\n \tif (!fp)\n \t\treturn -1;\n+\tif (advice_graft_file_deprecated)\n+\t\tadvise(_(\"Support for <GIT_DIR>/info/grafts is deprecated\\n\"\n+\t\t\t \"and will be removed in a future Git version.\\n\"\n+\t\t\t \"\\n\"\n+\t\t\t \"Please use \\\"git replace --convert-graft-file\\\"\\n\"\n+\t\t\t \"to convert the grafts into replace refs.\\n\"\n+\t\t\t \"\\n\"\n+\t\t\t \"Turn this message off by running\\n\"\n+\t\t\t \"\\\"git config advice.graftFileDeprecated false\\\"\"));\n \twhile (!strbuf_getwholeline(&buf, fp, '\\n')) {\n \t\t/* The format is just \"Commit Parent1 Parent2 ...\\n\" */\n \t\tstruct commit_graft *graft = read_graft_line(&buf);\ndiff --git a/t/t6001-rev-list-graft.sh b/t/t6001-rev-list-graft.sh\nindex 05ddc69cf2a..7504ba47511 100755\n--- a/t/t6001-rev-list-graft.sh\n+++ b/t/t6001-rev-list-graft.sh\n@@ -110,4 +110,13 @@ do\n \t\"\n \n done\n+\n+test_expect_success 'show advice that grafts are deprecated' '\n+\tgit show HEAD 2>err &&\n+\ttest_i18ngrep \"git replace\" err &&\n+\ttest_config advice.graftFileDeprecated false &&\n+\tgit show HEAD 2>err &&\n+\ttest_i18ngrep ! \"git replace\" err\n+'\n+\n test_done\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"345075","messageId":"b697daf8010e2fa34f6997bccf5330b53eb46f6a.1524125760.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524125760.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 5/7] filter-branch: stop suggesting to use grafts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-19T08:18:50Z","receivedAt":"2018-04-19T08:19:14Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The graft file is deprecated now, so let's use replace refs in the example\nin filter-branch's man page instead.\n\nSuggested-by: Eric Sunshine <sunshine@sunshineco.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-filter-branch.txt | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt\nindex b634043183b..1d4d2f86045 100644\n--- a/Documentation/git-filter-branch.txt\n+++ b/Documentation/git-filter-branch.txt\n@@ -288,7 +288,7 @@ git filter-branch --parent-filter \\\n or even simpler:\n \n -----------------------------------------------\n-echo \"$commit-id $graft-id\" >> .git/info/grafts\n+git replace --graft $commit-id $graft-id\n git filter-branch $graft-id..HEAD\n -----------------------------------------------\n \n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"345076","messageId":"f7fc83a745cda61f975f3d3330c91b8714fd4fcb.1524125760.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524125760.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 6/7] technical/shallow: describe the relationship with replace refs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-19T08:19:11Z","receivedAt":"2018-04-19T08:19:36Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Now that grafts are deprecated, we should start to assume that readers\nhave no idea what grafts are. So it makes more sense to describe the\n\"shallow\" feature in terms of replace refs.\n\nSuggested-by: Eric Sunshine <sunshine@sunshineco.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/technical/shallow.txt | 19 +++++++++++--------\n 1 file changed, 11 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/technical/shallow.txt b/Documentation/technical/shallow.txt\nindex 5183b154229..b3ff23c25f6 100644\n--- a/Documentation/technical/shallow.txt\n+++ b/Documentation/technical/shallow.txt\n@@ -9,14 +9,17 @@ these commits have no parents.\n *********************************************************\n \n The basic idea is to write the SHA-1s of shallow commits into\n-$GIT_DIR/shallow, and handle its contents like the contents\n-of $GIT_DIR/info/grafts (with the difference that shallow\n-cannot contain parent information).\n-\n-This information is stored in a new file instead of grafts, or\n-even the config, since the user should not touch that file\n-at all (even throughout development of the shallow clone, it\n-was never manually edited!).\n+$GIT_DIR/shallow, and handle its contents similar to replace\n+refs (with the difference that shallow does not actually\n+create those replace refs) and very much like the deprecated\n+graft file (with the difference that shallow commits will\n+always have their parents grafted away, not replaced by\n+different parents).\n+\n+This information is stored in a special-purpose file because the\n+user should not touch that file at all (even throughout\n+development of the shallow clone, it was never manually\n+edited!).\n \n Each line contains exactly one SHA-1. When read, a commit_graft\n will be constructed, which has nr_parent < 0 to make it easier\n-- \n2.17.0.windows.1.4.g7e4058d72e3\n\n\n"},{"id":"345077","messageId":"c041db4e62283e520a29039ddfec24d77d023d70.1524125760.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524125760.git.johannes.schindelin@gmx.de","subject":"[PATCH v2 7/7] technical/shallow: describe why shallow cannot use replace refs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-19T08:21:10Z","receivedAt":"2018-04-19T08:21:36Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"It is tempting to do away with commit_graft altogether (in the long\nhaul), now that grafts are deprecated.\n\nHowever, the shallow feature needs a couple of things that the replace\nrefs cannot fulfill. Let's point that out in the documentation.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n\n\tBy the way, I did not squash this into the previous patch on\n\tpurpose: in my mind, describing the relationship with replace refs\n\tis different enough from pointing out from the goal of this here\n\tpatch to merit a separate commit. And that goal is: to describe\n\twhy \"shallow\" cannot be implemented on top of \"replace refs\".\n\n Documentation/technical/shallow.txt | 7 +++++++\n 1 file changed, 7 insertions(+)\n\ndiff --git a/Documentation/technical/shallow.txt b/Documentation/technical/shallow.txt\nindex b3ff23c25f6..cb79181c2bb 100644\n--- a/Documentation/technical/shallow.txt\n+++ b/Documentation/technical/shallow.txt\n@@ -25,6 +25,13 @@ Each line contains exactly one SHA-1. When read, a commit_graft\n will be constructed, which has nr_parent < 0 to make it easier\n to discern from user provided grafts.\n \n+Note that the shallow feature could not be changed easily to\n+use replace refs: a commit containing a `mergetag` is not allowed\n+to be replaced, not even by a root commit. Such a commit can be\n+made shallow, though. Also, having a `shallow` file explicitly\n+listing all the commits made shallow makes it a *lot* easier to\n+do shallow-specific things such as to deepen the history.\n+\n Since fsck-objects relies on the library to read the objects,\n it honours shallow commits automatically.\n \n-- \n2.17.0.windows.1.4.g7e4058d72e3\n"},{"id":"345083","messageId":"CAP8UFD3cH4JkKo97o4k+BW=sdaFviJR06R1V_JqS9FkwuP7ZKw@mail.gmail.com","threadId":"48278","inReplyTo":"cecb82973f80fdd2db940410db2a7c3b23107c4b.1524125760.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 2/7] replace: introduce --convert-graft-file","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2018-04-19T10:09:36Z","receivedAt":"2018-04-19T10:09:41Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Thu, Apr 19, 2018 at 10:17 AM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n\n> @@ -87,9 +88,13 @@ OPTIONS\n>         content as <commit> except that its parents will be\n>         [<parent>...] instead of <commit>'s parents. A replacement ref\n>         is then created to replace <commit> with the newly created\n> -       commit. See contrib/convert-grafts-to-replace-refs.sh for an\n> -       example script based on this option that can convert grafts to\n> -       replace refs.\n> +       commit. Use `--convert-graft-file` to convert a\n> +       `$GIT_DIR/info/grafts` file use replace refs instead.\n\ns/file use replace refs/file to use replace refs/\n\n> +--convert-graft-file::\n> +       Creates graft commits for all entries in `$GIT_DIR/info/grafts`\n> +       and deletes that file upon success. The purpose is to help users\n> +       with transitioning off of the now-deprecated graft file.\n\nI wonder if it would be better to rename the file to \"old_grafts\" or\nsomething, and perhaps tell the user that we renamed it and it can now\nbe either deleted or moved somewhere else.\n\nThanks for working on this,\nChristian.\n"},{"id":"345181","messageId":"xmqqh8o6d8rg.fsf@gitster-ct.c.googlers.com","threadId":"48278","inReplyTo":"1f1c64314088383570dd2232e08a88c8df3298f5.1524125760.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 1/7] replace: \"libify\" create_graft()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-04-20T00:25:07Z","receivedAt":"2018-04-20T00:25:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n> It is quite convenient to simply die() in builtins, in the absence of\n> proper exception handling, because it allows us to just go belly up\n> without having to implement error handling chains.\n>\n> Of course, for reusable library functions, this is a big no-no, so we\n> (try to) restrict the usage of die() to one-shot commands, i.e. places\n> where we know that the caller does not want to, say, give the user a\n> useful high-level error message, i.e. a message that the function calling\n> die() could not possibly know.\n>\n> The problem with this reasoning is that sooner or later, pretty much all\n> useful functions will *need* to be libified: the more useful a function,\n> the more likely it is to be called from a call chain where the outer\n> function implements a high-level operation that needs to provide\n> additional advice to the user in case of failure.\n>\n> This is the case here: the create_graft() function is useful enough to be\n> called in a loop, say, in the upcoming patch to convert a graft file in\n> one fell swoop. Therefore, this function must not be allowed to die(), nor\n> any of its callees.\n\nAll of the first three paragraphs are already widely known to the\nproject, and I do not think you need to verbosely repeat it, if\nbrevity demands it.\n\nOne thing that the proposed log message for this step would be far\nmore useful to say is that the current callers of create_graft() is\nfine with the behaviour change (i.e. prepared to act on an error\nreturn).\n\nAlso it may want to say why the other die() we see in this function\nin the pre-context is OK.  I actually think it is not OK and should\nreturn error(not a valid object name) the same way (I suspect that\nthis is mere omission/mistake, and you did not intend to leave it\ndying).  As long as the caller is prepared to deal with an error\nreturn, that is, which was the previous point.\n\nFWIW, I fully agree with the goal of making this (or other pieces\nthat would be useful if they were reusable) reusable.\n\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  builtin/replace.c | 4 +++-\n>  1 file changed, 3 insertions(+), 1 deletion(-)\n>\n> diff --git a/builtin/replace.c b/builtin/replace.c\n> index 935647be6bd..43264f0998e 100644\n> --- a/builtin/replace.c\n> +++ b/builtin/replace.c\n> @@ -395,7 +395,9 @@ static int create_graft(int argc, const char **argv, int force)\n>  \n>  \tif (get_oid(old_ref, &old_oid) < 0)\n>  \t\tdie(_(\"Not a valid object name: '%s'\"), old_ref);\n> -\tcommit = lookup_commit_or_die(&old_oid, old_ref);\n> +\tcommit = lookup_commit_reference(&old_oid);\n> +\tif (!commit)\n> +\t\treturn error(_(\"could not parse %s\"), old_ref);\n>  \n>  \tbuffer = get_commit_buffer(commit, &size);\n>  \tstrbuf_add(&buf, buffer, size);\n"},{"id":"345182","messageId":"xmqqd0yud6vr.fsf@gitster-ct.c.googlers.com","threadId":"48278","inReplyTo":"cecb82973f80fdd2db940410db2a7c3b23107c4b.1524125760.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 2/7] replace: introduce --convert-graft-file","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-04-20T01:05:44Z","receivedAt":"2018-04-20T01:05:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n> This option is intended to help with the transition away from the\n> now-deprecated graft file.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  Documentation/git-replace.txt | 11 +++++--\n>  builtin/replace.c             | 59 ++++++++++++++++++++++++++++++++++-\n>  2 files changed, 66 insertions(+), 4 deletions(-)\n\nI expected you to remove convert-grafts-to-replace-refs.sh from the\ncontrib/ section in the same patch, actually.  FWIW, I think it is a\nmuch better approach to give the first-class UI for transition like\nthis patch does than \"go fish for a good way to transition yourself,\nwe may have something useful in contrib/\", which is what we had so\nfar.\n\n> diff --git a/Documentation/git-replace.txt b/Documentation/git-replace.txt\n> ...\n> @@ -87,9 +88,13 @@ 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. 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> +\tcommit. Use `--convert-graft-file` to convert a\n> +\t`$GIT_DIR/info/grafts` file use replace refs instead.\n> +\n\nNice.\n\n> diff --git a/builtin/replace.c b/builtin/replace.c\n> index 43264f0998e..4cdc00a96df 100644\n> --- a/builtin/replace.c\n> +++ b/builtin/replace.c\n> @@ -20,6 +20,7 @@ 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 [-f] --convert-graft-file\"),\n>  \tN_(\"git replace -d <object>...\"),\n>  \tN_(\"git replace [--format=<format>] [-l [<pattern>]]\"),\n>  \tNULL\n> @@ -423,6 +424,53 @@ static int create_graft(int argc, const char **argv, int force)\n>  \treturn replace_object_oid(old_ref, &old_oid, \"replacement\", &new_oid, force);\n>  }\n>  \n> +static int convert_graft_file(int force)\n> +{\n> +\tconst char *graft_file = get_graft_file();\n> +\tFILE *fp = fopen_or_warn(graft_file, \"r\");\n> +\tstruct strbuf buf = STRBUF_INIT, err = STRBUF_INIT;\n> +\tstruct argv_array args = ARGV_ARRAY_INIT;\n> +\n> +\tif (!fp)\n> +\t\treturn -1;\n\nReturning silently is fine as fopen_or_warn() would have said\nsomething already.  Good.\n\n> +\twhile (strbuf_getline(&buf, fp) != EOF) {\n> +\t\tint i = 0, j;\n> +\n> +\t\twhile (i != buf.len) {\n> +\t\t\tchar save;\n> +\n> +\t\t\tfor (j = i; j < buf.len && !isspace(buf.buf[j]); j++)\n> +\t\t\t\t; /* look further */\n> +\t\t\tsave = buf.buf[j];\n> +\t\t\tbuf.buf[j] = '\\0';\n> +\t\t\targv_array_push(&args, buf.buf + i);\n> +\t\t\tbuf.buf[j] = save;\n\nIt's a shame that we do not have a helper that splits the contents\nof a strbuf at SP and shove the result into an argv_array(). [*1*]\n\n*1* There is one that splits into an array of strbuf but the point\nof splitting is often that these split pieces are the final thing we\nwant, and placing them in separate strbuf (whose strength is that\ncontents are easily manipulatable) is pointless.\n\n> +\n> +\t\t\twhile (j < buf.len && isspace(buf.buf[j]))\n> +\t\t\t\tj++;\n> +\t\t\ti = j;\n\nOne difference I notice while comparing this with what is done by\ncontrib/convert-grafts-to-replace-refs.sh is that this does not\nskip a line that begins with # or SP.  I offhand do not know what\nthe point of skipping a line that begins with a SP, but I suspect\nthat skipping a line that begins with \"#\" is a desirable thing to\ndo, because commit.c::read_graft_line() does know that such a line\nis a valid comment.\n\n> +\t\t}\n> +\n> +\t\tif (create_graft(args.argc, args.argv, force))\n> +\t\t\tstrbuf_addf(&err, \"\\n\\t%s\", buf.buf);\n> +\n> +\t\targv_array_clear(&args);\n> +\t\tstrbuf_reset(&buf);\n\nStrictly speaking, this reset is redundant, as getline() will always\nstuff the line into a fresh buffer (and after the loop there\ncorrectly is a release).\n\n> +\t}\n> +\n> +\tstrbuf_release(&buf);\n> +\targv_array_clear(&args);\n> +\n> +\tif (!err.len)\n> +\t\treturn unlink_or_warn(graft_file);\n> +\twarning(_(\"could not convert the following graft(s):\\n%s\"), err.buf);\n> +\tstrbuf_release(&err);\n\ncommit.c::read_graft_file() seems to ignore a broken graft line and\nsalvages other lines, and this one follows suit, which is good.\n\nThe remaining die() I pointed out in 1/2 can safely be turned into\nreturn error() for this caller (I didn't check for existing callers,\nthough) and would automatically do the right thing.  The real\nconsumer of the graft file, commit.c::read_graft_line(), shows an\nerror when oid cannot be parsed, and the above code, when\ncreate_graft() is updated to return an error instead of dying, would\nappend the problematic record to buf.buf in the code above.\n\nLooking basically-good modulo minor nits.\n"},{"id":"345183","messageId":"xmqq7ep2d665.fsf@gitster-ct.c.googlers.com","threadId":"48278","inReplyTo":"cf8169c80f08612185f81927ee6ff22e087b2835.1524125760.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v2 3/7] Add a test for `git replace --convert-graft-file`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-04-20T01:21:06Z","receivedAt":"2018-04-20T01:21:12Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n> The proof, as the saying goes, lies in the pudding. So here is a\n> regression test that not only demonstrates what the option is supposed to\n> accomplish, but also demonstrates that it does accomplish it.\n\nThe above spreads the misconception that the value of the test is\n\"what I wrote works, look here\".  It is not.  \"Here is how this\nthing is supposed to work.  You are free to improve it, but do not\nbreak the basic promises these tests outline\" to protect the\nresulting system is.\n\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  t/t6050-replace.sh | 20 ++++++++++++++++++++\n>  1 file changed, 20 insertions(+)\n>\n> diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh\n> index c630aba657e..77ded6df653 100755\n> --- a/t/t6050-replace.sh\n> +++ b/t/t6050-replace.sh\n> @@ -444,4 +444,24 @@ test_expect_success GPG '--graft on a commit with a mergetag' '\n>  \tgit replace -d $HASH10\n>  '\n>  \n> +test_expect_success '--convert-graft-file' '\n> +\t: add and convert graft file &&\n> +\tprintf \"%s\\n%s %s\\n%s\\n\" \\\n> +\t\t$(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \\\n> +\t\t>.git/info/grafts &&\n\nI find the above much less readbale than something like\n\n\t{\n\t\tgit rev-parse HEAD^^\n\t\tgit rev-parse HEAD^ HEAD^^\n\t\tgit rev-parse HEAD^2\n\t} >.git/info/grafts\n\nbecause printf formatting string must be deciphered and then matched\nagainst the order and number of rev-parse arguments (and printf's\nability to happily accept more args than the placeholders does not\nhelp in readablity---the reader needs to verify that the code is not\ndoing anything overly clever exploiting that ability) before I can\nfigure out who's parent of whom.\n\nOf course, it saves a few spawning of subprocesses; I am not sure if\nthat is worth the loss of readability in this case, though.\n\n> +\tgit replace --convert-graft-file &&\n> +\ttest_path_is_missing .git/info/grafts &&\n\nSuccessful conversion exits with success, and removes the grafts\nfile.  Good.\n\n> +\t: verify that the history is now \"grafted\" &&\n> +\tgit rev-list HEAD >out &&\n> +\ttest_line_count = 4 out &&\n\nWe are pretending that HEAD^ has only one parent HEAD^^ and that\nHEAD^^ and HEAD^2 are roots with the above graft.  We want the\nresulting \"apparent\" history to also show that fact, and the\ntraversal should stop after counting HEAD, HEAD^, HEAD^2 (which is\nroot), and HEAD^^ (which is also root).  Good.\n\n> +\t: create invalid graft file and verify that it is not deleted &&\n> +\ttest_when_finished \"rm -f .git/info/grafts\" &&\n> +\techo $EMPTY_BLOB $EMPTY_TREE >.git/info/grafts &&\n> +\ttest_must_fail git replace --convert-graft-file 2>err &&\n> +\tgrep \"$EMPTY_BLOB $EMPTY_TREE\" err &&\n> +\tgrep \"$EMPTY_BLOB $EMPTY_TREE\" .git/info/grafts\n> +'\n> +\n>  test_done\n"},{"id":"345211","messageId":"86zi1ydrjt.fsf@gmail.com","threadId":"48278","inReplyTo":"xmqqh8o6d8rg.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 1/7] replace: \"libify\" create_graft()","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2018-04-20T11:51:34Z","receivedAt":"2018-04-20T11:51:41Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n>\n>> It is quite convenient to simply die() in builtins, in the absence of\n>> proper exception handling, because it allows us to just go belly up\n>> without having to implement error handling chains.\n>>\n>> Of course, for reusable library functions, this is a big no-no, so we\n>> (try to) restrict the usage of die() to one-shot commands, i.e. places\n>> where we know that the caller does not want to, say, give the user a\n>> useful high-level error message, i.e. a message that the function calling\n>> die() could not possibly know.\n>>\n>> The problem with this reasoning is that sooner or later, pretty much all\n>> useful functions will *need* to be libified: the more useful a function,\n>> the more likely it is to be called from a call chain where the outer\n>> function implements a high-level operation that needs to provide\n>> additional advice to the user in case of failure.\n>>\n>> This is the case here: the create_graft() function is useful enough to be\n>> called in a loop, say, in the upcoming patch to convert a graft file in\n>> one fell swoop. Therefore, this function must not be allowed to die(), nor\n>> any of its callees.\n>\n> All of the first three paragraphs are already widely known to the\n> project, and I do not think you need to verbosely repeat it, if\n> brevity demands it.\n\n[taking the role of Advocatus Diaboli]\n\nWell, this information (first three paragraphs) is maybe widely known to\nthe project, but do we have it explicitely documented somwehere, like\nCodingGuidelines, or FAQ on some web site?\n\n> One thing that the proposed log message for this step would be far\n> more useful to say is that the current callers of create_graft() is\n> fine with the behaviour change (i.e. prepared to act on an error\n> return).\n\nRight.\n\n>\n> Also it may want to say why the other die() we see in this function\n> in the pre-context is OK.  I actually think it is not OK and should\n> return error(not a valid object name) the same way (I suspect that\n> this is mere omission/mistake, and you did not intend to leave it\n> dying).  As long as the caller is prepared to deal with an error\n> return, that is, which was the previous point.\n\n[taking the role of Advocatus Diaboli]\n\nMaybe they are errors that caller cannot handle sanely in any way?\nIf I understand things correctly the problem is with parameter to the\n--graft option...\n\n>\n> FWIW, I fully agree with the goal of making this (or other pieces\n> that would be useful if they were reusable) reusable.\n>\n>> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n>> ---\n>>  builtin/replace.c | 4 +++-\n>>  1 file changed, 3 insertions(+), 1 deletion(-)\n>>\n>> diff --git a/builtin/replace.c b/builtin/replace.c\n>> index 935647be6bd..43264f0998e 100644\n>> --- a/builtin/replace.c\n>> +++ b/builtin/replace.c\n>> @@ -395,7 +395,9 @@ static int create_graft(int argc, const char **argv, int force)\n>>  \n>>  \tif (get_oid(old_ref, &old_oid) < 0)\n>>  \t\tdie(_(\"Not a valid object name: '%s'\"), old_ref);\n>> -\tcommit = lookup_commit_or_die(&old_oid, old_ref);\n>> +\tcommit = lookup_commit_reference(&old_oid);\n>> +\tif (!commit)\n>> +\t\treturn error(_(\"could not parse %s\"), old_ref);\n>>  \n>>  \tbuffer = get_commit_buffer(commit, &size);\n>>  \tstrbuf_add(&buf, buffer, size);\n"},{"id":"345226","messageId":"nycvar.QRO.7.76.6.1804201540310.4241@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"48278","inReplyTo":"CAP8UFD3cH4JkKo97o4k+BW=sdaFviJR06R1V_JqS9FkwuP7ZKw@mail.gmail.com","subject":"Re: [PATCH v2 2/7] replace: introduce --convert-graft-file","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-20T13:57:46Z","receivedAt":"2018-04-20T13:58:11Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Christian,\n\nOn Thu, 19 Apr 2018, Christian Couder wrote:\n\n> On Thu, Apr 19, 2018 at 10:17 AM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> \n> > @@ -87,9 +88,13 @@ OPTIONS\n> >         content as <commit> except that its parents will be\n> >         [<parent>...] instead of <commit>'s parents. A replacement ref\n> >         is then created to replace <commit> with the newly created\n> > -       commit. See contrib/convert-grafts-to-replace-refs.sh for an\n> > -       example script based on this option that can convert grafts to\n> > -       replace refs.\n> > +       commit. Use `--convert-graft-file` to convert a\n> > +       `$GIT_DIR/info/grafts` file use replace refs instead.\n> \n> s/file use replace refs/file to use replace refs/\n\nOuch... thanks for catching this!\n\n> > +--convert-graft-file::\n> > +       Creates graft commits for all entries in `$GIT_DIR/info/grafts`\n> > +       and deletes that file upon success. The purpose is to help users\n> > +       with transitioning off of the now-deprecated graft file.\n> \n> I wonder if it would be better to rename the file to \"old_grafts\" or\n> something, and perhaps tell the user that we renamed it and it can now\n> be either deleted or moved somewhere else.\n\nI do not think so. Those old_grafts will only lie around uselessly... and\nare no longer necessary.\n\nCiao,\nDscho\n"},{"id":"345228","messageId":"nycvar.QRO.7.76.6.1804201640560.4241@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"48278","inReplyTo":"xmqqd0yud6vr.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 2/7] replace: introduce --convert-graft-file","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-20T15:26:59Z","receivedAt":"2018-04-20T15:27:26Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Fri, 20 Apr 2018, Junio C Hamano wrote:\n\n> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> \n> > This option is intended to help with the transition away from the\n> > now-deprecated graft file.\n> >\n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> >  Documentation/git-replace.txt | 11 +++++--\n> >  builtin/replace.c             | 59 ++++++++++++++++++++++++++++++++++-\n> >  2 files changed, 66 insertions(+), 4 deletions(-)\n> \n> I expected you to remove convert-grafts-to-replace-refs.sh from the\n> contrib/ section in the same patch, actually.\n\nSorry, I was still under the impression that contrib/ was somewhat off\nlimits when replacing scripts by C code (it used to be the rule, but I did\nsee that the contrib/examples/*.sh files went poof).\n\nWill change.\n\n> FWIW, I think it is a much better approach to give the first-class UI\n> for transition like this patch does than \"go fish for a good way to\n> transition yourself, we may have something useful in contrib/\", which is\n> what we had so far.\n\nObviously I agree. It just makes for such a vastly superior user\nexperience to get an explanation that yes, something has been deprecated,\nbut Do Not Panic, this is what you do to get out of this fix.\n\nIn the same vein, I considered some real hackery that would make the\ndeprecation notice in commit.c less annoying: I wanted to use some\ntentative date in the future as cut-off, and then use some arithmetic\nbased on the time left until that date to show the deprecation notice\nincreasingly more often.\n\nHowever, this would have made it really frustrating for users seeing the\nnotice fly by, say, in a lengthy script's output, and then trying to\nreproduce the message. So as much fun as it would have been to come up\nwith that formula, I refrained from it.\n\n> > +\twhile (strbuf_getline(&buf, fp) != EOF) {\n> > +\t\tint i = 0, j;\n> > +\n> > +\t\twhile (i != buf.len) {\n> > +\t\t\tchar save;\n> > +\n> > +\t\t\tfor (j = i; j < buf.len && !isspace(buf.buf[j]); j++)\n> > +\t\t\t\t; /* look further */\n> > +\t\t\tsave = buf.buf[j];\n> > +\t\t\tbuf.buf[j] = '\\0';\n> > +\t\t\targv_array_push(&args, buf.buf + i);\n> > +\t\t\tbuf.buf[j] = save;\n> \n> It's a shame that we do not have a helper that splits the contents\n> of a strbuf at SP and shove the result into an argv_array(). [*1*]\n> \n> *1* There is one that splits into an array of strbuf but the point\n> of splitting is often that these split pieces are the final thing we\n> want, and placing them in separate strbuf (whose strength is that\n> contents are easily manipulatable) is pointless.\n\nFWIW I considered introducing such a helper. But I really want to have the\nfull line to show to the user, so I went with the current version.\n\nBased on your comment, I realized that since argv_array_push() duplicates\nthe string *anyway*, I could have implemented argv_array_split().\n\nBriefly deterred by the insight that some readers will invariably think\nthat this function performs de-quoting, Unix shell style, I almost decided\nagainst that.\n\nBut only almost.\n\nIf anybody needs a de-quoting version of argv_array_split(), or a version\nthat uses a different delimiter than white-space, my version should\nprovide a neat starting point (new parameters should be added for those\npurposes, of course, since we really need a non-de-quoting version in\n--convert-graft-file).\n\nSo the next iteration of this patch series will sport a shiny new\nargv_array_split(). Enjoy.\n\n> > +\n> > +\t\t\twhile (j < buf.len && isspace(buf.buf[j]))\n> > +\t\t\t\tj++;\n> > +\t\t\ti = j;\n> \n> One difference I notice while comparing this with what is done by\n> contrib/convert-grafts-to-replace-refs.sh is that this does not\n> skip a line that begins with # or SP.  I offhand do not know what\n> the point of skipping a line that begins with a SP, but I suspect\n> that skipping a line that begins with \"#\" is a desirable thing to\n> do, because commit.c::read_graft_line() does know that such a line\n> is a valid comment.\n\nRiiiight! I meant to look at the parser to verify that I do the same, but\nforgot (I had the incorrect recollection that the graft file cannot\ncontain comments or empty lines).\n\nSo I remedied that now. Including correct handling of empty lines.\n\n> > +\t\t}\n> > +\n> > +\t\tif (create_graft(args.argc, args.argv, force))\n> > +\t\t\tstrbuf_addf(&err, \"\\n\\t%s\", buf.buf);\n> > +\n> > +\t\targv_array_clear(&args);\n> > +\t\tstrbuf_reset(&buf);\n> \n> Strictly speaking, this reset is redundant, as getline() will always\n> stuff the line into a fresh buffer (and after the loop there\n> correctly is a release).\n\nGood point. I did assume that strbuf_getline() would append, and I was\nwrong. I dropped the needless strbuf_reset() call.\n\n> > +\t}\n> > +\n> > +\tstrbuf_release(&buf);\n> > +\targv_array_clear(&args);\n> > +\n> > +\tif (!err.len)\n> > +\t\treturn unlink_or_warn(graft_file);\n> > +\twarning(_(\"could not convert the following graft(s):\\n%s\"), err.buf);\n> > +\tstrbuf_release(&err);\n> \n> commit.c::read_graft_file() seems to ignore a broken graft line and\n> salvages other lines, and this one follows suit, which is good.\n\nThanks.\n\n> The remaining die() I pointed out in 1/2 can safely be turned into\n> return error() for this caller (I didn't check for existing callers,\n> though) and would automatically do the right thing.  The real\n> consumer of the graft file, commit.c::read_graft_line(), shows an\n> error when oid cannot be parsed, and the above code, when\n> create_graft() is updated to return an error instead of dying, would\n> append the problematic record to buf.buf in the code above.\n\nIt is a *lot* worse than just one remaining die(), unfortunately:\ncreate_graft() calls replace_parents() and check_mergetags(), both without\nchecking their return value. Because neither return anything. Because they\nall die (cue Dash from The Incredibles: We're gonna DIIIIIE!). Also,\nreplace_object_oid(), while it returns an int indicating an error, chooses\nto die() in plenty of places. After following down to for_each_mergetag(),\nexport_object() and check_ref_valid(), I decided to just go ahead and\nlibify the entire builtin/replace.c, which seemed to be the vastly more\nefficient route, and I really wish we would start deprecating die().\nThanks for sending me down that rabbit hole ;-)\n\nNote: there is a call to get_commit_buffer() which still die()s if it\ncannot read the object or if it is not a commit. If we care enough about\nthe concocted scenario where somebody would have installed a graft file\nwith a line that references, say, a tree object instead of a commit object\nin its first oid, we need to do something about that: the graft line would\nsimply have been ignored, as grafts were only used when a corresponding\ncommit was parsed (which would never happen for the concocted example I\ngave). I am rather certain, though, that I do not want to care about such\na scenario: a user would have to go out of their way to get into that\nsituation.\n\nCiao,\nDscho\n"},{"id":"345230","messageId":"nycvar.QRO.7.76.6.1804201728210.4241@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"48278","inReplyTo":"xmqq7ep2d665.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 3/7] Add a test for `git replace --convert-graft-file`","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-20T15:31:18Z","receivedAt":"2018-04-20T15:31:44Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Fri, 20 Apr 2018, Junio C Hamano wrote:\n\n> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> \n> > The proof, as the saying goes, lies in the pudding. So here is a\n> > regression test that not only demonstrates what the option is supposed to\n> > accomplish, but also demonstrates that it does accomplish it.\n> \n> The above spreads the misconception that the value of the test is\n> \"what I wrote works, look here\".  It is not.  \"Here is how this\n> thing is supposed to work.  You are free to improve it, but do not\n> break the basic promises these tests outline\" to protect the\n> resulting system is.\n\nI am but a lousy foreigner, but to me, basically we said the same.\n\n> > diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh\n> > index c630aba657e..77ded6df653 100755\n> > --- a/t/t6050-replace.sh\n> > +++ b/t/t6050-replace.sh\n> > @@ -444,4 +444,24 @@ test_expect_success GPG '--graft on a commit with a mergetag' '\n> >  \tgit replace -d $HASH10\n> >  '\n> >  \n> > +test_expect_success '--convert-graft-file' '\n> > +\t: add and convert graft file &&\n> > +\tprintf \"%s\\n%s %s\\n%s\\n\" \\\n> > +\t\t$(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \\\n> > +\t\t>.git/info/grafts &&\n> \n> I find the above much less readbale than something like\n> \n> \t{\n> \t\tgit rev-parse HEAD^^\n> \t\tgit rev-parse HEAD^ HEAD^^\n> \t\tgit rev-parse HEAD^2\n> \t} >.git/info/grafts\n\nWell, don't you know, that is how my first version looked. It failed,\nthough, as `git rev-parse HEAD^ HEAD^^` outputs two *lines*.\n\nAnd the version with a here-doc and inlined `$(git rev-parse ...)` really\nlooks even uglier.\n\n> because printf formatting string must be deciphered and then matched\n> against the order and number of rev-parse arguments (and printf's\n> ability to happily accept more args than the placeholders does not\n> help in readablity---the reader needs to verify that the code is not\n> doing anything overly clever exploiting that ability) before I can\n> figure out who's parent of whom.\n> \n> Of course, it saves a few spawning of subprocesses; I am not sure if\n> that is worth the loss of readability in this case, though.\n\nI disagree that it is so horrible to read. If a regression occurs, you\nwill have the .git/info/grafts file as a reference anyway, so there is\nlittle you need to decipher.\n\nCiao,\nDscho\n"},{"id":"345231","messageId":"nycvar.QRO.7.76.6.1804201732150.4241@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"48278","inReplyTo":"xmqqh8o6d8rg.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v2 1/7] replace: \"libify\" create_graft()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-20T15:32:38Z","receivedAt":"2018-04-20T15:33:02Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Fri, 20 Apr 2018, Junio C Hamano wrote:\n\n> FWIW, I fully agree with the goal of making this (or other pieces\n> that would be useful if they were reusable) reusable.\n\nGreat! That's something I'll gladly take into my break.\n\nCiao,\nDscho\n"},{"id":"345278","messageId":"cover.1524262793.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524125760.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 00/11] Deprecate .git/info/grafts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-20T22:20:18Z","receivedAt":"2018-04-20T22:20:45Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"It is fragile, as there is no way for the revision machinery to say \"but\nnow I want to traverse the graph ignoring the graft file\" e.g. when\npushing commits to a remote repository (which, as a consequence, can\nmiss commits).\n\nAnd we already have a better solution with `git replace --graft <comit>\n[<parent>...]`.\n\nChanges since v2:\n\n- Fixed tyop in the description of --graft where it suggests\n  --convert-graft-file instead.\n\n- Dropped the rant from the \"libify create_graft()\" commit.\n\n- Changed a die(\"BUG: ...\") call to BUG() (instead of libifying it).\n\n- Truly libified create_graft() by replacing every single instance where die()\n  (or lookup_commit_or_die()) was called with a proper error(), adding resource\n  handling to avoid leaks, so that that rant that was dropped from the commit\n  message would now truly be merited.\n\n- Graft files with commented lines are now handled correctly.\n\n- Graft files with empty lines are now handled correctly, too.\n\n- The graft file parser is now based on a shiny new argv_array_split().\n\n- The script in contrib/ whose functionality is superseded by `git replace\n  --convert-graft-file` was removed.\n\n\nJohannes Schindelin (11):\n  argv_array: offer to split a string by whitespace\n  commit: Let the callback of for_each_mergetag return on error\n  replace: avoid using die() to indicate a bug\n  replace: \"libify\" create_graft() and callees\n  replace: introduce --convert-graft-file\n  Add a test for `git replace --convert-graft-file`\n  Deprecate support for .git/info/grafts\n  filter-branch: stop suggesting to use grafts\n  technical/shallow: describe the relationship with replace refs\n  technical/shallow: describe why shallow cannot use replace refs\n  Remove obsolete script to convert grafts to replace refs\n\n Documentation/git-filter-branch.txt       |   2 +-\n Documentation/git-replace.txt             |  11 +-\n Documentation/technical/shallow.txt       |  24 ++-\n advice.c                                  |   2 +\n advice.h                                  |   1 +\n argv-array.c                              |  20 +++\n argv-array.h                              |   1 +\n builtin/replace.c                         | 189 +++++++++++++++-------\n commit.c                                  |  18 ++-\n commit.h                                  |   4 +-\n contrib/convert-grafts-to-replace-refs.sh |  28 ----\n log-tree.c                                |  13 +-\n t/t6001-rev-list-graft.sh                 |   9 ++\n t/t6050-replace.sh                        |  20 +++\n 14 files changed, 235 insertions(+), 107 deletions(-)\n delete mode 100755 contrib/convert-grafts-to-replace-refs.sh\n\n\nbase-commit: fe0a9eaf31dd0c349ae4308498c33a5c3794b293\nPublished-As: https://github.com/dscho/git/releases/tag/deprecate-grafts-v3\nFetch-It-Via: git fetch https://github.com/dscho/git deprecate-grafts-v3\n\nInterdiff vs v2:\n diff --git a/Documentation/git-replace.txt b/Documentation/git-replace.txt\n index 4dc0686f7d6..246dc9943c2 100644\n --- a/Documentation/git-replace.txt\n +++ b/Documentation/git-replace.txt\n @@ -89,7 +89,7 @@ OPTIONS\n  \t[<parent>...] instead of <commit>'s parents. A replacement ref\n  \tis then created to replace <commit> with the newly created\n  \tcommit. Use `--convert-graft-file` to convert a\n -\t`$GIT_DIR/info/grafts` file use replace refs instead.\n +\t`$GIT_DIR/info/grafts` file and use replace refs instead.\n  \n  --convert-graft-file::\n  \tCreates graft commits for all entries in `$GIT_DIR/info/grafts`\n diff --git a/argv-array.c b/argv-array.c\n index 5d370fa3366..cb5bcd2c064 100644\n --- a/argv-array.c\n +++ b/argv-array.c\n @@ -64,6 +64,26 @@ void argv_array_pop(struct argv_array *array)\n  \tarray->argc--;\n  }\n  \n +void argv_array_split(struct argv_array *array, const char *to_split)\n +{\n +\twhile (isspace(*to_split))\n +\t\tto_split++;\n +\tfor (;;) {\n +\t\tconst char *p = to_split;\n +\n +\t\tif (!*p)\n +\t\t\tbreak;\n +\n +\t\twhile (*p && !isspace(*p))\n +\t\t\tp++;\n +\t\targv_array_push_nodup(array, xstrndup(to_split, p - to_split));\n +\n +\t\twhile (isspace(*p))\n +\t\t\tp++;\n +\t\tto_split = p;\n +\t}\n +}\n +\n  void argv_array_clear(struct argv_array *array)\n  {\n  \tif (array->argv != empty_argv) {\n diff --git a/argv-array.h b/argv-array.h\n index 29056e49a12..c7c397695df 100644\n --- a/argv-array.h\n +++ b/argv-array.h\n @@ -19,6 +19,7 @@ LAST_ARG_MUST_BE_NULL\n  void argv_array_pushl(struct argv_array *, ...);\n  void argv_array_pushv(struct argv_array *, const char **);\n  void argv_array_pop(struct argv_array *);\n +void argv_array_split(struct argv_array *, const char *);\n  void argv_array_clear(struct argv_array *);\n  const char **argv_array_detach(struct argv_array *);\n  \n diff --git a/builtin/replace.c b/builtin/replace.c\n index 4cdc00a96df..6b3e0301e90 100644\n --- a/builtin/replace.c\n +++ b/builtin/replace.c\n @@ -80,9 +80,9 @@ static int list_replace_refs(const char *pattern, const char *format)\n  \telse if (!strcmp(format, \"long\"))\n  \t\tdata.format = REPLACE_FORMAT_LONG;\n  \telse\n -\t\tdie(\"invalid replace format '%s'\\n\"\n -\t\t    \"valid formats are 'short', 'medium' and 'long'\\n\",\n -\t\t    format);\n +\t\treturn error(\"invalid replace format '%s'\\n\"\n +\t\t\t     \"valid formats are 'short', 'medium' and 'long'\\n\",\n +\t\t\t     format);\n  \n  \tfor_each_replace_ref(show_reference, (void *)&data);\n  \n @@ -135,7 +135,7 @@ static int delete_replace_ref(const char *name, const char *ref,\n  \treturn 0;\n  }\n  \n -static void check_ref_valid(struct object_id *object,\n +static int check_ref_valid(struct object_id *object,\n  \t\t\t    struct object_id *prev,\n  \t\t\t    struct strbuf *ref,\n  \t\t\t    int force)\n @@ -143,12 +143,13 @@ static void check_ref_valid(struct object_id *object,\n  \tstrbuf_reset(ref);\n  \tstrbuf_addf(ref, \"%s%s\", git_replace_ref_base, oid_to_hex(object));\n  \tif (check_refname_format(ref->buf, 0))\n -\t\tdie(\"'%s' is not a valid ref name.\", ref->buf);\n +\t\treturn error(\"'%s' is not a valid ref name.\", ref->buf);\n  \n  \tif (read_ref(ref->buf, prev))\n  \t\toidclr(prev);\n  \telse if (!force)\n -\t\tdie(\"replace ref '%s' already exists\", ref->buf);\n +\t\treturn error(\"replace ref '%s' already exists\", ref->buf);\n +\treturn 0;\n  }\n  \n  static int replace_object_oid(const char *object_ref,\n @@ -162,28 +163,31 @@ static int replace_object_oid(const char *object_ref,\n  \tstruct strbuf ref = STRBUF_INIT;\n  \tstruct ref_transaction *transaction;\n  \tstruct strbuf err = STRBUF_INIT;\n +\tint res = 0;\n  \n  \tobj_type = oid_object_info(object, NULL);\n  \trepl_type = oid_object_info(repl, NULL);\n  \tif (!force && obj_type != repl_type)\n -\t\tdie(\"Objects must be of the same type.\\n\"\n -\t\t    \"'%s' points to a replaced object of type '%s'\\n\"\n -\t\t    \"while '%s' points to a replacement object of type '%s'.\",\n -\t\t    object_ref, type_name(obj_type),\n -\t\t    replace_ref, type_name(repl_type));\n -\n -\tcheck_ref_valid(object, &prev, &ref, force);\n +\t\treturn error(\"Objects must be of the same type.\\n\"\n +\t\t\t     \"'%s' points to a replaced object of type '%s'\\n\"\n +\t\t\t     \"while '%s' points to a replacement object of \"\n +\t\t\t     \"type '%s'.\",\n +\t\t\t     object_ref, type_name(obj_type),\n +\t\t\t     replace_ref, type_name(repl_type));\n +\n +\tif (check_ref_valid(object, &prev, &ref, force))\n +\t\treturn -1;\n  \n  \ttransaction = ref_transaction_begin(&err);\n  \tif (!transaction ||\n  \t    ref_transaction_update(transaction, ref.buf, repl, &prev,\n  \t\t\t\t   0, NULL, &err) ||\n  \t    ref_transaction_commit(transaction, &err))\n -\t\tdie(\"%s\", err.buf);\n +\t\tres = error(\"%s\", err.buf);\n  \n  \tref_transaction_free(transaction);\n  \tstrbuf_release(&ref);\n -\treturn 0;\n +\treturn res;\n  }\n  \n  static int replace_object(const char *object_ref, const char *replace_ref, int force)\n @@ -191,9 +195,11 @@ static int replace_object(const char *object_ref, const char *replace_ref, int f\n  \tstruct object_id object, repl;\n  \n  \tif (get_oid(object_ref, &object))\n -\t\tdie(\"Failed to resolve '%s' as a valid ref.\", object_ref);\n +\t\treturn error(\"Failed to resolve '%s' as a valid ref.\",\n +\t\t\t     object_ref);\n  \tif (get_oid(replace_ref, &repl))\n -\t\tdie(\"Failed to resolve '%s' as a valid ref.\", replace_ref);\n +\t\treturn error(\"Failed to resolve '%s' as a valid ref.\",\n +\t\t\t     replace_ref);\n  \n  \treturn replace_object_oid(object_ref, &object, replace_ref, &repl, force);\n  }\n @@ -203,7 +209,7 @@ static int replace_object(const char *object_ref, const char *replace_ref, int f\n   * If \"raw\" is true, then the object's raw contents are printed according to\n   * \"type\". Otherwise, we pretty-print the contents for human editing.\n   */\n -static void export_object(const struct object_id *oid, enum object_type type,\n +static int export_object(const struct object_id *oid, enum object_type type,\n  \t\t\t  int raw, const char *filename)\n  {\n  \tstruct child_process cmd = CHILD_PROCESS_INIT;\n @@ -211,7 +217,7 @@ static void export_object(const struct object_id *oid, enum object_type type,\n  \n  \tfd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);\n  \tif (fd < 0)\n -\t\tdie_errno(\"unable to open %s for writing\", filename);\n +\t\treturn error_errno(\"unable to open %s for writing\", filename);\n  \n  \targv_array_push(&cmd.args, \"--no-replace-objects\");\n  \targv_array_push(&cmd.args, \"cat-file\");\n @@ -224,7 +230,8 @@ static void export_object(const struct object_id *oid, enum object_type type,\n  \tcmd.out = fd;\n  \n  \tif (run_command(&cmd))\n -\t\tdie(\"cat-file reported failure\");\n +\t\treturn error(\"cat-file reported failure\");\n +\treturn 0;\n  }\n  \n  /*\n @@ -232,14 +239,14 @@ static void export_object(const struct object_id *oid, enum object_type type,\n   * interpreting it as \"type\", and writing the result to the object database.\n   * The sha1 of the written object is returned via sha1.\n   */\n -static void import_object(struct object_id *oid, enum object_type type,\n +static int import_object(struct object_id *oid, enum object_type type,\n  \t\t\t  int raw, const char *filename)\n  {\n  \tint fd;\n  \n  \tfd = open(filename, O_RDONLY);\n  \tif (fd < 0)\n -\t\tdie_errno(\"unable to open %s for reading\", filename);\n +\t\treturn error_errno(\"unable to open %s for reading\", filename);\n  \n  \tif (!raw && type == OBJ_TREE) {\n  \t\tconst char *argv[] = { \"mktree\", NULL };\n @@ -251,27 +258,38 @@ static void import_object(struct object_id *oid, enum object_type type,\n  \t\tcmd.in = fd;\n  \t\tcmd.out = -1;\n  \n -\t\tif (start_command(&cmd))\n -\t\t\tdie(\"unable to spawn mktree\");\n +\t\tif (start_command(&cmd)) {\n +\t\t\tclose(fd);\n +\t\t\treturn error(\"unable to spawn mktree\");\n +\t\t}\n  \n -\t\tif (strbuf_read(&result, cmd.out, 41) < 0)\n -\t\t\tdie_errno(\"unable to read from mktree\");\n +\t\tif (strbuf_read(&result, cmd.out, 41) < 0) {\n +\t\t\tclose(fd);\n +\t\t\tclose(cmd.out);\n +\t\t\treturn error_errno(\"unable to read from mktree\");\n +\t\t}\n  \t\tclose(cmd.out);\n  \n -\t\tif (finish_command(&cmd))\n -\t\t\tdie(\"mktree reported failure\");\n -\t\tif (get_oid_hex(result.buf, oid) < 0)\n -\t\t\tdie(\"mktree did not return an object name\");\n +\t\tif (finish_command(&cmd)) {\n +\t\t\tstrbuf_release(&result);\n +\t\t\treturn error(\"mktree reported failure\");\n +\t\t}\n +\t\tif (get_oid_hex(result.buf, oid) < 0) {\n +\t\t\tstrbuf_release(&result);\n +\t\t\treturn error(\"mktree did not return an object name\");\n +\t\t}\n  \n  \t\tstrbuf_release(&result);\n  \t} else {\n  \t\tstruct stat st;\n  \t\tint flags = HASH_FORMAT_CHECK | HASH_WRITE_OBJECT;\n  \n -\t\tif (fstat(fd, &st) < 0)\n -\t\t\tdie_errno(\"unable to fstat %s\", filename);\n +\t\tif (fstat(fd, &st) < 0) {\n +\t\t\tclose(fd);\n +\t\t\treturn error_errno(\"unable to fstat %s\", filename);\n +\t\t}\n  \t\tif (index_fd(oid, fd, &st, type, NULL, flags) < 0)\n -\t\t\tdie(\"unable to write object to database\");\n +\t\t\treturn error(\"unable to write object to database\");\n  \t\t/* index_fd close()s fd for us */\n  \t}\n  \n @@ -279,6 +297,7 @@ static void import_object(struct object_id *oid, enum object_type type,\n  \t * No need to close(fd) here; both run-command and index-fd\n  \t * will have done it for us.\n  \t */\n +\treturn 0;\n  }\n  \n  static int edit_and_replace(const char *object_ref, int force, int raw)\n @@ -289,19 +308,23 @@ static int edit_and_replace(const char *object_ref, int force, int raw)\n  \tstruct strbuf ref = STRBUF_INIT;\n  \n  \tif (get_oid(object_ref, &old_oid) < 0)\n -\t\tdie(\"Not a valid object name: '%s'\", object_ref);\n +\t\treturn error(\"Not a valid object name: '%s'\", object_ref);\n  \n  \ttype = oid_object_info(&old_oid, NULL);\n  \tif (type < 0)\n -\t\tdie(\"unable to get object type for %s\", oid_to_hex(&old_oid));\n +\t\treturn error(\"unable to get object type for %s\",\n +\t\t\t     oid_to_hex(&old_oid));\n  \n -\tcheck_ref_valid(&old_oid, &prev, &ref, force);\n +\tif (check_ref_valid(&old_oid, &prev, &ref, force))\n +\t\treturn -1;\n  \tstrbuf_release(&ref);\n  \n -\texport_object(&old_oid, type, raw, tmpfile);\n +\tif (export_object(&old_oid, type, raw, tmpfile))\n +\t\treturn -1;\n  \tif (launch_editor(tmpfile, NULL, NULL) < 0)\n -\t\tdie(\"editing object file failed\");\n -\timport_object(&new_oid, type, raw, tmpfile);\n +\t\treturn error(\"editing object file failed\");\n +\tif (import_object(&new_oid, type, raw, tmpfile))\n +\t\treturn -1;\n  \n  \tfree(tmpfile);\n  \n @@ -311,7 +334,7 @@ static int edit_and_replace(const char *object_ref, int force, int raw)\n  \treturn replace_object_oid(object_ref, &old_oid, \"replacement\", &new_oid, force);\n  }\n  \n -static void replace_parents(struct strbuf *buf, int argc, const char **argv)\n +static int 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 @@ -329,8 +352,10 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv)\n  \tfor (i = 0; i < argc; i++) {\n  \t\tstruct object_id oid;\n  \t\tif (get_oid(argv[i], &oid) < 0)\n -\t\t\tdie(_(\"Not a valid object name: '%s'\"), argv[i]);\n -\t\tlookup_commit_or_die(&oid, argv[i]);\n +\t\t\treturn error(_(\"Not a valid object name: '%s'\"),\n +\t\t\t\t     argv[i]);\n +\t\tif (!lookup_commit_reference(&oid))\n +\t\t\treturn error(_(\"could not parse %s\"), argv[i]);\n  \t\tstrbuf_addf(&new_parents, \"parent %s\\n\", oid_to_hex(&oid));\n  \t}\n  \n @@ -339,6 +364,7 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv)\n  \t\t      new_parents.buf, new_parents.len);\n  \n  \tstrbuf_release(&new_parents);\n +\treturn 0;\n  }\n  \n  struct check_mergetag_data {\n @@ -346,7 +372,7 @@ struct check_mergetag_data {\n  \tconst char **argv;\n  };\n  \n -static void check_one_mergetag(struct commit *commit,\n +static int check_one_mergetag(struct commit *commit,\n  \t\t\t       struct commit_extra_header *extra,\n  \t\t\t       void *data)\n  {\n @@ -359,30 +385,32 @@ static void check_one_mergetag(struct commit *commit,\n  \thash_object_file(extra->value, extra->len, type_name(OBJ_TAG), &tag_oid);\n  \ttag = lookup_tag(&tag_oid);\n  \tif (!tag)\n -\t\tdie(_(\"bad mergetag in commit '%s'\"), ref);\n +\t\treturn error(_(\"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 +\t\treturn error(_(\"malformed mergetag in commit '%s'\"), ref);\n  \n  \t/* iterate over new parents */\n  \tfor (i = 1; i < mergetag_data->argc; i++) {\n  \t\tstruct object_id oid;\n  \t\tif (get_oid(mergetag_data->argv[i], &oid) < 0)\n -\t\t\tdie(_(\"Not a valid object name: '%s'\"), mergetag_data->argv[i]);\n +\t\t\treturn error(_(\"Not a valid object name: '%s'\"),\n +\t\t\t\t     mergetag_data->argv[i]);\n  \t\tif (!oidcmp(&tag->tagged->oid, &oid))\n -\t\t\treturn; /* found */\n +\t\t\treturn 0; /* found */\n  \t}\n  \n -\tdie(_(\"original commit '%s' contains mergetag '%s' that is discarded; \"\n -\t      \"use --edit instead of --graft\"), ref, oid_to_hex(&tag_oid));\n +\treturn error(_(\"original commit '%s' contains mergetag '%s' that is \"\n +\t\t       \"discarded; use --edit instead of --graft\"), ref,\n +\t\t     oid_to_hex(&tag_oid));\n  }\n  \n -static void check_mergetags(struct commit *commit, int argc, const char **argv)\n +static int 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 +\treturn for_each_mergetag(check_one_mergetag, commit, &mergetag_data);\n  }\n  \n  static int create_graft(int argc, const char **argv, int force)\n @@ -395,7 +423,7 @@ static int create_graft(int argc, const char **argv, int force)\n  \tunsigned long size;\n  \n  \tif (get_oid(old_ref, &old_oid) < 0)\n -\t\tdie(_(\"Not a valid object name: '%s'\"), old_ref);\n +\t\treturn error(_(\"Not a valid object name: '%s'\"), old_ref);\n  \tcommit = lookup_commit_reference(&old_oid);\n  \tif (!commit)\n  \t\treturn error(_(\"could not parse %s\"), old_ref);\n @@ -404,17 +432,20 @@ static int create_graft(int argc, const char **argv, int force)\n  \tstrbuf_add(&buf, buffer, size);\n  \tunuse_commit_buffer(commit, buffer);\n  \n -\treplace_parents(&buf, argc - 1, &argv[1]);\n +\tif (replace_parents(&buf, argc - 1, &argv[1]) < 0)\n +\t\treturn -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 -\tcheck_mergetags(commit, argc, argv);\n +\tif (check_mergetags(commit, argc, argv))\n +\t\treturn -1;\n  \n  \tif (write_object_file(buf.buf, buf.len, commit_type, &new_oid))\n -\t\tdie(_(\"could not write replacement commit for: '%s'\"), old_ref);\n +\t\treturn error(_(\"could not write replacement commit for: '%s'\"),\n +\t\t\t     old_ref);\n  \n  \tstrbuf_release(&buf);\n  \n @@ -435,28 +466,13 @@ static int convert_graft_file(int force)\n  \t\treturn -1;\n  \n  \twhile (strbuf_getline(&buf, fp) != EOF) {\n -\t\tint i = 0, j;\n -\n -\t\twhile (i != buf.len) {\n -\t\t\tchar save;\n -\n -\t\t\tfor (j = i; j < buf.len && !isspace(buf.buf[j]); j++)\n -\t\t\t\t; /* look further */\n -\t\t\tsave = buf.buf[j];\n -\t\t\tbuf.buf[j] = '\\0';\n -\t\t\targv_array_push(&args, buf.buf + i);\n -\t\t\tbuf.buf[j] = save;\n -\n -\t\t\twhile (j < buf.len && isspace(buf.buf[j]))\n -\t\t\t\tj++;\n -\t\t\ti = j;\n -\t\t}\n +\t\tif (*buf.buf == '#')\n +\t\t\tcontinue;\n  \n -\t\tif (create_graft(args.argc, args.argv, force))\n +\t\targv_array_split(&args, buf.buf);\n +\t\tif (args.argc && create_graft(args.argc, args.argv, force))\n  \t\t\tstrbuf_addf(&err, \"\\n\\t%s\", buf.buf);\n -\n  \t\targv_array_clear(&args);\n -\t\tstrbuf_reset(&buf);\n  \t}\n  \n  \tstrbuf_release(&buf);\n @@ -560,6 +576,6 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n  \t\treturn list_replace_refs(argv[0], format);\n  \n  \tdefault:\n -\t\tdie(\"BUG: invalid cmdmode %d\", (int)cmdmode);\n +\t\tBUG(\"invalid cmdmode %d\", (int)cmdmode);\n  \t}\n  }\n diff --git a/commit.c b/commit.c\n index 1a5e8777617..451d3ce8dfe 100644\n --- a/commit.c\n +++ b/commit.c\n @@ -1298,17 +1298,19 @@ struct commit_extra_header *read_commit_extra_headers(struct commit *commit,\n  \treturn extra;\n  }\n  \n -void for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data)\n +int for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data)\n  {\n  \tstruct commit_extra_header *extra, *to_free;\n +\tint res = 0;\n  \n  \tto_free = read_commit_extra_headers(commit, NULL);\n -\tfor (extra = to_free; extra; extra = extra->next) {\n +\tfor (extra = to_free; !res && extra; extra = extra->next) {\n  \t\tif (strcmp(extra->key, \"mergetag\"))\n  \t\t\tcontinue; /* not a merge tag */\n -\t\tfn(commit, extra, data);\n +\t\tres = fn(commit, extra, data);\n  \t}\n  \tfree_commit_extra_headers(to_free);\n +\treturn res;\n  }\n  \n  static inline int standard_header_field(const char *field, size_t len)\n diff --git a/commit.h b/commit.h\n index 0fb8271665c..9000895ad91 100644\n --- a/commit.h\n +++ b/commit.h\n @@ -291,10 +291,10 @@ extern const char *find_commit_header(const char *msg, const char *key,\n  /* Find the end of the log message, the right place for a new trailer. */\n  extern int ignore_non_trailer(const char *buf, size_t len);\n  \n -typedef void (*each_mergetag_fn)(struct commit *commit, struct commit_extra_header *extra,\n +typedef int (*each_mergetag_fn)(struct commit *commit, struct commit_extra_header *extra,\n  \t\t\t\t void *cb_data);\n  \n -extern void for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data);\n +extern int for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data);\n  \n  struct merge_remote_desc {\n  \tstruct object *obj; /* the named object, could be a tag */\n diff --git a/contrib/convert-grafts-to-replace-refs.sh b/contrib/convert-grafts-to-replace-refs.sh\n deleted file mode 100755\n index 0cbc917b8cf..00000000000\n --- a/contrib/convert-grafts-to-replace-refs.sh\n +++ /dev/null\n @@ -1,28 +0,0 @@\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 diff --git a/log-tree.c b/log-tree.c\n index d1c0bedf244..f3a51a6e726 100644\n --- a/log-tree.c\n +++ b/log-tree.c\n @@ -488,9 +488,9 @@ static int is_common_merge(const struct commit *commit)\n  \t\t&& !commit->parents->next->next);\n  }\n  \n -static void show_one_mergetag(struct commit *commit,\n -\t\t\t      struct commit_extra_header *extra,\n -\t\t\t      void *data)\n +static int show_one_mergetag(struct commit *commit,\n +\t\t\t     struct commit_extra_header *extra,\n +\t\t\t     void *data)\n  {\n  \tstruct rev_info *opt = (struct rev_info *)data;\n  \tstruct object_id oid;\n @@ -502,7 +502,7 @@ static void show_one_mergetag(struct commit *commit,\n  \thash_object_file(extra->value, extra->len, type_name(OBJ_TAG), &oid);\n  \ttag = lookup_tag(&oid);\n  \tif (!tag)\n -\t\treturn; /* error message already given */\n +\t\treturn -1; /* error message already given */\n  \n  \tstrbuf_init(&verify_message, 256);\n  \tif (parse_tag_buffer(tag, extra->value, extra->len))\n @@ -536,11 +536,12 @@ static void show_one_mergetag(struct commit *commit,\n  \n  \tshow_sig_lines(opt, status, verify_message.buf);\n  \tstrbuf_release(&verify_message);\n +\treturn 0;\n  }\n  \n -static void show_mergetag(struct rev_info *opt, struct commit *commit)\n +static int show_mergetag(struct rev_info *opt, struct commit *commit)\n  {\n -\tfor_each_mergetag(show_one_mergetag, commit, opt);\n +\treturn for_each_mergetag(show_one_mergetag, commit, opt);\n  }\n  \n  void show_log(struct rev_info *opt)\n diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh\n index 77ded6df653..8a3ee7c3db9 100755\n --- a/t/t6050-replace.sh\n +++ b/t/t6050-replace.sh\n @@ -446,7 +446,7 @@ test_expect_success GPG '--graft on a commit with a mergetag' '\n  \n  test_expect_success '--convert-graft-file' '\n  \t: add and convert graft file &&\n -\tprintf \"%s\\n%s %s\\n%s\\n\" \\\n +\tprintf \"%s\\n%s %s\\n\\n# comment\\n%s\\n\" \\\n  \t\t$(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \\\n  \t\t>.git/info/grafts &&\n  \tgit replace --convert-graft-file &&\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n"},{"id":"345279","messageId":"8171fd3920846e37c9f1cab6f1ac658dbfb01bfe.1524262793.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524262793.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 01/11] argv_array: offer to split a string by whitespace","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-20T22:20:44Z","receivedAt":"2018-04-20T22:21:09Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This is a simple function that will interpret a string as a whitespace\ndelimited list of values, and add those values into the array.\n\nNote: this function does not (yet) offer to split by arbitrary delimiters,\nor keep empty values in case of runs of whitespace, or de-quote Unix shell\nstyle. All fo this functionality can be added later, when and if needed.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n argv-array.c | 20 ++++++++++++++++++++\n argv-array.h |  1 +\n 2 files changed, 21 insertions(+)\n\ndiff --git a/argv-array.c b/argv-array.c\nindex 5d370fa3366..cb5bcd2c064 100644\n--- a/argv-array.c\n+++ b/argv-array.c\n@@ -64,6 +64,26 @@ void argv_array_pop(struct argv_array *array)\n \tarray->argc--;\n }\n \n+void argv_array_split(struct argv_array *array, const char *to_split)\n+{\n+\twhile (isspace(*to_split))\n+\t\tto_split++;\n+\tfor (;;) {\n+\t\tconst char *p = to_split;\n+\n+\t\tif (!*p)\n+\t\t\tbreak;\n+\n+\t\twhile (*p && !isspace(*p))\n+\t\t\tp++;\n+\t\targv_array_push_nodup(array, xstrndup(to_split, p - to_split));\n+\n+\t\twhile (isspace(*p))\n+\t\t\tp++;\n+\t\tto_split = p;\n+\t}\n+}\n+\n void argv_array_clear(struct argv_array *array)\n {\n \tif (array->argv != empty_argv) {\ndiff --git a/argv-array.h b/argv-array.h\nindex 29056e49a12..c7c397695df 100644\n--- a/argv-array.h\n+++ b/argv-array.h\n@@ -19,6 +19,7 @@ LAST_ARG_MUST_BE_NULL\n void argv_array_pushl(struct argv_array *, ...);\n void argv_array_pushv(struct argv_array *, const char **);\n void argv_array_pop(struct argv_array *);\n+void argv_array_split(struct argv_array *, const char *);\n void argv_array_clear(struct argv_array *);\n const char **argv_array_detach(struct argv_array *);\n \n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345280","messageId":"4593996360eb12da218bc73745298a68bd71ab20.1524262793.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524262793.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 02/11] commit: Let the callback of for_each_mergetag return on error","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-20T22:21:05Z","receivedAt":"2018-04-20T22:21:27Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This is yet another patch to be filed under the keyword \"libification\".\n\nThere is one subtle change in behavior here, where a `git log` that has\nbeen asked to show the mergetags would now stop reporting the mergetags\nupon the first failure, whereas previously, it would have continued to the\nnext mergetag, if any.\n\nIn practice, that change should not matter, as it is 1) uncommon to\nperform octopus merges using multiple tags as merge heads, and 2) when the\nuser asks to be shown those tags, they really should be there.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/replace.c |  8 ++++----\n commit.c          |  8 +++++---\n commit.h          |  4 ++--\n log-tree.c        | 13 +++++++------\n 4 files changed, 18 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 935647be6bd..245d3f4164e 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -345,7 +345,7 @@ struct check_mergetag_data {\n \tconst char **argv;\n };\n \n-static void check_one_mergetag(struct commit *commit,\n+static int check_one_mergetag(struct commit *commit,\n \t\t\t       struct commit_extra_header *extra,\n \t\t\t       void *data)\n {\n@@ -368,20 +368,20 @@ static void check_one_mergetag(struct commit *commit,\n \t\tif (get_oid(mergetag_data->argv[i], &oid) < 0)\n \t\t\tdie(_(\"Not a valid object name: '%s'\"), mergetag_data->argv[i]);\n \t\tif (!oidcmp(&tag->tagged->oid, &oid))\n-\t\t\treturn; /* found */\n+\t\t\treturn 0; /* found */\n \t}\n \n \tdie(_(\"original commit '%s' contains mergetag '%s' that is discarded; \"\n \t      \"use --edit instead of --graft\"), ref, oid_to_hex(&tag_oid));\n }\n \n-static void check_mergetags(struct commit *commit, int argc, const char **argv)\n+static int 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+\treturn for_each_mergetag(check_one_mergetag, commit, &mergetag_data);\n }\n \n static int create_graft(int argc, const char **argv, int force)\ndiff --git a/commit.c b/commit.c\nindex ca474a7c112..2952ec987c5 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -1288,17 +1288,19 @@ struct commit_extra_header *read_commit_extra_headers(struct commit *commit,\n \treturn extra;\n }\n \n-void for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data)\n+int for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data)\n {\n \tstruct commit_extra_header *extra, *to_free;\n+\tint res = 0;\n \n \tto_free = read_commit_extra_headers(commit, NULL);\n-\tfor (extra = to_free; extra; extra = extra->next) {\n+\tfor (extra = to_free; !res && extra; extra = extra->next) {\n \t\tif (strcmp(extra->key, \"mergetag\"))\n \t\t\tcontinue; /* not a merge tag */\n-\t\tfn(commit, extra, data);\n+\t\tres = fn(commit, extra, data);\n \t}\n \tfree_commit_extra_headers(to_free);\n+\treturn res;\n }\n \n static inline int standard_header_field(const char *field, size_t len)\ndiff --git a/commit.h b/commit.h\nindex 0fb8271665c..9000895ad91 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -291,10 +291,10 @@ extern const char *find_commit_header(const char *msg, const char *key,\n /* Find the end of the log message, the right place for a new trailer. */\n extern int ignore_non_trailer(const char *buf, size_t len);\n \n-typedef void (*each_mergetag_fn)(struct commit *commit, struct commit_extra_header *extra,\n+typedef int (*each_mergetag_fn)(struct commit *commit, struct commit_extra_header *extra,\n \t\t\t\t void *cb_data);\n \n-extern void for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data);\n+extern int for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data);\n \n struct merge_remote_desc {\n \tstruct object *obj; /* the named object, could be a tag */\ndiff --git a/log-tree.c b/log-tree.c\nindex d1c0bedf244..f3a51a6e726 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -488,9 +488,9 @@ static int is_common_merge(const struct commit *commit)\n \t\t&& !commit->parents->next->next);\n }\n \n-static void show_one_mergetag(struct commit *commit,\n-\t\t\t      struct commit_extra_header *extra,\n-\t\t\t      void *data)\n+static int show_one_mergetag(struct commit *commit,\n+\t\t\t     struct commit_extra_header *extra,\n+\t\t\t     void *data)\n {\n \tstruct rev_info *opt = (struct rev_info *)data;\n \tstruct object_id oid;\n@@ -502,7 +502,7 @@ static void show_one_mergetag(struct commit *commit,\n \thash_object_file(extra->value, extra->len, type_name(OBJ_TAG), &oid);\n \ttag = lookup_tag(&oid);\n \tif (!tag)\n-\t\treturn; /* error message already given */\n+\t\treturn -1; /* error message already given */\n \n \tstrbuf_init(&verify_message, 256);\n \tif (parse_tag_buffer(tag, extra->value, extra->len))\n@@ -536,11 +536,12 @@ static void show_one_mergetag(struct commit *commit,\n \n \tshow_sig_lines(opt, status, verify_message.buf);\n \tstrbuf_release(&verify_message);\n+\treturn 0;\n }\n \n-static void show_mergetag(struct rev_info *opt, struct commit *commit)\n+static int show_mergetag(struct rev_info *opt, struct commit *commit)\n {\n-\tfor_each_mergetag(show_one_mergetag, commit, opt);\n+\treturn for_each_mergetag(show_one_mergetag, commit, opt);\n }\n \n void show_log(struct rev_info *opt)\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345281","messageId":"1d045a3b832933e583efe18de9553d7747538da2.1524262793.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524262793.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 03/11] replace: avoid using die() to indicate a bug","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-20T22:21:24Z","receivedAt":"2018-04-20T22:21:47Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"We have the BUG() macro for that purpose.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/replace.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 245d3f4164e..e345a5a0f1c 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -501,6 +501,6 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n \t\treturn list_replace_refs(argv[0], format);\n \n \tdefault:\n-\t\tdie(\"BUG: invalid cmdmode %d\", (int)cmdmode);\n+\t\tBUG(\"invalid cmdmode %d\", (int)cmdmode);\n \t}\n }\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345282","messageId":"11ee7383bae422789c29bf1b5637cea8e23c0679.1524262793.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524262793.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 04/11] replace: \"libify\" create_graft() and callees","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-20T22:21:45Z","receivedAt":"2018-04-20T22:22:11Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"File this away as yet another patch in the \"libification\" category.\n\nAs with all useful functions, in the next commit we want to use\ncreate_graft() from a higher-level function where it would be\ninconvenient if the called function simply die()s: if there is a\nproblem, we want to let the user know how to proceed, and the callee\nsimply has no way of knowing what to say.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/replace.c | 135 ++++++++++++++++++++++++++++------------------\n 1 file changed, 84 insertions(+), 51 deletions(-)\n\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex e345a5a0f1c..f63df405fd0 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -79,9 +79,9 @@ static int list_replace_refs(const char *pattern, const char *format)\n \telse if (!strcmp(format, \"long\"))\n \t\tdata.format = REPLACE_FORMAT_LONG;\n \telse\n-\t\tdie(\"invalid replace format '%s'\\n\"\n-\t\t    \"valid formats are 'short', 'medium' and 'long'\\n\",\n-\t\t    format);\n+\t\treturn error(\"invalid replace format '%s'\\n\"\n+\t\t\t     \"valid formats are 'short', 'medium' and 'long'\\n\",\n+\t\t\t     format);\n \n \tfor_each_replace_ref(show_reference, (void *)&data);\n \n@@ -134,7 +134,7 @@ static int delete_replace_ref(const char *name, const char *ref,\n \treturn 0;\n }\n \n-static void check_ref_valid(struct object_id *object,\n+static int check_ref_valid(struct object_id *object,\n \t\t\t    struct object_id *prev,\n \t\t\t    struct strbuf *ref,\n \t\t\t    int force)\n@@ -142,12 +142,13 @@ static void check_ref_valid(struct object_id *object,\n \tstrbuf_reset(ref);\n \tstrbuf_addf(ref, \"%s%s\", git_replace_ref_base, oid_to_hex(object));\n \tif (check_refname_format(ref->buf, 0))\n-\t\tdie(\"'%s' is not a valid ref name.\", ref->buf);\n+\t\treturn error(\"'%s' is not a valid ref name.\", ref->buf);\n \n \tif (read_ref(ref->buf, prev))\n \t\toidclr(prev);\n \telse if (!force)\n-\t\tdie(\"replace ref '%s' already exists\", ref->buf);\n+\t\treturn error(\"replace ref '%s' already exists\", ref->buf);\n+\treturn 0;\n }\n \n static int replace_object_oid(const char *object_ref,\n@@ -161,28 +162,31 @@ static int replace_object_oid(const char *object_ref,\n \tstruct strbuf ref = STRBUF_INIT;\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n+\tint res = 0;\n \n \tobj_type = oid_object_info(object, NULL);\n \trepl_type = oid_object_info(repl, NULL);\n \tif (!force && obj_type != repl_type)\n-\t\tdie(\"Objects must be of the same type.\\n\"\n-\t\t    \"'%s' points to a replaced object of type '%s'\\n\"\n-\t\t    \"while '%s' points to a replacement object of type '%s'.\",\n-\t\t    object_ref, type_name(obj_type),\n-\t\t    replace_ref, type_name(repl_type));\n+\t\treturn error(\"Objects must be of the same type.\\n\"\n+\t\t\t     \"'%s' points to a replaced object of type '%s'\\n\"\n+\t\t\t     \"while '%s' points to a replacement object of \"\n+\t\t\t     \"type '%s'.\",\n+\t\t\t     object_ref, type_name(obj_type),\n+\t\t\t     replace_ref, type_name(repl_type));\n \n-\tcheck_ref_valid(object, &prev, &ref, force);\n+\tif (check_ref_valid(object, &prev, &ref, force))\n+\t\treturn -1;\n \n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, ref.buf, repl, &prev,\n \t\t\t\t   0, NULL, &err) ||\n \t    ref_transaction_commit(transaction, &err))\n-\t\tdie(\"%s\", err.buf);\n+\t\tres = error(\"%s\", err.buf);\n \n \tref_transaction_free(transaction);\n \tstrbuf_release(&ref);\n-\treturn 0;\n+\treturn res;\n }\n \n static int replace_object(const char *object_ref, const char *replace_ref, int force)\n@@ -190,9 +194,11 @@ static int replace_object(const char *object_ref, const char *replace_ref, int f\n \tstruct object_id object, repl;\n \n \tif (get_oid(object_ref, &object))\n-\t\tdie(\"Failed to resolve '%s' as a valid ref.\", object_ref);\n+\t\treturn error(\"Failed to resolve '%s' as a valid ref.\",\n+\t\t\t     object_ref);\n \tif (get_oid(replace_ref, &repl))\n-\t\tdie(\"Failed to resolve '%s' as a valid ref.\", replace_ref);\n+\t\treturn error(\"Failed to resolve '%s' as a valid ref.\",\n+\t\t\t     replace_ref);\n \n \treturn replace_object_oid(object_ref, &object, replace_ref, &repl, force);\n }\n@@ -202,7 +208,7 @@ static int replace_object(const char *object_ref, const char *replace_ref, int f\n  * If \"raw\" is true, then the object's raw contents are printed according to\n  * \"type\". Otherwise, we pretty-print the contents for human editing.\n  */\n-static void export_object(const struct object_id *oid, enum object_type type,\n+static int export_object(const struct object_id *oid, enum object_type type,\n \t\t\t  int raw, const char *filename)\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n@@ -210,7 +216,7 @@ static void export_object(const struct object_id *oid, enum object_type type,\n \n \tfd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);\n \tif (fd < 0)\n-\t\tdie_errno(\"unable to open %s for writing\", filename);\n+\t\treturn error_errno(\"unable to open %s for writing\", filename);\n \n \targv_array_push(&cmd.args, \"--no-replace-objects\");\n \targv_array_push(&cmd.args, \"cat-file\");\n@@ -223,7 +229,8 @@ static void export_object(const struct object_id *oid, enum object_type type,\n \tcmd.out = fd;\n \n \tif (run_command(&cmd))\n-\t\tdie(\"cat-file reported failure\");\n+\t\treturn error(\"cat-file reported failure\");\n+\treturn 0;\n }\n \n /*\n@@ -231,14 +238,14 @@ static void export_object(const struct object_id *oid, enum object_type type,\n  * interpreting it as \"type\", and writing the result to the object database.\n  * The sha1 of the written object is returned via sha1.\n  */\n-static void import_object(struct object_id *oid, enum object_type type,\n+static int import_object(struct object_id *oid, enum object_type type,\n \t\t\t  int raw, const char *filename)\n {\n \tint fd;\n \n \tfd = open(filename, O_RDONLY);\n \tif (fd < 0)\n-\t\tdie_errno(\"unable to open %s for reading\", filename);\n+\t\treturn error_errno(\"unable to open %s for reading\", filename);\n \n \tif (!raw && type == OBJ_TREE) {\n \t\tconst char *argv[] = { \"mktree\", NULL };\n@@ -250,27 +257,38 @@ static void import_object(struct object_id *oid, enum object_type type,\n \t\tcmd.in = fd;\n \t\tcmd.out = -1;\n \n-\t\tif (start_command(&cmd))\n-\t\t\tdie(\"unable to spawn mktree\");\n+\t\tif (start_command(&cmd)) {\n+\t\t\tclose(fd);\n+\t\t\treturn error(\"unable to spawn mktree\");\n+\t\t}\n \n-\t\tif (strbuf_read(&result, cmd.out, 41) < 0)\n-\t\t\tdie_errno(\"unable to read from mktree\");\n+\t\tif (strbuf_read(&result, cmd.out, 41) < 0) {\n+\t\t\tclose(fd);\n+\t\t\tclose(cmd.out);\n+\t\t\treturn error_errno(\"unable to read from mktree\");\n+\t\t}\n \t\tclose(cmd.out);\n \n-\t\tif (finish_command(&cmd))\n-\t\t\tdie(\"mktree reported failure\");\n-\t\tif (get_oid_hex(result.buf, oid) < 0)\n-\t\t\tdie(\"mktree did not return an object name\");\n+\t\tif (finish_command(&cmd)) {\n+\t\t\tstrbuf_release(&result);\n+\t\t\treturn error(\"mktree reported failure\");\n+\t\t}\n+\t\tif (get_oid_hex(result.buf, oid) < 0) {\n+\t\t\tstrbuf_release(&result);\n+\t\t\treturn error(\"mktree did not return an object name\");\n+\t\t}\n \n \t\tstrbuf_release(&result);\n \t} else {\n \t\tstruct stat st;\n \t\tint flags = HASH_FORMAT_CHECK | HASH_WRITE_OBJECT;\n \n-\t\tif (fstat(fd, &st) < 0)\n-\t\t\tdie_errno(\"unable to fstat %s\", filename);\n+\t\tif (fstat(fd, &st) < 0) {\n+\t\t\tclose(fd);\n+\t\t\treturn error_errno(\"unable to fstat %s\", filename);\n+\t\t}\n \t\tif (index_fd(oid, fd, &st, type, NULL, flags) < 0)\n-\t\t\tdie(\"unable to write object to database\");\n+\t\t\treturn error(\"unable to write object to database\");\n \t\t/* index_fd close()s fd for us */\n \t}\n \n@@ -278,6 +296,7 @@ static void import_object(struct object_id *oid, enum object_type type,\n \t * No need to close(fd) here; both run-command and index-fd\n \t * will have done it for us.\n \t */\n+\treturn 0;\n }\n \n static int edit_and_replace(const char *object_ref, int force, int raw)\n@@ -288,19 +307,23 @@ static int edit_and_replace(const char *object_ref, int force, int raw)\n \tstruct strbuf ref = STRBUF_INIT;\n \n \tif (get_oid(object_ref, &old_oid) < 0)\n-\t\tdie(\"Not a valid object name: '%s'\", object_ref);\n+\t\treturn error(\"Not a valid object name: '%s'\", object_ref);\n \n \ttype = oid_object_info(&old_oid, NULL);\n \tif (type < 0)\n-\t\tdie(\"unable to get object type for %s\", oid_to_hex(&old_oid));\n+\t\treturn error(\"unable to get object type for %s\",\n+\t\t\t     oid_to_hex(&old_oid));\n \n-\tcheck_ref_valid(&old_oid, &prev, &ref, force);\n+\tif (check_ref_valid(&old_oid, &prev, &ref, force))\n+\t\treturn -1;\n \tstrbuf_release(&ref);\n \n-\texport_object(&old_oid, type, raw, tmpfile);\n+\tif (export_object(&old_oid, type, raw, tmpfile))\n+\t\treturn -1;\n \tif (launch_editor(tmpfile, NULL, NULL) < 0)\n-\t\tdie(\"editing object file failed\");\n-\timport_object(&new_oid, type, raw, tmpfile);\n+\t\treturn error(\"editing object file failed\");\n+\tif (import_object(&new_oid, type, raw, tmpfile))\n+\t\treturn -1;\n \n \tfree(tmpfile);\n \n@@ -310,7 +333,7 @@ static int edit_and_replace(const char *object_ref, int force, int raw)\n \treturn replace_object_oid(object_ref, &old_oid, \"replacement\", &new_oid, force);\n }\n \n-static void replace_parents(struct strbuf *buf, int argc, const char **argv)\n+static int 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@@ -328,8 +351,10 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv)\n \tfor (i = 0; i < argc; i++) {\n \t\tstruct object_id oid;\n \t\tif (get_oid(argv[i], &oid) < 0)\n-\t\t\tdie(_(\"Not a valid object name: '%s'\"), argv[i]);\n-\t\tlookup_commit_or_die(&oid, argv[i]);\n+\t\t\treturn error(_(\"Not a valid object name: '%s'\"),\n+\t\t\t\t     argv[i]);\n+\t\tif (!lookup_commit_reference(&oid))\n+\t\t\treturn error(_(\"could not parse %s\"), argv[i]);\n \t\tstrbuf_addf(&new_parents, \"parent %s\\n\", oid_to_hex(&oid));\n \t}\n \n@@ -338,6 +363,7 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv)\n \t\t      new_parents.buf, new_parents.len);\n \n \tstrbuf_release(&new_parents);\n+\treturn 0;\n }\n \n struct check_mergetag_data {\n@@ -358,21 +384,23 @@ static int check_one_mergetag(struct commit *commit,\n \thash_object_file(extra->value, extra->len, type_name(OBJ_TAG), &tag_oid);\n \ttag = lookup_tag(&tag_oid);\n \tif (!tag)\n-\t\tdie(_(\"bad mergetag in commit '%s'\"), ref);\n+\t\treturn error(_(\"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+\t\treturn error(_(\"malformed mergetag in commit '%s'\"), ref);\n \n \t/* iterate over new parents */\n \tfor (i = 1; i < mergetag_data->argc; i++) {\n \t\tstruct object_id oid;\n \t\tif (get_oid(mergetag_data->argv[i], &oid) < 0)\n-\t\t\tdie(_(\"Not a valid object name: '%s'\"), mergetag_data->argv[i]);\n+\t\t\treturn error(_(\"Not a valid object name: '%s'\"),\n+\t\t\t\t     mergetag_data->argv[i]);\n \t\tif (!oidcmp(&tag->tagged->oid, &oid))\n \t\t\treturn 0; /* found */\n \t}\n \n-\tdie(_(\"original commit '%s' contains mergetag '%s' that is discarded; \"\n-\t      \"use --edit instead of --graft\"), ref, oid_to_hex(&tag_oid));\n+\treturn error(_(\"original commit '%s' contains mergetag '%s' that is \"\n+\t\t       \"discarded; use --edit instead of --graft\"), ref,\n+\t\t     oid_to_hex(&tag_oid));\n }\n \n static int check_mergetags(struct commit *commit, int argc, const char **argv)\n@@ -394,24 +422,29 @@ static int create_graft(int argc, const char **argv, int force)\n \tunsigned long size;\n \n \tif (get_oid(old_ref, &old_oid) < 0)\n-\t\tdie(_(\"Not a valid object name: '%s'\"), old_ref);\n-\tcommit = lookup_commit_or_die(&old_oid, old_ref);\n+\t\treturn error(_(\"Not a valid object name: '%s'\"), old_ref);\n+\tcommit = lookup_commit_reference(&old_oid);\n+\tif (!commit)\n+\t\treturn error(_(\"could not parse %s\"), 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+\tif (replace_parents(&buf, argc - 1, &argv[1]) < 0)\n+\t\treturn -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-\tcheck_mergetags(commit, argc, argv);\n+\tif (check_mergetags(commit, argc, argv))\n+\t\treturn -1;\n \n \tif (write_object_file(buf.buf, buf.len, commit_type, &new_oid))\n-\t\tdie(_(\"could not write replacement commit for: '%s'\"), old_ref);\n+\t\treturn error(_(\"could not write replacement commit for: '%s'\"),\n+\t\t\t     old_ref);\n \n \tstrbuf_release(&buf);\n \n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345283","messageId":"b51610acdcd981c7215df9e1feac78b541d4b37a.1524262793.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524262793.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 05/11] replace: introduce --convert-graft-file","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-20T22:22:06Z","receivedAt":"2018-04-20T22:22:29Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This option is intended to help with the transition away from the\nnow-deprecated graft file.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-replace.txt | 11 ++++++---\n builtin/replace.c             | 44 ++++++++++++++++++++++++++++++++++-\n 2 files changed, 51 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-replace.txt b/Documentation/git-replace.txt\nindex e5c57ae6ef4..246dc9943c2 100644\n--- a/Documentation/git-replace.txt\n+++ b/Documentation/git-replace.txt\n@@ -11,6 +11,7 @@ SYNOPSIS\n 'git replace' [-f] <object> <replacement>\n 'git replace' [-f] --edit <object>\n 'git replace' [-f] --graft <commit> [<parent>...]\n+'git replace' [-f] --convert-graft-file\n 'git replace' -d <object>...\n 'git replace' [--format=<format>] [-l [<pattern>]]\n \n@@ -87,9 +88,13 @@ 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. 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+\tcommit. Use `--convert-graft-file` to convert a\n+\t`$GIT_DIR/info/grafts` file and use replace refs instead.\n+\n+--convert-graft-file::\n+\tCreates graft commits for all entries in `$GIT_DIR/info/grafts`\n+\tand deletes that file upon success. The purpose is to help users\n+\twith transitioning off of the now-deprecated graft file.\n \n -l <pattern>::\n --list <pattern>::\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex f63df405fd0..6b3e0301e90 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -20,6 +20,7 @@ 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 [-f] --convert-graft-file\"),\n \tN_(\"git replace -d <object>...\"),\n \tN_(\"git replace [--format=<format>] [-l [<pattern>]]\"),\n \tNULL\n@@ -454,6 +455,38 @@ static int create_graft(int argc, const char **argv, int force)\n \treturn replace_object_oid(old_ref, &old_oid, \"replacement\", &new_oid, force);\n }\n \n+static int convert_graft_file(int force)\n+{\n+\tconst char *graft_file = get_graft_file();\n+\tFILE *fp = fopen_or_warn(graft_file, \"r\");\n+\tstruct strbuf buf = STRBUF_INIT, err = STRBUF_INIT;\n+\tstruct argv_array args = ARGV_ARRAY_INIT;\n+\n+\tif (!fp)\n+\t\treturn -1;\n+\n+\twhile (strbuf_getline(&buf, fp) != EOF) {\n+\t\tif (*buf.buf == '#')\n+\t\t\tcontinue;\n+\n+\t\targv_array_split(&args, buf.buf);\n+\t\tif (args.argc && create_graft(args.argc, args.argv, force))\n+\t\t\tstrbuf_addf(&err, \"\\n\\t%s\", buf.buf);\n+\t\targv_array_clear(&args);\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\targv_array_clear(&args);\n+\n+\tif (!err.len)\n+\t\treturn unlink_or_warn(graft_file);\n+\n+\twarning(_(\"could not convert the following graft(s):\\n%s\"), err.buf);\n+\tstrbuf_release(&err);\n+\n+\treturn -1;\n+}\n+\n int cmd_replace(int argc, const char **argv, const char *prefix)\n {\n \tint force = 0;\n@@ -465,6 +498,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n \t\tMODE_DELETE,\n \t\tMODE_EDIT,\n \t\tMODE_GRAFT,\n+\t\tMODE_CONVERT_GRAFT_FILE,\n \t\tMODE_REPLACE\n \t} cmdmode = MODE_UNSPECIFIED;\n \tstruct option options[] = {\n@@ -472,6 +506,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\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_CMDMODE(0, \"convert-graft-file\", &cmdmode, N_(\"convert existing graft file\"), MODE_CONVERT_GRAFT_FILE),\n \t\tOPT_BOOL_F('f', \"force\", &force, N_(\"replace the ref if it exists\"),\n \t\t\t   PARSE_OPT_NOCOMPLETE),\n \t\tOPT_BOOL(0, \"raw\", &raw, N_(\"do not pretty-print contents for --edit\")),\n@@ -494,7 +529,8 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n \tif (force &&\n \t    cmdmode != MODE_REPLACE &&\n \t    cmdmode != MODE_EDIT &&\n-\t    cmdmode != MODE_GRAFT)\n+\t    cmdmode != MODE_GRAFT &&\n+\t    cmdmode != MODE_CONVERT_GRAFT_FILE)\n \t\tusage_msg_opt(\"-f only makes sense when writing a replacement\",\n \t\t\t      git_replace_usage, options);\n \n@@ -527,6 +563,12 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n \t\t\t\t      git_replace_usage, options);\n \t\treturn create_graft(argc, argv, force);\n \n+\tcase MODE_CONVERT_GRAFT_FILE:\n+\t\tif (argc != 0)\n+\t\t\tusage_msg_opt(\"--convert-graft-file takes no argument\",\n+\t\t\t\t      git_replace_usage, options);\n+\t\treturn !!convert_graft_file(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.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345284","messageId":"de1d455d6e526cf264ce1291c1923b97271969c3.1524262793.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524262793.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 06/11] Add a test for `git replace --convert-graft-file`","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-20T22:23:10Z","receivedAt":"2018-04-20T22:23:32Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The proof, as the saying goes, lies in the pudding. So here is a\nregression test that not only demonstrates what the option is supposed to\naccomplish, but also demonstrates that it does accomplish it.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/t6050-replace.sh | 20 ++++++++++++++++++++\n 1 file changed, 20 insertions(+)\n\ndiff --git a/t/t6050-replace.sh b/t/t6050-replace.sh\nindex c630aba657e..8a3ee7c3db9 100755\n--- a/t/t6050-replace.sh\n+++ b/t/t6050-replace.sh\n@@ -444,4 +444,24 @@ test_expect_success GPG '--graft on a commit with a mergetag' '\n \tgit replace -d $HASH10\n '\n \n+test_expect_success '--convert-graft-file' '\n+\t: add and convert graft file &&\n+\tprintf \"%s\\n%s %s\\n\\n# comment\\n%s\\n\" \\\n+\t\t$(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \\\n+\t\t>.git/info/grafts &&\n+\tgit replace --convert-graft-file &&\n+\ttest_path_is_missing .git/info/grafts &&\n+\n+\t: verify that the history is now \"grafted\" &&\n+\tgit rev-list HEAD >out &&\n+\ttest_line_count = 4 out &&\n+\n+\t: create invalid graft file and verify that it is not deleted &&\n+\ttest_when_finished \"rm -f .git/info/grafts\" &&\n+\techo $EMPTY_BLOB $EMPTY_TREE >.git/info/grafts &&\n+\ttest_must_fail git replace --convert-graft-file 2>err &&\n+\tgrep \"$EMPTY_BLOB $EMPTY_TREE\" err &&\n+\tgrep \"$EMPTY_BLOB $EMPTY_TREE\" .git/info/grafts\n+'\n+\n test_done\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345285","messageId":"20b6b0cb61e0285fd586dc190a0a96e642cd917a.1524262793.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524262793.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 07/11] Deprecate support for .git/info/grafts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-20T22:24:07Z","receivedAt":"2018-04-20T22:24:32Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The grafts feature was a convenient way to \"stitch together\" ancient\nhistory to the fresh start of linux.git.\n\nIts implementation is, however, not up to Git's standards, as there are\ntoo many ways where it can lead to surprising and unwelcome behavior.\n\nFor example, when pushing from a repository with active grafts, it is\npossible to miss commits that have been \"grafted out\", resulting in a\nbroken state on the other side.\n\nAlso, the grafts feature is limited to \"rewriting\" commits' list of\nparents, it cannot replace anything else.\n\nThe much younger feature implemented as `git replace` set out to remedy\nthose limitations and dangerous bugs.\n\nSeeing as `git replace` is pretty mature by now (since 4228e8bc98\n(replace: add --graft option, 2014-07-19) it can perform the graft\nfile's duties), it is time to deprecate support for the graft file, and\nto retire it eventually.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nReviewed-by: Stefan Beller <sbeller@google.com>\n---\n advice.c                  |  2 ++\n advice.h                  |  1 +\n commit.c                  | 10 ++++++++++\n t/t6001-rev-list-graft.sh |  9 +++++++++\n 4 files changed, 22 insertions(+)\n\ndiff --git a/advice.c b/advice.c\nindex 406efc183ba..4411704fd45 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -19,6 +19,7 @@ int advice_rm_hints = 1;\n int advice_add_embedded_repo = 1;\n int advice_ignored_hook = 1;\n int advice_waiting_for_editor = 1;\n+int advice_graft_file_deprecated = 1;\n \n static struct {\n \tconst char *name;\n@@ -42,6 +43,7 @@ static struct {\n \t{ \"addembeddedrepo\", &advice_add_embedded_repo },\n \t{ \"ignoredhook\", &advice_ignored_hook },\n \t{ \"waitingforeditor\", &advice_waiting_for_editor },\n+\t{ \"graftfiledeprecated\", &advice_graft_file_deprecated },\n \n \t/* make this an alias for backward compatibility */\n \t{ \"pushnonfastforward\", &advice_push_update_rejected }\ndiff --git a/advice.h b/advice.h\nindex 70568fa7922..9f5064e82a8 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -21,6 +21,7 @@ extern int advice_rm_hints;\n extern int advice_add_embedded_repo;\n extern int advice_ignored_hook;\n extern int advice_waiting_for_editor;\n+extern int advice_graft_file_deprecated;\n \n int git_default_advice_config(const char *var, const char *value);\n __attribute__((format (printf, 1, 2)))\ndiff --git a/commit.c b/commit.c\nindex 2952ec987c5..451d3ce8dfe 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -12,6 +12,7 @@\n #include \"prio-queue.h\"\n #include \"sha1-lookup.h\"\n #include \"wt-status.h\"\n+#include \"advice.h\"\n \n static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);\n \n@@ -176,6 +177,15 @@ static int read_graft_file(const char *graft_file)\n \tstruct strbuf buf = STRBUF_INIT;\n \tif (!fp)\n \t\treturn -1;\n+\tif (advice_graft_file_deprecated)\n+\t\tadvise(_(\"Support for <GIT_DIR>/info/grafts is deprecated\\n\"\n+\t\t\t \"and will be removed in a future Git version.\\n\"\n+\t\t\t \"\\n\"\n+\t\t\t \"Please use \\\"git replace --convert-graft-file\\\"\\n\"\n+\t\t\t \"to convert the grafts into replace refs.\\n\"\n+\t\t\t \"\\n\"\n+\t\t\t \"Turn this message off by running\\n\"\n+\t\t\t \"\\\"git config advice.graftFileDeprecated false\\\"\"));\n \twhile (!strbuf_getwholeline(&buf, fp, '\\n')) {\n \t\t/* The format is just \"Commit Parent1 Parent2 ...\\n\" */\n \t\tstruct commit_graft *graft = read_graft_line(&buf);\ndiff --git a/t/t6001-rev-list-graft.sh b/t/t6001-rev-list-graft.sh\nindex 05ddc69cf2a..7504ba47511 100755\n--- a/t/t6001-rev-list-graft.sh\n+++ b/t/t6001-rev-list-graft.sh\n@@ -110,4 +110,13 @@ do\n \t\"\n \n done\n+\n+test_expect_success 'show advice that grafts are deprecated' '\n+\tgit show HEAD 2>err &&\n+\ttest_i18ngrep \"git replace\" err &&\n+\ttest_config advice.graftFileDeprecated false &&\n+\tgit show HEAD 2>err &&\n+\ttest_i18ngrep ! \"git replace\" err\n+'\n+\n test_done\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345286","messageId":"8c0bcade20cf0b475f9b3d15f9dfad37d3ad6d4f.1524262793.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524262793.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 08/11] filter-branch: stop suggesting to use grafts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-20T22:25:16Z","receivedAt":"2018-04-20T22:25:41Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The graft file is deprecated now, so let's use replace refs in the example\nin filter-branch's man page instead.\n\nSuggested-by: Eric Sunshine <sunshine@sunshineco.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-filter-branch.txt | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt\nindex b634043183b..1d4d2f86045 100644\n--- a/Documentation/git-filter-branch.txt\n+++ b/Documentation/git-filter-branch.txt\n@@ -288,7 +288,7 @@ git filter-branch --parent-filter \\\n or even simpler:\n \n -----------------------------------------------\n-echo \"$commit-id $graft-id\" >> .git/info/grafts\n+git replace --graft $commit-id $graft-id\n git filter-branch $graft-id..HEAD\n -----------------------------------------------\n \n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345287","messageId":"853b0a445ab1d78e8cbe19dacd09e70892b34b03.1524262793.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524262793.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 09/11] technical/shallow: describe the relationship with replace refs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-20T22:26:03Z","receivedAt":"2018-04-20T22:26:26Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Now that grafts are deprecated, we should start to assume that readers\nhave no idea what grafts are. So it makes more sense to describe the\n\"shallow\" feature in terms of replace refs.\n\nSuggested-by: Eric Sunshine <sunshine@sunshineco.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/technical/shallow.txt | 19 +++++++++++--------\n 1 file changed, 11 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/technical/shallow.txt b/Documentation/technical/shallow.txt\nindex 5183b154229..b3ff23c25f6 100644\n--- a/Documentation/technical/shallow.txt\n+++ b/Documentation/technical/shallow.txt\n@@ -9,14 +9,17 @@ these commits have no parents.\n *********************************************************\n \n The basic idea is to write the SHA-1s of shallow commits into\n-$GIT_DIR/shallow, and handle its contents like the contents\n-of $GIT_DIR/info/grafts (with the difference that shallow\n-cannot contain parent information).\n-\n-This information is stored in a new file instead of grafts, or\n-even the config, since the user should not touch that file\n-at all (even throughout development of the shallow clone, it\n-was never manually edited!).\n+$GIT_DIR/shallow, and handle its contents similar to replace\n+refs (with the difference that shallow does not actually\n+create those replace refs) and very much like the deprecated\n+graft file (with the difference that shallow commits will\n+always have their parents grafted away, not replaced by\n+different parents).\n+\n+This information is stored in a special-purpose file because the\n+user should not touch that file at all (even throughout\n+development of the shallow clone, it was never manually\n+edited!).\n \n Each line contains exactly one SHA-1. When read, a commit_graft\n will be constructed, which has nr_parent < 0 to make it easier\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345288","messageId":"b391791dceff0589c49e798a5257fea66e9f0101.1524262793.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524262793.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 10/11] technical/shallow: describe why shallow cannot use replace refs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-20T22:26:43Z","receivedAt":"2018-04-20T22:27:07Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"It is tempting to do away with commit_graft altogether (in the long\nhaul), now that grafts are deprecated.\n\nHowever, the shallow feature needs a couple of things that the replace\nrefs cannot fulfill. Let's point that out in the documentation.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/technical/shallow.txt | 7 +++++++\n 1 file changed, 7 insertions(+)\n\ndiff --git a/Documentation/technical/shallow.txt b/Documentation/technical/shallow.txt\nindex b3ff23c25f6..cb79181c2bb 100644\n--- a/Documentation/technical/shallow.txt\n+++ b/Documentation/technical/shallow.txt\n@@ -25,6 +25,13 @@ Each line contains exactly one SHA-1. When read, a commit_graft\n will be constructed, which has nr_parent < 0 to make it easier\n to discern from user provided grafts.\n \n+Note that the shallow feature could not be changed easily to\n+use replace refs: a commit containing a `mergetag` is not allowed\n+to be replaced, not even by a root commit. Such a commit can be\n+made shallow, though. Also, having a `shallow` file explicitly\n+listing all the commits made shallow makes it a *lot* easier to\n+do shallow-specific things such as to deepen the history.\n+\n Since fsck-objects relies on the library to read the objects,\n it honours shallow commits automatically.\n \n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345289","messageId":"6c351dbd2391715a2737533af41dbea846ba294c.1524262793.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524262793.git.johannes.schindelin@gmx.de","subject":"[PATCH v3 11/11] Remove obsolete script to convert grafts to replace refs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-20T22:27:02Z","receivedAt":"2018-04-20T22:27:25Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The functionality is now implemented as `git replace\n--convert-graft-file`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n contrib/convert-grafts-to-replace-refs.sh | 28 -----------------------\n 1 file changed, 28 deletions(-)\n delete mode 100755 contrib/convert-grafts-to-replace-refs.sh\n\ndiff --git a/contrib/convert-grafts-to-replace-refs.sh b/contrib/convert-grafts-to-replace-refs.sh\ndeleted file mode 100755\nindex 0cbc917b8cf..00000000000\n--- a/contrib/convert-grafts-to-replace-refs.sh\n+++ /dev/null\n@@ -1,28 +0,0 @@\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.17.0.windows.1.15.gaa56ade3205\n"},{"id":"345292","messageId":"CAGZ79kaH9RWJ=bK=6tEOt2nXjXopC=uxasH6NUUASieRHL7giA@mail.gmail.com","threadId":"48278","inReplyTo":"8171fd3920846e37c9f1cab6f1ac658dbfb01bfe.1524262793.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v3 01/11] argv_array: offer to split a string by whitespace","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-04-20T23:29:02Z","receivedAt":"2018-04-20T23:29:07Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Fri, Apr 20, 2018 at 3:20 PM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> This is a simple function that will interpret a string as a whitespace\n> delimited list of values, and add those values into the array.\n>\n> Note: this function does not (yet) offer to split by arbitrary delimiters,\n> or keep empty values in case of runs of whitespace, or de-quote Unix shell\n> style. All fo this functionality can be added later, when and if needed.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  argv-array.c | 20 ++++++++++++++++++++\n>  argv-array.h |  1 +\n>  2 files changed, 21 insertions(+)\n>\n> diff --git a/argv-array.c b/argv-array.c\n> index 5d370fa3366..cb5bcd2c064 100644\n> --- a/argv-array.c\n> +++ b/argv-array.c\n> @@ -64,6 +64,26 @@ void argv_array_pop(struct argv_array *array)\n>         array->argc--;\n>  }\n>\n> +void argv_array_split(struct argv_array *array, const char *to_split)\n> +{\n> +       while (isspace(*to_split))\n> +               to_split++;\n> +       for (;;) {\n> +               const char *p = to_split;\n> +\n> +               if (!*p)\n> +                       break;\n> +\n> +               while (*p && !isspace(*p))\n> +                       p++;\n> +               argv_array_push_nodup(array, xstrndup(to_split, p - to_split));\n> +\n> +               while (isspace(*p))\n> +                       p++;\n> +               to_split = p;\n> +       }\n> +}\n\nThe code looks correct to me.\n\nThough this seems so low level, that I find it hard to accept\nto implement yet another low level split function.\nWould reuse of strbuf_split or string_list_split make sense?\n\nLooking at the user in patch 5 you really want to have the\nargv array, though, so it cannot be pushed to an even higher\nabstraction layer and be solved there. You really want a\nstring -> argv array split, which would mean we'd have to\ndo the split via string -> {strbufs, stringlist} and then perform\na conversion from that to argv array and both conversions\nlook ugly as we'd need to iterate their specific data structure\nand push each element itself again.\n\nSo I guess we rather implement it yet another time.\n\nLooking at their function declarations:\n\n/*\n * lots of very good comments for string list splitting\n */\nint string_list_split(struct string_list *list, const char *string,\n          int delim, int maxsplit);\n\n/*\n * lots of very good comments for strbuf splitting\n */\nstatic inline struct strbuf **strbuf_split(const struct strbuf *sb,\n           int terminator)\n\nI find they have comments in common as well as the\nterminator. In the commit message you defer the\nimplementation of a terminating symbol, as we\ncan do it later. Also the isspace takes more than one\ndelimiter, which the others do not.\n\nI am debating myself if I want to ask for a comment, as\nargv-array.h is very short for now and it would be consistent\nnot to add a comment.\n\nThanks,\nStefan\n"},{"id":"345311","messageId":"20180421062054.4290-1-szeder.dev@gmail.com","threadId":"48278","inReplyTo":"de1d455d6e526cf264ce1291c1923b97271969c3.1524262793.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v3 06/11] Add a test for `git replace --convert-graft-file`","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2018-04-21T06:20:54Z","receivedAt":"2018-04-21T06:21:15Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"> The proof, as the saying goes, lies in the pudding. So here is a\n> regression test that not only demonstrates what the option is supposed to\n> accomplish, but also demonstrates that it does accomplish it.\n> \n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  t/t6050-replace.sh | 20 ++++++++++++++++++++\n>  1 file changed, 20 insertions(+)\n> \n> diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh\n> index c630aba657e..8a3ee7c3db9 100755\n> --- a/t/t6050-replace.sh\n> +++ b/t/t6050-replace.sh\n> @@ -444,4 +444,24 @@ test_expect_success GPG '--graft on a commit with a mergetag' '\n>  \tgit replace -d $HASH10\n>  '\n>  \n> +test_expect_success '--convert-graft-file' '\n> +\t: add and convert graft file &&\n> +\tprintf \"%s\\n%s %s\\n\\n# comment\\n%s\\n\" \\\n> +\t\t$(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \\\n> +\t\t>.git/info/grafts &&\n> +\tgit replace --convert-graft-file &&\n> +\ttest_path_is_missing .git/info/grafts &&\n> +\n> +\t: verify that the history is now \"grafted\" &&\n> +\tgit rev-list HEAD >out &&\n> +\ttest_line_count = 4 out &&\n> +\n> +\t: create invalid graft file and verify that it is not deleted &&\n> +\ttest_when_finished \"rm -f .git/info/grafts\" &&\n> +\techo $EMPTY_BLOB $EMPTY_TREE >.git/info/grafts &&\n> +\ttest_must_fail git replace --convert-graft-file 2>err &&\n> +\tgrep \"$EMPTY_BLOB $EMPTY_TREE\" err &&\n\nThis should be 'test_i18ngrep'.  Apparently this error message is\ntranslated, and, consequently, the check fails in a GETTEXT_POISON\nbuild.\n\n> +\tgrep \"$EMPTY_BLOB $EMPTY_TREE\" .git/info/grafts\n> +'\n> +\n>  test_done\n> -- \n> 2.17.0.windows.1.15.gaa56ade3205\n"},{"id":"345326","messageId":"nycvar.QRO.7.76.6.1804211138230.4241@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"48278","inReplyTo":"CAGZ79kaH9RWJ=bK=6tEOt2nXjXopC=uxasH6NUUASieRHL7giA@mail.gmail.com","subject":"Re: [PATCH v3 01/11] argv_array: offer to split a string by whitespace","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T09:39:12Z","receivedAt":"2018-04-21T09:39:26Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Fri, 20 Apr 2018, Stefan Beller wrote:\n\n> On Fri, Apr 20, 2018 at 3:20 PM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > This is a simple function that will interpret a string as a whitespace\n> > delimited list of values, and add those values into the array.\n> >\n> > Note: this function does not (yet) offer to split by arbitrary delimiters,\n> > or keep empty values in case of runs of whitespace, or de-quote Unix shell\n> > style. All fo this functionality can be added later, when and if needed.\n> >\n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> >  argv-array.c | 20 ++++++++++++++++++++\n> >  argv-array.h |  1 +\n> >  2 files changed, 21 insertions(+)\n> >\n> > diff --git a/argv-array.c b/argv-array.c\n> > index 5d370fa3366..cb5bcd2c064 100644\n> > --- a/argv-array.c\n> > +++ b/argv-array.c\n> > @@ -64,6 +64,26 @@ void argv_array_pop(struct argv_array *array)\n> >         array->argc--;\n> >  }\n> >\n> > +void argv_array_split(struct argv_array *array, const char *to_split)\n> > +{\n> > +       while (isspace(*to_split))\n> > +               to_split++;\n> > +       for (;;) {\n> > +               const char *p = to_split;\n> > +\n> > +               if (!*p)\n> > +                       break;\n> > +\n> > +               while (*p && !isspace(*p))\n> > +                       p++;\n> > +               argv_array_push_nodup(array, xstrndup(to_split, p - to_split));\n> > +\n> > +               while (isspace(*p))\n> > +                       p++;\n> > +               to_split = p;\n> > +       }\n> > +}\n> \n> The code looks correct to me.\n> \n> Though this seems so low level, that I find it hard to accept\n> to implement yet another low level split function.\n> Would reuse of strbuf_split or string_list_split make sense?\n> \n> Looking at the user in patch 5 you really want to have the\n> argv array, though, so it cannot be pushed to an even higher\n> abstraction layer and be solved there. You really want a\n> string -> argv array split, which would mean we'd have to\n> do the split via string -> {strbufs, stringlist} and then perform\n> a conversion from that to argv array and both conversions\n> look ugly as we'd need to iterate their specific data structure\n> and push each element itself again.\n\nMaybe we could reconcile all of this by introducing yet another layer of\nindirection? I am wary of this, as I tend to think that layers of\nindirection make things tedious to debug.\n\nBut I could imagine that we could introduce something like\n\n    typedef int (*split_string_fn)(const char *item, size_t len, void *data);\n    int split_string(const char *string, int delimiter, int maxsplit,\n\t\t     split_string_fn fn, void *fn_data);\n\nAnd then argv_array_split() would be implemented like this:\n\n    struct argv_array_split_data {\n\tstruct argv_array *array;\n    };\n\n    static int argv_array_split_fn(const char *item, size_t len, void *data)\n    {\n\tstruct argv_array *array =\n\t    ((struct argv_array_split_data *)data)->array;\n\targv_array_push_nodup(array, xstrndup(item, len));\n\treturn 0;\n    }\n\n    void argv_array_split(struct argv_array *array, const char *string,\n\t\t\t  int delimiter, int maxsplit)\n    {\n\tstruct argv_array_split_data data;\n\tdata.array = array;\n\tsplit_string(string, delimiter, maxsplit, argv_array_split_fn, &data);\n    }\n\nPossible? Yes. Desirable? Dunno. Looks like a lot of effort for little\ngain so far.\n\n> So I guess we rather implement it yet another time.\n> \n> Looking at their function declarations:\n> \n> /*\n>  * lots of very good comments for string list splitting\n>  */\n> int string_list_split(struct string_list *list, const char *string,\n>           int delim, int maxsplit);\n> \n> /*\n>  * lots of very good comments for strbuf splitting\n>  */\n> static inline struct strbuf **strbuf_split(const struct strbuf *sb,\n>            int terminator)\n> \n> I find they have comments in common as well as the\n> terminator. In the commit message you defer the\n> implementation of a terminating symbol, as we\n> can do it later. Also the isspace takes more than one\n> delimiter, which the others do not.\n> \n> I am debating myself if I want to ask for a comment, as\n> argv-array.h is very short for now and it would be consistent\n> not to add a comment.\n\nThat was my thinking.\n\nAnd I thought further: once argv_array_split() becomes more complex, we\nwould need to consider again whether we want that level of indirection\n(and consolidation), and then also whether we need a comment.\n\nOn the other hand, argv_array's name suggests that it handles\ncommand-lines, so I think you are correct that at least a little comment\nis needed to state that this does not handle quoted arguments.\n\nCiao,\nDscho\n"},{"id":"345327","messageId":"nycvar.QRO.7.76.6.1804211140170.4241@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"48278","inReplyTo":"20180421062054.4290-1-szeder.dev@gmail.com","subject":"Re: [PATCH v3 06/11] Add a test for `git replace --convert-graft-file`","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T09:42:23Z","receivedAt":"2018-04-21T09:42:49Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Gábor,\n\nOn Sat, 21 Apr 2018, SZEDER Gábor wrote:\n\n> > The proof, as the saying goes, lies in the pudding. So here is a\n> > regression test that not only demonstrates what the option is supposed to\n> > accomplish, but also demonstrates that it does accomplish it.\n> > \n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> >  t/t6050-replace.sh | 20 ++++++++++++++++++++\n> >  1 file changed, 20 insertions(+)\n> > \n> > diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh\n> > index c630aba657e..8a3ee7c3db9 100755\n> > --- a/t/t6050-replace.sh\n> > +++ b/t/t6050-replace.sh\n> > @@ -444,4 +444,24 @@ test_expect_success GPG '--graft on a commit with a mergetag' '\n> >  \tgit replace -d $HASH10\n> >  '\n> >  \n> > +test_expect_success '--convert-graft-file' '\n> > +\t: add and convert graft file &&\n> > +\tprintf \"%s\\n%s %s\\n\\n# comment\\n%s\\n\" \\\n> > +\t\t$(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \\\n> > +\t\t>.git/info/grafts &&\n> > +\tgit replace --convert-graft-file &&\n> > +\ttest_path_is_missing .git/info/grafts &&\n> > +\n> > +\t: verify that the history is now \"grafted\" &&\n> > +\tgit rev-list HEAD >out &&\n> > +\ttest_line_count = 4 out &&\n> > +\n> > +\t: create invalid graft file and verify that it is not deleted &&\n> > +\ttest_when_finished \"rm -f .git/info/grafts\" &&\n> > +\techo $EMPTY_BLOB $EMPTY_TREE >.git/info/grafts &&\n> > +\ttest_must_fail git replace --convert-graft-file 2>err &&\n> > +\tgrep \"$EMPTY_BLOB $EMPTY_TREE\" err &&\n> \n> This should be 'test_i18ngrep'.  Apparently this error message is\n> translated, and, consequently, the check fails in a GETTEXT_POISON\n> build.\n\nSure enough, you're right! I thought it would not be translated, what with\nbeing an parameter to a formatted string...\n\nWill fix,\nDscho"},{"id":"345328","messageId":"cover.1524303776.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524262793.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 00/11] Deprecate .git/info/grafts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T09:43:29Z","receivedAt":"2018-04-21T09:43:52Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"It is fragile, as there is no way for the revision machinery to say \"but\nnow I want to traverse the graph ignoring the graft file\" e.g. when\npushing commits to a remote repository (which, as a consequence, can\nmiss commits).\n\nAnd we already have a better solution with `git replace --graft <comit>\n[<parent>...]`.\n\nChanges since v3:\n\n- The argv_array_split() declaration now has a clear comment indicating\n  that it does not perform any \"un-quoting\" but goes purely by\n  whitespace.\n\n- Fixed t6050 under GETTEXT_POISON.\n\n\nJohannes Schindelin (11):\n  argv_array: offer to split a string by whitespace\n  commit: Let the callback of for_each_mergetag return on error\n  replace: avoid using die() to indicate a bug\n  replace: \"libify\" create_graft() and callees\n  replace: introduce --convert-graft-file\n  Add a test for `git replace --convert-graft-file`\n  Deprecate support for .git/info/grafts\n  filter-branch: stop suggesting to use grafts\n  technical/shallow: describe the relationship with replace refs\n  technical/shallow: describe why shallow cannot use replace refs\n  Remove obsolete script to convert grafts to replace refs\n\n Documentation/git-filter-branch.txt       |   2 +-\n Documentation/git-replace.txt             |  11 +-\n Documentation/technical/shallow.txt       |  24 ++-\n advice.c                                  |   2 +\n advice.h                                  |   1 +\n argv-array.c                              |  20 +++\n argv-array.h                              |   2 +\n builtin/replace.c                         | 189 +++++++++++++++-------\n commit.c                                  |  18 ++-\n commit.h                                  |   4 +-\n contrib/convert-grafts-to-replace-refs.sh |  28 ----\n log-tree.c                                |  13 +-\n t/t6001-rev-list-graft.sh                 |   9 ++\n t/t6050-replace.sh                        |  20 +++\n 14 files changed, 236 insertions(+), 107 deletions(-)\n delete mode 100755 contrib/convert-grafts-to-replace-refs.sh\n\n\nbase-commit: fe0a9eaf31dd0c349ae4308498c33a5c3794b293\nPublished-As: https://github.com/dscho/git/releases/tag/deprecate-grafts-v4\nFetch-It-Via: git fetch https://github.com/dscho/git deprecate-grafts-v4\n\nInterdiff vs v3:\n diff --git a/argv-array.h b/argv-array.h\n index c7c397695df..750c30d2f2c 100644\n --- a/argv-array.h\n +++ b/argv-array.h\n @@ -19,6 +19,7 @@ LAST_ARG_MUST_BE_NULL\n  void argv_array_pushl(struct argv_array *, ...);\n  void argv_array_pushv(struct argv_array *, const char **);\n  void argv_array_pop(struct argv_array *);\n +/* Splits by whitespace; does not handle quoted arguments! */\n  void argv_array_split(struct argv_array *, const char *);\n  void argv_array_clear(struct argv_array *);\n  const char **argv_array_detach(struct argv_array *);\n diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh\n index 8a3ee7c3db9..bed86a0af3d 100755\n --- a/t/t6050-replace.sh\n +++ b/t/t6050-replace.sh\n @@ -460,8 +460,8 @@ test_expect_success '--convert-graft-file' '\n  \ttest_when_finished \"rm -f .git/info/grafts\" &&\n  \techo $EMPTY_BLOB $EMPTY_TREE >.git/info/grafts &&\n  \ttest_must_fail git replace --convert-graft-file 2>err &&\n -\tgrep \"$EMPTY_BLOB $EMPTY_TREE\" err &&\n -\tgrep \"$EMPTY_BLOB $EMPTY_TREE\" .git/info/grafts\n +\ttest_i18ngrep \"$EMPTY_BLOB $EMPTY_TREE\" err &&\n +\ttest_i18ngrep \"$EMPTY_BLOB $EMPTY_TREE\" .git/info/grafts\n  '\n  \n  test_done\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n"},{"id":"345329","messageId":"6ec155b834768f38765c63907a48b1bd7ab6bafc.1524303776.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524303776.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 01/11] argv_array: offer to split a string by whitespace","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T09:46:12Z","receivedAt":"2018-04-21T09:46:40Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This is a simple function that will interpret a string as a whitespace\ndelimited list of values, and add those values into the array.\n\nNote: this function does not (yet) offer to split by arbitrary delimiters,\nor keep empty values in case of runs of whitespace, or de-quote Unix shell\nstyle. All fo this functionality can be added later, when and if needed.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n argv-array.c | 20 ++++++++++++++++++++\n argv-array.h |  2 ++\n 2 files changed, 22 insertions(+)\n\ndiff --git a/argv-array.c b/argv-array.c\nindex 5d370fa3366..cb5bcd2c064 100644\n--- a/argv-array.c\n+++ b/argv-array.c\n@@ -64,6 +64,26 @@ void argv_array_pop(struct argv_array *array)\n \tarray->argc--;\n }\n \n+void argv_array_split(struct argv_array *array, const char *to_split)\n+{\n+\twhile (isspace(*to_split))\n+\t\tto_split++;\n+\tfor (;;) {\n+\t\tconst char *p = to_split;\n+\n+\t\tif (!*p)\n+\t\t\tbreak;\n+\n+\t\twhile (*p && !isspace(*p))\n+\t\t\tp++;\n+\t\targv_array_push_nodup(array, xstrndup(to_split, p - to_split));\n+\n+\t\twhile (isspace(*p))\n+\t\t\tp++;\n+\t\tto_split = p;\n+\t}\n+}\n+\n void argv_array_clear(struct argv_array *array)\n {\n \tif (array->argv != empty_argv) {\ndiff --git a/argv-array.h b/argv-array.h\nindex 29056e49a12..750c30d2f2c 100644\n--- a/argv-array.h\n+++ b/argv-array.h\n@@ -19,6 +19,8 @@ LAST_ARG_MUST_BE_NULL\n void argv_array_pushl(struct argv_array *, ...);\n void argv_array_pushv(struct argv_array *, const char **);\n void argv_array_pop(struct argv_array *);\n+/* Splits by whitespace; does not handle quoted arguments! */\n+void argv_array_split(struct argv_array *, const char *);\n void argv_array_clear(struct argv_array *);\n const char **argv_array_detach(struct argv_array *);\n \n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345330","messageId":"9e3238893668e2d3d0b1f2f17aebac9b90a5759c.1524303776.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524303776.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 02/11] commit: Let the callback of for_each_mergetag return on error","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T09:46:43Z","receivedAt":"2018-04-21T09:47:22Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This is yet another patch to be filed under the keyword \"libification\".\n\nThere is one subtle change in behavior here, where a `git log` that has\nbeen asked to show the mergetags would now stop reporting the mergetags\nupon the first failure, whereas previously, it would have continued to the\nnext mergetag, if any.\n\nIn practice, that change should not matter, as it is 1) uncommon to\nperform octopus merges using multiple tags as merge heads, and 2) when the\nuser asks to be shown those tags, they really should be there.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/replace.c |  8 ++++----\n commit.c          |  8 +++++---\n commit.h          |  4 ++--\n log-tree.c        | 13 +++++++------\n 4 files changed, 18 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 935647be6bd..245d3f4164e 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -345,7 +345,7 @@ struct check_mergetag_data {\n \tconst char **argv;\n };\n \n-static void check_one_mergetag(struct commit *commit,\n+static int check_one_mergetag(struct commit *commit,\n \t\t\t       struct commit_extra_header *extra,\n \t\t\t       void *data)\n {\n@@ -368,20 +368,20 @@ static void check_one_mergetag(struct commit *commit,\n \t\tif (get_oid(mergetag_data->argv[i], &oid) < 0)\n \t\t\tdie(_(\"Not a valid object name: '%s'\"), mergetag_data->argv[i]);\n \t\tif (!oidcmp(&tag->tagged->oid, &oid))\n-\t\t\treturn; /* found */\n+\t\t\treturn 0; /* found */\n \t}\n \n \tdie(_(\"original commit '%s' contains mergetag '%s' that is discarded; \"\n \t      \"use --edit instead of --graft\"), ref, oid_to_hex(&tag_oid));\n }\n \n-static void check_mergetags(struct commit *commit, int argc, const char **argv)\n+static int 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+\treturn for_each_mergetag(check_one_mergetag, commit, &mergetag_data);\n }\n \n static int create_graft(int argc, const char **argv, int force)\ndiff --git a/commit.c b/commit.c\nindex ca474a7c112..2952ec987c5 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -1288,17 +1288,19 @@ struct commit_extra_header *read_commit_extra_headers(struct commit *commit,\n \treturn extra;\n }\n \n-void for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data)\n+int for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data)\n {\n \tstruct commit_extra_header *extra, *to_free;\n+\tint res = 0;\n \n \tto_free = read_commit_extra_headers(commit, NULL);\n-\tfor (extra = to_free; extra; extra = extra->next) {\n+\tfor (extra = to_free; !res && extra; extra = extra->next) {\n \t\tif (strcmp(extra->key, \"mergetag\"))\n \t\t\tcontinue; /* not a merge tag */\n-\t\tfn(commit, extra, data);\n+\t\tres = fn(commit, extra, data);\n \t}\n \tfree_commit_extra_headers(to_free);\n+\treturn res;\n }\n \n static inline int standard_header_field(const char *field, size_t len)\ndiff --git a/commit.h b/commit.h\nindex 0fb8271665c..9000895ad91 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -291,10 +291,10 @@ extern const char *find_commit_header(const char *msg, const char *key,\n /* Find the end of the log message, the right place for a new trailer. */\n extern int ignore_non_trailer(const char *buf, size_t len);\n \n-typedef void (*each_mergetag_fn)(struct commit *commit, struct commit_extra_header *extra,\n+typedef int (*each_mergetag_fn)(struct commit *commit, struct commit_extra_header *extra,\n \t\t\t\t void *cb_data);\n \n-extern void for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data);\n+extern int for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data);\n \n struct merge_remote_desc {\n \tstruct object *obj; /* the named object, could be a tag */\ndiff --git a/log-tree.c b/log-tree.c\nindex d1c0bedf244..f3a51a6e726 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -488,9 +488,9 @@ static int is_common_merge(const struct commit *commit)\n \t\t&& !commit->parents->next->next);\n }\n \n-static void show_one_mergetag(struct commit *commit,\n-\t\t\t      struct commit_extra_header *extra,\n-\t\t\t      void *data)\n+static int show_one_mergetag(struct commit *commit,\n+\t\t\t     struct commit_extra_header *extra,\n+\t\t\t     void *data)\n {\n \tstruct rev_info *opt = (struct rev_info *)data;\n \tstruct object_id oid;\n@@ -502,7 +502,7 @@ static void show_one_mergetag(struct commit *commit,\n \thash_object_file(extra->value, extra->len, type_name(OBJ_TAG), &oid);\n \ttag = lookup_tag(&oid);\n \tif (!tag)\n-\t\treturn; /* error message already given */\n+\t\treturn -1; /* error message already given */\n \n \tstrbuf_init(&verify_message, 256);\n \tif (parse_tag_buffer(tag, extra->value, extra->len))\n@@ -536,11 +536,12 @@ static void show_one_mergetag(struct commit *commit,\n \n \tshow_sig_lines(opt, status, verify_message.buf);\n \tstrbuf_release(&verify_message);\n+\treturn 0;\n }\n \n-static void show_mergetag(struct rev_info *opt, struct commit *commit)\n+static int show_mergetag(struct rev_info *opt, struct commit *commit)\n {\n-\tfor_each_mergetag(show_one_mergetag, commit, opt);\n+\treturn for_each_mergetag(show_one_mergetag, commit, opt);\n }\n \n void show_log(struct rev_info *opt)\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345331","messageId":"aade5dab3392eba4090d1f285f26dc94fa56aee9.1524303776.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524303776.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 03/11] replace: avoid using die() to indicate a bug","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T09:47:19Z","receivedAt":"2018-04-21T09:48:15Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"We have the BUG() macro for that purpose.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/replace.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 245d3f4164e..e345a5a0f1c 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -501,6 +501,6 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n \t\treturn list_replace_refs(argv[0], format);\n \n \tdefault:\n-\t\tdie(\"BUG: invalid cmdmode %d\", (int)cmdmode);\n+\t\tBUG(\"invalid cmdmode %d\", (int)cmdmode);\n \t}\n }\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345332","messageId":"f962f8043fb1125b47090a82c35e2d67d3d9d216.1524303776.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524303776.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 04/11] replace: \"libify\" create_graft() and callees","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T09:47:50Z","receivedAt":"2018-04-21T09:48:15Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"File this away as yet another patch in the \"libification\" category.\n\nAs with all useful functions, in the next commit we want to use\ncreate_graft() from a higher-level function where it would be\ninconvenient if the called function simply die()s: if there is a\nproblem, we want to let the user know how to proceed, and the callee\nsimply has no way of knowing what to say.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/replace.c | 135 ++++++++++++++++++++++++++++------------------\n 1 file changed, 84 insertions(+), 51 deletions(-)\n\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex e345a5a0f1c..f63df405fd0 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -79,9 +79,9 @@ static int list_replace_refs(const char *pattern, const char *format)\n \telse if (!strcmp(format, \"long\"))\n \t\tdata.format = REPLACE_FORMAT_LONG;\n \telse\n-\t\tdie(\"invalid replace format '%s'\\n\"\n-\t\t    \"valid formats are 'short', 'medium' and 'long'\\n\",\n-\t\t    format);\n+\t\treturn error(\"invalid replace format '%s'\\n\"\n+\t\t\t     \"valid formats are 'short', 'medium' and 'long'\\n\",\n+\t\t\t     format);\n \n \tfor_each_replace_ref(show_reference, (void *)&data);\n \n@@ -134,7 +134,7 @@ static int delete_replace_ref(const char *name, const char *ref,\n \treturn 0;\n }\n \n-static void check_ref_valid(struct object_id *object,\n+static int check_ref_valid(struct object_id *object,\n \t\t\t    struct object_id *prev,\n \t\t\t    struct strbuf *ref,\n \t\t\t    int force)\n@@ -142,12 +142,13 @@ static void check_ref_valid(struct object_id *object,\n \tstrbuf_reset(ref);\n \tstrbuf_addf(ref, \"%s%s\", git_replace_ref_base, oid_to_hex(object));\n \tif (check_refname_format(ref->buf, 0))\n-\t\tdie(\"'%s' is not a valid ref name.\", ref->buf);\n+\t\treturn error(\"'%s' is not a valid ref name.\", ref->buf);\n \n \tif (read_ref(ref->buf, prev))\n \t\toidclr(prev);\n \telse if (!force)\n-\t\tdie(\"replace ref '%s' already exists\", ref->buf);\n+\t\treturn error(\"replace ref '%s' already exists\", ref->buf);\n+\treturn 0;\n }\n \n static int replace_object_oid(const char *object_ref,\n@@ -161,28 +162,31 @@ static int replace_object_oid(const char *object_ref,\n \tstruct strbuf ref = STRBUF_INIT;\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n+\tint res = 0;\n \n \tobj_type = oid_object_info(object, NULL);\n \trepl_type = oid_object_info(repl, NULL);\n \tif (!force && obj_type != repl_type)\n-\t\tdie(\"Objects must be of the same type.\\n\"\n-\t\t    \"'%s' points to a replaced object of type '%s'\\n\"\n-\t\t    \"while '%s' points to a replacement object of type '%s'.\",\n-\t\t    object_ref, type_name(obj_type),\n-\t\t    replace_ref, type_name(repl_type));\n+\t\treturn error(\"Objects must be of the same type.\\n\"\n+\t\t\t     \"'%s' points to a replaced object of type '%s'\\n\"\n+\t\t\t     \"while '%s' points to a replacement object of \"\n+\t\t\t     \"type '%s'.\",\n+\t\t\t     object_ref, type_name(obj_type),\n+\t\t\t     replace_ref, type_name(repl_type));\n \n-\tcheck_ref_valid(object, &prev, &ref, force);\n+\tif (check_ref_valid(object, &prev, &ref, force))\n+\t\treturn -1;\n \n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, ref.buf, repl, &prev,\n \t\t\t\t   0, NULL, &err) ||\n \t    ref_transaction_commit(transaction, &err))\n-\t\tdie(\"%s\", err.buf);\n+\t\tres = error(\"%s\", err.buf);\n \n \tref_transaction_free(transaction);\n \tstrbuf_release(&ref);\n-\treturn 0;\n+\treturn res;\n }\n \n static int replace_object(const char *object_ref, const char *replace_ref, int force)\n@@ -190,9 +194,11 @@ static int replace_object(const char *object_ref, const char *replace_ref, int f\n \tstruct object_id object, repl;\n \n \tif (get_oid(object_ref, &object))\n-\t\tdie(\"Failed to resolve '%s' as a valid ref.\", object_ref);\n+\t\treturn error(\"Failed to resolve '%s' as a valid ref.\",\n+\t\t\t     object_ref);\n \tif (get_oid(replace_ref, &repl))\n-\t\tdie(\"Failed to resolve '%s' as a valid ref.\", replace_ref);\n+\t\treturn error(\"Failed to resolve '%s' as a valid ref.\",\n+\t\t\t     replace_ref);\n \n \treturn replace_object_oid(object_ref, &object, replace_ref, &repl, force);\n }\n@@ -202,7 +208,7 @@ static int replace_object(const char *object_ref, const char *replace_ref, int f\n  * If \"raw\" is true, then the object's raw contents are printed according to\n  * \"type\". Otherwise, we pretty-print the contents for human editing.\n  */\n-static void export_object(const struct object_id *oid, enum object_type type,\n+static int export_object(const struct object_id *oid, enum object_type type,\n \t\t\t  int raw, const char *filename)\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n@@ -210,7 +216,7 @@ static void export_object(const struct object_id *oid, enum object_type type,\n \n \tfd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);\n \tif (fd < 0)\n-\t\tdie_errno(\"unable to open %s for writing\", filename);\n+\t\treturn error_errno(\"unable to open %s for writing\", filename);\n \n \targv_array_push(&cmd.args, \"--no-replace-objects\");\n \targv_array_push(&cmd.args, \"cat-file\");\n@@ -223,7 +229,8 @@ static void export_object(const struct object_id *oid, enum object_type type,\n \tcmd.out = fd;\n \n \tif (run_command(&cmd))\n-\t\tdie(\"cat-file reported failure\");\n+\t\treturn error(\"cat-file reported failure\");\n+\treturn 0;\n }\n \n /*\n@@ -231,14 +238,14 @@ static void export_object(const struct object_id *oid, enum object_type type,\n  * interpreting it as \"type\", and writing the result to the object database.\n  * The sha1 of the written object is returned via sha1.\n  */\n-static void import_object(struct object_id *oid, enum object_type type,\n+static int import_object(struct object_id *oid, enum object_type type,\n \t\t\t  int raw, const char *filename)\n {\n \tint fd;\n \n \tfd = open(filename, O_RDONLY);\n \tif (fd < 0)\n-\t\tdie_errno(\"unable to open %s for reading\", filename);\n+\t\treturn error_errno(\"unable to open %s for reading\", filename);\n \n \tif (!raw && type == OBJ_TREE) {\n \t\tconst char *argv[] = { \"mktree\", NULL };\n@@ -250,27 +257,38 @@ static void import_object(struct object_id *oid, enum object_type type,\n \t\tcmd.in = fd;\n \t\tcmd.out = -1;\n \n-\t\tif (start_command(&cmd))\n-\t\t\tdie(\"unable to spawn mktree\");\n+\t\tif (start_command(&cmd)) {\n+\t\t\tclose(fd);\n+\t\t\treturn error(\"unable to spawn mktree\");\n+\t\t}\n \n-\t\tif (strbuf_read(&result, cmd.out, 41) < 0)\n-\t\t\tdie_errno(\"unable to read from mktree\");\n+\t\tif (strbuf_read(&result, cmd.out, 41) < 0) {\n+\t\t\tclose(fd);\n+\t\t\tclose(cmd.out);\n+\t\t\treturn error_errno(\"unable to read from mktree\");\n+\t\t}\n \t\tclose(cmd.out);\n \n-\t\tif (finish_command(&cmd))\n-\t\t\tdie(\"mktree reported failure\");\n-\t\tif (get_oid_hex(result.buf, oid) < 0)\n-\t\t\tdie(\"mktree did not return an object name\");\n+\t\tif (finish_command(&cmd)) {\n+\t\t\tstrbuf_release(&result);\n+\t\t\treturn error(\"mktree reported failure\");\n+\t\t}\n+\t\tif (get_oid_hex(result.buf, oid) < 0) {\n+\t\t\tstrbuf_release(&result);\n+\t\t\treturn error(\"mktree did not return an object name\");\n+\t\t}\n \n \t\tstrbuf_release(&result);\n \t} else {\n \t\tstruct stat st;\n \t\tint flags = HASH_FORMAT_CHECK | HASH_WRITE_OBJECT;\n \n-\t\tif (fstat(fd, &st) < 0)\n-\t\t\tdie_errno(\"unable to fstat %s\", filename);\n+\t\tif (fstat(fd, &st) < 0) {\n+\t\t\tclose(fd);\n+\t\t\treturn error_errno(\"unable to fstat %s\", filename);\n+\t\t}\n \t\tif (index_fd(oid, fd, &st, type, NULL, flags) < 0)\n-\t\t\tdie(\"unable to write object to database\");\n+\t\t\treturn error(\"unable to write object to database\");\n \t\t/* index_fd close()s fd for us */\n \t}\n \n@@ -278,6 +296,7 @@ static void import_object(struct object_id *oid, enum object_type type,\n \t * No need to close(fd) here; both run-command and index-fd\n \t * will have done it for us.\n \t */\n+\treturn 0;\n }\n \n static int edit_and_replace(const char *object_ref, int force, int raw)\n@@ -288,19 +307,23 @@ static int edit_and_replace(const char *object_ref, int force, int raw)\n \tstruct strbuf ref = STRBUF_INIT;\n \n \tif (get_oid(object_ref, &old_oid) < 0)\n-\t\tdie(\"Not a valid object name: '%s'\", object_ref);\n+\t\treturn error(\"Not a valid object name: '%s'\", object_ref);\n \n \ttype = oid_object_info(&old_oid, NULL);\n \tif (type < 0)\n-\t\tdie(\"unable to get object type for %s\", oid_to_hex(&old_oid));\n+\t\treturn error(\"unable to get object type for %s\",\n+\t\t\t     oid_to_hex(&old_oid));\n \n-\tcheck_ref_valid(&old_oid, &prev, &ref, force);\n+\tif (check_ref_valid(&old_oid, &prev, &ref, force))\n+\t\treturn -1;\n \tstrbuf_release(&ref);\n \n-\texport_object(&old_oid, type, raw, tmpfile);\n+\tif (export_object(&old_oid, type, raw, tmpfile))\n+\t\treturn -1;\n \tif (launch_editor(tmpfile, NULL, NULL) < 0)\n-\t\tdie(\"editing object file failed\");\n-\timport_object(&new_oid, type, raw, tmpfile);\n+\t\treturn error(\"editing object file failed\");\n+\tif (import_object(&new_oid, type, raw, tmpfile))\n+\t\treturn -1;\n \n \tfree(tmpfile);\n \n@@ -310,7 +333,7 @@ static int edit_and_replace(const char *object_ref, int force, int raw)\n \treturn replace_object_oid(object_ref, &old_oid, \"replacement\", &new_oid, force);\n }\n \n-static void replace_parents(struct strbuf *buf, int argc, const char **argv)\n+static int 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@@ -328,8 +351,10 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv)\n \tfor (i = 0; i < argc; i++) {\n \t\tstruct object_id oid;\n \t\tif (get_oid(argv[i], &oid) < 0)\n-\t\t\tdie(_(\"Not a valid object name: '%s'\"), argv[i]);\n-\t\tlookup_commit_or_die(&oid, argv[i]);\n+\t\t\treturn error(_(\"Not a valid object name: '%s'\"),\n+\t\t\t\t     argv[i]);\n+\t\tif (!lookup_commit_reference(&oid))\n+\t\t\treturn error(_(\"could not parse %s\"), argv[i]);\n \t\tstrbuf_addf(&new_parents, \"parent %s\\n\", oid_to_hex(&oid));\n \t}\n \n@@ -338,6 +363,7 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv)\n \t\t      new_parents.buf, new_parents.len);\n \n \tstrbuf_release(&new_parents);\n+\treturn 0;\n }\n \n struct check_mergetag_data {\n@@ -358,21 +384,23 @@ static int check_one_mergetag(struct commit *commit,\n \thash_object_file(extra->value, extra->len, type_name(OBJ_TAG), &tag_oid);\n \ttag = lookup_tag(&tag_oid);\n \tif (!tag)\n-\t\tdie(_(\"bad mergetag in commit '%s'\"), ref);\n+\t\treturn error(_(\"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+\t\treturn error(_(\"malformed mergetag in commit '%s'\"), ref);\n \n \t/* iterate over new parents */\n \tfor (i = 1; i < mergetag_data->argc; i++) {\n \t\tstruct object_id oid;\n \t\tif (get_oid(mergetag_data->argv[i], &oid) < 0)\n-\t\t\tdie(_(\"Not a valid object name: '%s'\"), mergetag_data->argv[i]);\n+\t\t\treturn error(_(\"Not a valid object name: '%s'\"),\n+\t\t\t\t     mergetag_data->argv[i]);\n \t\tif (!oidcmp(&tag->tagged->oid, &oid))\n \t\t\treturn 0; /* found */\n \t}\n \n-\tdie(_(\"original commit '%s' contains mergetag '%s' that is discarded; \"\n-\t      \"use --edit instead of --graft\"), ref, oid_to_hex(&tag_oid));\n+\treturn error(_(\"original commit '%s' contains mergetag '%s' that is \"\n+\t\t       \"discarded; use --edit instead of --graft\"), ref,\n+\t\t     oid_to_hex(&tag_oid));\n }\n \n static int check_mergetags(struct commit *commit, int argc, const char **argv)\n@@ -394,24 +422,29 @@ static int create_graft(int argc, const char **argv, int force)\n \tunsigned long size;\n \n \tif (get_oid(old_ref, &old_oid) < 0)\n-\t\tdie(_(\"Not a valid object name: '%s'\"), old_ref);\n-\tcommit = lookup_commit_or_die(&old_oid, old_ref);\n+\t\treturn error(_(\"Not a valid object name: '%s'\"), old_ref);\n+\tcommit = lookup_commit_reference(&old_oid);\n+\tif (!commit)\n+\t\treturn error(_(\"could not parse %s\"), 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+\tif (replace_parents(&buf, argc - 1, &argv[1]) < 0)\n+\t\treturn -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-\tcheck_mergetags(commit, argc, argv);\n+\tif (check_mergetags(commit, argc, argv))\n+\t\treturn -1;\n \n \tif (write_object_file(buf.buf, buf.len, commit_type, &new_oid))\n-\t\tdie(_(\"could not write replacement commit for: '%s'\"), old_ref);\n+\t\treturn error(_(\"could not write replacement commit for: '%s'\"),\n+\t\t\t     old_ref);\n \n \tstrbuf_release(&buf);\n \n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345333","messageId":"fd4ea54ad8da5e4e93fafd558e104b0a19f6268c.1524303776.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524303776.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 05/11] replace: introduce --convert-graft-file","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T09:48:17Z","receivedAt":"2018-04-21T09:48:41Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This option is intended to help with the transition away from the\nnow-deprecated graft file.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-replace.txt | 11 ++++++---\n builtin/replace.c             | 44 ++++++++++++++++++++++++++++++++++-\n 2 files changed, 51 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-replace.txt b/Documentation/git-replace.txt\nindex e5c57ae6ef4..246dc9943c2 100644\n--- a/Documentation/git-replace.txt\n+++ b/Documentation/git-replace.txt\n@@ -11,6 +11,7 @@ SYNOPSIS\n 'git replace' [-f] <object> <replacement>\n 'git replace' [-f] --edit <object>\n 'git replace' [-f] --graft <commit> [<parent>...]\n+'git replace' [-f] --convert-graft-file\n 'git replace' -d <object>...\n 'git replace' [--format=<format>] [-l [<pattern>]]\n \n@@ -87,9 +88,13 @@ 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. 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+\tcommit. Use `--convert-graft-file` to convert a\n+\t`$GIT_DIR/info/grafts` file and use replace refs instead.\n+\n+--convert-graft-file::\n+\tCreates graft commits for all entries in `$GIT_DIR/info/grafts`\n+\tand deletes that file upon success. The purpose is to help users\n+\twith transitioning off of the now-deprecated graft file.\n \n -l <pattern>::\n --list <pattern>::\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex f63df405fd0..6b3e0301e90 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -20,6 +20,7 @@ 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 [-f] --convert-graft-file\"),\n \tN_(\"git replace -d <object>...\"),\n \tN_(\"git replace [--format=<format>] [-l [<pattern>]]\"),\n \tNULL\n@@ -454,6 +455,38 @@ static int create_graft(int argc, const char **argv, int force)\n \treturn replace_object_oid(old_ref, &old_oid, \"replacement\", &new_oid, force);\n }\n \n+static int convert_graft_file(int force)\n+{\n+\tconst char *graft_file = get_graft_file();\n+\tFILE *fp = fopen_or_warn(graft_file, \"r\");\n+\tstruct strbuf buf = STRBUF_INIT, err = STRBUF_INIT;\n+\tstruct argv_array args = ARGV_ARRAY_INIT;\n+\n+\tif (!fp)\n+\t\treturn -1;\n+\n+\twhile (strbuf_getline(&buf, fp) != EOF) {\n+\t\tif (*buf.buf == '#')\n+\t\t\tcontinue;\n+\n+\t\targv_array_split(&args, buf.buf);\n+\t\tif (args.argc && create_graft(args.argc, args.argv, force))\n+\t\t\tstrbuf_addf(&err, \"\\n\\t%s\", buf.buf);\n+\t\targv_array_clear(&args);\n+\t}\n+\n+\tstrbuf_release(&buf);\n+\targv_array_clear(&args);\n+\n+\tif (!err.len)\n+\t\treturn unlink_or_warn(graft_file);\n+\n+\twarning(_(\"could not convert the following graft(s):\\n%s\"), err.buf);\n+\tstrbuf_release(&err);\n+\n+\treturn -1;\n+}\n+\n int cmd_replace(int argc, const char **argv, const char *prefix)\n {\n \tint force = 0;\n@@ -465,6 +498,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n \t\tMODE_DELETE,\n \t\tMODE_EDIT,\n \t\tMODE_GRAFT,\n+\t\tMODE_CONVERT_GRAFT_FILE,\n \t\tMODE_REPLACE\n \t} cmdmode = MODE_UNSPECIFIED;\n \tstruct option options[] = {\n@@ -472,6 +506,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\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_CMDMODE(0, \"convert-graft-file\", &cmdmode, N_(\"convert existing graft file\"), MODE_CONVERT_GRAFT_FILE),\n \t\tOPT_BOOL_F('f', \"force\", &force, N_(\"replace the ref if it exists\"),\n \t\t\t   PARSE_OPT_NOCOMPLETE),\n \t\tOPT_BOOL(0, \"raw\", &raw, N_(\"do not pretty-print contents for --edit\")),\n@@ -494,7 +529,8 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n \tif (force &&\n \t    cmdmode != MODE_REPLACE &&\n \t    cmdmode != MODE_EDIT &&\n-\t    cmdmode != MODE_GRAFT)\n+\t    cmdmode != MODE_GRAFT &&\n+\t    cmdmode != MODE_CONVERT_GRAFT_FILE)\n \t\tusage_msg_opt(\"-f only makes sense when writing a replacement\",\n \t\t\t      git_replace_usage, options);\n \n@@ -527,6 +563,12 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n \t\t\t\t      git_replace_usage, options);\n \t\treturn create_graft(argc, argv, force);\n \n+\tcase MODE_CONVERT_GRAFT_FILE:\n+\t\tif (argc != 0)\n+\t\t\tusage_msg_opt(\"--convert-graft-file takes no argument\",\n+\t\t\t\t      git_replace_usage, options);\n+\t\treturn !!convert_graft_file(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.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345334","messageId":"1b703f9b9d5c182d3ade6ef776a04c977638690b.1524303776.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524303776.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 06/11] Add a test for `git replace --convert-graft-file`","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T09:48:54Z","receivedAt":"2018-04-21T09:49:17Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The proof, as the saying goes, lies in the pudding. So here is a\nregression test that not only demonstrates what the option is supposed to\naccomplish, but also demonstrates that it does accomplish it.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/t6050-replace.sh | 20 ++++++++++++++++++++\n 1 file changed, 20 insertions(+)\n\ndiff --git a/t/t6050-replace.sh b/t/t6050-replace.sh\nindex c630aba657e..bed86a0af3d 100755\n--- a/t/t6050-replace.sh\n+++ b/t/t6050-replace.sh\n@@ -444,4 +444,24 @@ test_expect_success GPG '--graft on a commit with a mergetag' '\n \tgit replace -d $HASH10\n '\n \n+test_expect_success '--convert-graft-file' '\n+\t: add and convert graft file &&\n+\tprintf \"%s\\n%s %s\\n\\n# comment\\n%s\\n\" \\\n+\t\t$(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \\\n+\t\t>.git/info/grafts &&\n+\tgit replace --convert-graft-file &&\n+\ttest_path_is_missing .git/info/grafts &&\n+\n+\t: verify that the history is now \"grafted\" &&\n+\tgit rev-list HEAD >out &&\n+\ttest_line_count = 4 out &&\n+\n+\t: create invalid graft file and verify that it is not deleted &&\n+\ttest_when_finished \"rm -f .git/info/grafts\" &&\n+\techo $EMPTY_BLOB $EMPTY_TREE >.git/info/grafts &&\n+\ttest_must_fail git replace --convert-graft-file 2>err &&\n+\ttest_i18ngrep \"$EMPTY_BLOB $EMPTY_TREE\" err &&\n+\ttest_i18ngrep \"$EMPTY_BLOB $EMPTY_TREE\" .git/info/grafts\n+'\n+\n test_done\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345335","messageId":"1d96f3e3aad44df4d0dd93863d5d1a5157e38999.1524303776.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524303776.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 07/11] Deprecate support for .git/info/grafts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T09:49:38Z","receivedAt":"2018-04-21T09:50:03Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The grafts feature was a convenient way to \"stitch together\" ancient\nhistory to the fresh start of linux.git.\n\nIts implementation is, however, not up to Git's standards, as there are\ntoo many ways where it can lead to surprising and unwelcome behavior.\n\nFor example, when pushing from a repository with active grafts, it is\npossible to miss commits that have been \"grafted out\", resulting in a\nbroken state on the other side.\n\nAlso, the grafts feature is limited to \"rewriting\" commits' list of\nparents, it cannot replace anything else.\n\nThe much younger feature implemented as `git replace` set out to remedy\nthose limitations and dangerous bugs.\n\nSeeing as `git replace` is pretty mature by now (since 4228e8bc98\n(replace: add --graft option, 2014-07-19) it can perform the graft\nfile's duties), it is time to deprecate support for the graft file, and\nto retire it eventually.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nReviewed-by: Stefan Beller <sbeller@google.com>\n---\n advice.c                  |  2 ++\n advice.h                  |  1 +\n commit.c                  | 10 ++++++++++\n t/t6001-rev-list-graft.sh |  9 +++++++++\n 4 files changed, 22 insertions(+)\n\ndiff --git a/advice.c b/advice.c\nindex 406efc183ba..4411704fd45 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -19,6 +19,7 @@ int advice_rm_hints = 1;\n int advice_add_embedded_repo = 1;\n int advice_ignored_hook = 1;\n int advice_waiting_for_editor = 1;\n+int advice_graft_file_deprecated = 1;\n \n static struct {\n \tconst char *name;\n@@ -42,6 +43,7 @@ static struct {\n \t{ \"addembeddedrepo\", &advice_add_embedded_repo },\n \t{ \"ignoredhook\", &advice_ignored_hook },\n \t{ \"waitingforeditor\", &advice_waiting_for_editor },\n+\t{ \"graftfiledeprecated\", &advice_graft_file_deprecated },\n \n \t/* make this an alias for backward compatibility */\n \t{ \"pushnonfastforward\", &advice_push_update_rejected }\ndiff --git a/advice.h b/advice.h\nindex 70568fa7922..9f5064e82a8 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -21,6 +21,7 @@ extern int advice_rm_hints;\n extern int advice_add_embedded_repo;\n extern int advice_ignored_hook;\n extern int advice_waiting_for_editor;\n+extern int advice_graft_file_deprecated;\n \n int git_default_advice_config(const char *var, const char *value);\n __attribute__((format (printf, 1, 2)))\ndiff --git a/commit.c b/commit.c\nindex 2952ec987c5..451d3ce8dfe 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -12,6 +12,7 @@\n #include \"prio-queue.h\"\n #include \"sha1-lookup.h\"\n #include \"wt-status.h\"\n+#include \"advice.h\"\n \n static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);\n \n@@ -176,6 +177,15 @@ static int read_graft_file(const char *graft_file)\n \tstruct strbuf buf = STRBUF_INIT;\n \tif (!fp)\n \t\treturn -1;\n+\tif (advice_graft_file_deprecated)\n+\t\tadvise(_(\"Support for <GIT_DIR>/info/grafts is deprecated\\n\"\n+\t\t\t \"and will be removed in a future Git version.\\n\"\n+\t\t\t \"\\n\"\n+\t\t\t \"Please use \\\"git replace --convert-graft-file\\\"\\n\"\n+\t\t\t \"to convert the grafts into replace refs.\\n\"\n+\t\t\t \"\\n\"\n+\t\t\t \"Turn this message off by running\\n\"\n+\t\t\t \"\\\"git config advice.graftFileDeprecated false\\\"\"));\n \twhile (!strbuf_getwholeline(&buf, fp, '\\n')) {\n \t\t/* The format is just \"Commit Parent1 Parent2 ...\\n\" */\n \t\tstruct commit_graft *graft = read_graft_line(&buf);\ndiff --git a/t/t6001-rev-list-graft.sh b/t/t6001-rev-list-graft.sh\nindex 05ddc69cf2a..7504ba47511 100755\n--- a/t/t6001-rev-list-graft.sh\n+++ b/t/t6001-rev-list-graft.sh\n@@ -110,4 +110,13 @@ do\n \t\"\n \n done\n+\n+test_expect_success 'show advice that grafts are deprecated' '\n+\tgit show HEAD 2>err &&\n+\ttest_i18ngrep \"git replace\" err &&\n+\ttest_config advice.graftFileDeprecated false &&\n+\tgit show HEAD 2>err &&\n+\ttest_i18ngrep ! \"git replace\" err\n+'\n+\n test_done\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345336","messageId":"bfbcf732e3a8cbd8cd6255051b5d0397089d6e58.1524303776.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524303776.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 08/11] filter-branch: stop suggesting to use grafts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T09:49:58Z","receivedAt":"2018-04-21T09:51:20Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The graft file is deprecated now, so let's use replace refs in the example\nin filter-branch's man page instead.\n\nSuggested-by: Eric Sunshine <sunshine@sunshineco.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-filter-branch.txt | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt\nindex b634043183b..1d4d2f86045 100644\n--- a/Documentation/git-filter-branch.txt\n+++ b/Documentation/git-filter-branch.txt\n@@ -288,7 +288,7 @@ git filter-branch --parent-filter \\\n or even simpler:\n \n -----------------------------------------------\n-echo \"$commit-id $graft-id\" >> .git/info/grafts\n+git replace --graft $commit-id $graft-id\n git filter-branch $graft-id..HEAD\n -----------------------------------------------\n \n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345337","messageId":"6527ac4a14623670ef2804ded921ba7c308db218.1524303776.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524303776.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 09/11] technical/shallow: describe the relationship with replace refs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T09:51:58Z","receivedAt":"2018-04-21T09:52:21Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Now that grafts are deprecated, we should start to assume that readers\nhave no idea what grafts are. So it makes more sense to describe the\n\"shallow\" feature in terms of replace refs.\n\nSuggested-by: Eric Sunshine <sunshine@sunshineco.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/technical/shallow.txt | 19 +++++++++++--------\n 1 file changed, 11 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/technical/shallow.txt b/Documentation/technical/shallow.txt\nindex 5183b154229..b3ff23c25f6 100644\n--- a/Documentation/technical/shallow.txt\n+++ b/Documentation/technical/shallow.txt\n@@ -9,14 +9,17 @@ these commits have no parents.\n *********************************************************\n \n The basic idea is to write the SHA-1s of shallow commits into\n-$GIT_DIR/shallow, and handle its contents like the contents\n-of $GIT_DIR/info/grafts (with the difference that shallow\n-cannot contain parent information).\n-\n-This information is stored in a new file instead of grafts, or\n-even the config, since the user should not touch that file\n-at all (even throughout development of the shallow clone, it\n-was never manually edited!).\n+$GIT_DIR/shallow, and handle its contents similar to replace\n+refs (with the difference that shallow does not actually\n+create those replace refs) and very much like the deprecated\n+graft file (with the difference that shallow commits will\n+always have their parents grafted away, not replaced by\n+different parents).\n+\n+This information is stored in a special-purpose file because the\n+user should not touch that file at all (even throughout\n+development of the shallow clone, it was never manually\n+edited!).\n \n Each line contains exactly one SHA-1. When read, a commit_graft\n will be constructed, which has nr_parent < 0 to make it easier\n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345338","messageId":"f8a93d3ff6ab598d4f47811b638a85ffbb3d3ba2.1524303776.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524303776.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 10/11] technical/shallow: describe why shallow cannot use replace refs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T09:56:16Z","receivedAt":"2018-04-21T09:56:48Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"It is tempting to do away with commit_graft altogether (in the long\nhaul), now that grafts are deprecated.\n\nHowever, the shallow feature needs a couple of things that the replace\nrefs cannot fulfill. Let's point that out in the documentation.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/technical/shallow.txt | 7 +++++++\n 1 file changed, 7 insertions(+)\n\ndiff --git a/Documentation/technical/shallow.txt b/Documentation/technical/shallow.txt\nindex b3ff23c25f6..cb79181c2bb 100644\n--- a/Documentation/technical/shallow.txt\n+++ b/Documentation/technical/shallow.txt\n@@ -25,6 +25,13 @@ Each line contains exactly one SHA-1. When read, a commit_graft\n will be constructed, which has nr_parent < 0 to make it easier\n to discern from user provided grafts.\n \n+Note that the shallow feature could not be changed easily to\n+use replace refs: a commit containing a `mergetag` is not allowed\n+to be replaced, not even by a root commit. Such a commit can be\n+made shallow, though. Also, having a `shallow` file explicitly\n+listing all the commits made shallow makes it a *lot* easier to\n+do shallow-specific things such as to deepen the history.\n+\n Since fsck-objects relies on the library to read the objects,\n it honours shallow commits automatically.\n \n-- \n2.17.0.windows.1.15.gaa56ade3205\n\n\n"},{"id":"345339","messageId":"1b4e085ceed9ed5125b2fec2b297a970739c175f.1524303776.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524303776.git.johannes.schindelin@gmx.de","subject":"[PATCH v4 11/11] Remove obsolete script to convert grafts to replace refs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-21T09:56:47Z","receivedAt":"2018-04-21T09:57:09Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The functionality is now implemented as `git replace\n--convert-graft-file`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n contrib/convert-grafts-to-replace-refs.sh | 28 -----------------------\n 1 file changed, 28 deletions(-)\n delete mode 100755 contrib/convert-grafts-to-replace-refs.sh\n\ndiff --git a/contrib/convert-grafts-to-replace-refs.sh b/contrib/convert-grafts-to-replace-refs.sh\ndeleted file mode 100755\nindex 0cbc917b8cf..00000000000\n--- a/contrib/convert-grafts-to-replace-refs.sh\n+++ /dev/null\n@@ -1,28 +0,0 @@\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.17.0.windows.1.15.gaa56ade3205\n"},{"id":"345380","messageId":"CAPig+cTDbTtUeFYmkNtM773EBgE14Tpic4g4XEFuSVwSypdMjw@mail.gmail.com","threadId":"48278","inReplyTo":"fd4ea54ad8da5e4e93fafd558e104b0a19f6268c.1524303776.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v4 05/11] replace: introduce --convert-graft-file","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2018-04-22T03:30:53Z","receivedAt":"2018-04-22T03:30:58Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sat, Apr 21, 2018 at 5:48 AM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> This option is intended to help with the transition away from the\n> now-deprecated graft file.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n> diff --git a/builtin/replace.c b/builtin/replace.c\n> @@ -454,6 +455,38 @@ static int create_graft(int argc, const char **argv, int force)\n> +static int convert_graft_file(int force)\n> +{\n> +       const char *graft_file = get_graft_file();\n> +       FILE *fp = fopen_or_warn(graft_file, \"r\");\n> +       struct strbuf buf = STRBUF_INIT, err = STRBUF_INIT;\n> +       struct argv_array args = ARGV_ARRAY_INIT;\n> +\n> +       if (!fp)\n> +               return -1;\n> +\n> +       while (strbuf_getline(&buf, fp) != EOF) {\n> +               if (*buf.buf == '#')\n> +                       continue;\n> +\n> +               argv_array_split(&args, buf.buf);\n> +               if (args.argc && create_graft(args.argc, args.argv, force))\n> +                       strbuf_addf(&err, \"\\n\\t%s\", buf.buf);\n> +               argv_array_clear(&args);\n> +       }\n> +\n> +       strbuf_release(&buf);\n> +       argv_array_clear(&args);\n\nThis argv_array_clear() is redundant, isn't it?\n\n> +       if (!err.len)\n> +               return unlink_or_warn(graft_file);\n> +\n> +       warning(_(\"could not convert the following graft(s):\\n%s\"), err.buf);\n> +       strbuf_release(&err);\n> +\n> +       return -1;\n> +}\n"},{"id":"345394","messageId":"52C2C5A51FE7400E937F06B7641F4319@PhilipOakley","threadId":"48278","inReplyTo":"853b0a445ab1d78e8cbe19dacd09e70892b34b03.1524262793.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v3 09/11] technical/shallow: describe the relationship with replace refs","fromName":"Philip Oakley","fromEmail":"philipoakley@iee.org","sentAt":"2018-04-22T15:16:05Z","receivedAt":"2018-04-22T15:16:09Z","isPatch":true,"sender":{"key":"philipoakley@iee.email","avatar":"https://avatars.githubusercontent.com/u/914343?v=4"},"body":"From: \"Johannes Schindelin\" <johannes.schindelin@gmx.de>\n> Now that grafts are deprecated, we should start to assume that readers\n> have no idea what grafts are. So it makes more sense to describe the\n> \"shallow\" feature in terms of replace refs.\n>\n> Suggested-by: Eric Sunshine <sunshine@sunshineco.com>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n> Documentation/technical/shallow.txt | 19 +++++++++++--------\n> 1 file changed, 11 insertions(+), 8 deletions(-)\n>\n> diff --git a/Documentation/technical/shallow.txt \n> b/Documentation/technical/shallow.txt\n> index 5183b154229..b3ff23c25f6 100644\n> --- a/Documentation/technical/shallow.txt\n> +++ b/Documentation/technical/shallow.txt\n> @@ -9,14 +9,17 @@ these commits have no parents.\n> *********************************************************\n>\n> The basic idea is to write the SHA-1s of shallow commits into\n> -$GIT_DIR/shallow, and handle its contents like the contents\n> -of $GIT_DIR/info/grafts (with the difference that shallow\n> -cannot contain parent information).\n> -\n> -This information is stored in a new file instead of grafts, or\n> -even the config, since the user should not touch that file\n> -at all (even throughout development of the shallow clone, it\n> -was never manually edited!).\n> +$GIT_DIR/shallow, and handle its contents similar to replace\n> +refs (with the difference that shallow does not actually\n> +create those replace refs) and\n\nIf grafts are deprecated, why not alse get rid of this mention and simply \nleave the 'what it does' part.\n\n>                                               very much like the \n> deprecated\n> +graft file (with\n\n>                           the difference that shallow commits will\n> +always have their parents grafted away, not replaced by\ns/their parents grafted away/no parents/ (rather than being replaced..)\n\n> +different parents).\n> +\n> +This information is stored in a special-purpose file because the\n> +user should not touch that file at all (even throughout\n> +development of the shallow clone, it was never manually\n> +edited!).\n>\n> Each line contains exactly one SHA-1. When read, a commit_graft\n> will be constructed, which has nr_parent < 0 to make it easier\n> -- \n> 2.17.0.windows.1.15.gaa56ade3205\n>\n>\n> \n\n"},{"id":"345497","messageId":"CAGZ79ka=BLGCCTOw848m0SE9O+ZKhQfiW9RUz99W4=Gdg+7ofQ@mail.gmail.com","threadId":"48278","inReplyTo":"f962f8043fb1125b47090a82c35e2d67d3d9d216.1524303776.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v4 04/11] replace: \"libify\" create_graft() and callees","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-04-23T19:08:37Z","receivedAt":"2018-04-23T19:08:42Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"Hi Johannes,\n\nOn Fri, Apr 20, 2018 at 3:21 PM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> @@ -250,27 +257,38 @@ static void import_object(struct object_id *oid, enum object_type type,\n> -               if (strbuf_read(&result, cmd.out, 41) < 0)\n> -                       die_errno(\"unable to read from mktree\");\n> +               if (strbuf_read(&result, cmd.out, 41) < 0) {\n> +                       close(fd);\n> +                       close(cmd.out);\n> +                       return error_errno(\"unable to read from mktree\");\n\nSo before the errno is coming directly from strbuf_read,\nwhich will set errno on error to the desired errno.\n(It will come from an underlying read())\n\nHowever close() may fail and clobber errno,\nso I would think we'd need to\n\n    if (strbuf_read(&result, cmd.out, 41) < 0) {\n      int err =  errno; /* close shall not clobber errno */\n      close(fd);\n      close(cmd.out);\n      errno = err;\n      return error_errno(...);\n    }\n\n> -               if (fstat(fd, &st) < 0)\n> -                       die_errno(\"unable to fstat %s\", filename);\n> +               if (fstat(fd, &st) < 0) {\n> +                       close(fd);\n> +                       return error_errno(\"unable to fstat %s\", filename);\n> +               }\n\nSame here?\n\nAn alternative would be to do\n    ret = error_errno(...)\n    close (..)\n    return ret;\n\n\n> @@ -288,19 +307,23 @@ static int edit_and_replace(const char *object_ref, int force, int raw)\n>         struct strbuf ref = STRBUF_INIT;\n>\n>         if (get_oid(object_ref, &old_oid) < 0)\n> -               die(\"Not a valid object name: '%s'\", object_ref);\n> +               return error(\"Not a valid object name: '%s'\", object_ref);\n>\n>         type = oid_object_info(&old_oid, NULL);\n>         if (type < 0)\n> -               die(\"unable to get object type for %s\", oid_to_hex(&old_oid));\n> +               return error(\"unable to get object type for %s\",\n> +                            oid_to_hex(&old_oid));\n>\n> -       check_ref_valid(&old_oid, &prev, &ref, force);\n> +       if (check_ref_valid(&old_oid, &prev, &ref, force))\n> +               return -1;\n>         strbuf_release(&ref);\n>\n> -       export_object(&old_oid, type, raw, tmpfile);\n> +       if (export_object(&old_oid, type, raw, tmpfile))\n> +               return -1;\n>         if (launch_editor(tmpfile, NULL, NULL) < 0)\n> -               die(\"editing object file failed\");\n> -       import_object(&new_oid, type, raw, tmpfile);\n> +               return error(\"editing object file failed\");\n> +       if (import_object(&new_oid, type, raw, tmpfile))\n> +               return -1;\n>\n>         free(tmpfile);\n\nDo we need to free tmpfile in previous returns?\n\n> @@ -394,24 +422,29 @@ static int create_graft(int argc, const char **argv, int force)\n>         unsigned long size;\n>\n>         if (get_oid(old_ref, &old_oid) < 0)\n> -               die(_(\"Not a valid object name: '%s'\"), old_ref);\n> -       commit = lookup_commit_or_die(&old_oid, old_ref);\n> +               return error(_(\"Not a valid object name: '%s'\"), old_ref);\n> +       commit = lookup_commit_reference(&old_oid);\n> +       if (!commit)\n> +               return error(_(\"could not parse %s\"), old_ref);\n>\n>         buffer = get_commit_buffer(commit, &size);\n>         strbuf_add(&buf, buffer, size);\n>         unuse_commit_buffer(commit, buffer);\n>\n> -       replace_parents(&buf, argc - 1, &argv[1]);\n> +       if (replace_parents(&buf, argc - 1, &argv[1]) < 0)\n> +               return -1;\n>\n>         if (remove_signature(&buf)) {\n>                 warning(_(\"the original commit '%s' has a gpg signature.\"), old_ref);\n>                 warning(_(\"the signature will be removed in the replacement commit!\"));\n>         }\n>\n> -       check_mergetags(commit, argc, argv);\n> +       if (check_mergetags(commit, argc, argv))\n> +               return -1;\n>\n>         if (write_object_file(buf.buf, buf.len, commit_type, &new_oid))\n> -               die(_(\"could not write replacement commit for: '%s'\"), old_ref);\n> +               return error(_(\"could not write replacement commit for: '%s'\"),\n> +                            old_ref);\n>\n>         strbuf_release(&buf);\n\nRelease &buf in the other return cases, too?\n\nThanks,\nStefan\n"},{"id":"345498","messageId":"CAGZ79kaEdb0bd0C7GZXozq2n=CTuB2PZwM6qvgggkdHdNCg=_A@mail.gmail.com","threadId":"48278","inReplyTo":"cover.1524303776.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v4 00/11] Deprecate .git/info/grafts","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-04-23T19:24:52Z","receivedAt":"2018-04-23T19:24:57Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"On Sat, Apr 21, 2018 at 2:43 AM, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> It is fragile, as there is no way for the revision machinery to say \"but\n> now I want to traverse the graph ignoring the graft file\" e.g. when\n> pushing commits to a remote repository (which, as a consequence, can\n> miss commits).\n>\n> And we already have a better solution with `git replace --graft <comit>\n> [<parent>...]`.\n>\n\nApart from some technical questions on patch 4 [1]\nthis series looks good to me,.\n\nThanks,\nStefan\n\n[1] https://public-inbox.org/git/CAGZ79ka=BLGCCTOw848m0SE9O+ZKhQfiW9RUz99W4=Gdg+7ofQ@mail.gmail.com/\n"},{"id":"345591","messageId":"xmqq8t9d8kvm.fsf@gitster-ct.c.googlers.com","threadId":"48278","inReplyTo":"6ec155b834768f38765c63907a48b1bd7ab6bafc.1524303776.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v4 01/11] argv_array: offer to split a string by whitespace","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-04-24T01:15:57Z","receivedAt":"2018-04-24T01:16:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n> +void argv_array_split(struct argv_array *array, const char *to_split)\n> +{\n> +\twhile (isspace(*to_split))\n> +\t\tto_split++;\n> +\tfor (;;) {\n> +\t\tconst char *p = to_split;\n> +\n> +\t\tif (!*p)\n> +\t\t\tbreak;\n> +\n> +\t\twhile (*p && !isspace(*p))\n> +\t\t\tp++;\n> +\t\targv_array_push_nodup(array, xstrndup(to_split, p - to_split));\n\nCan *p be '\\0' at this point?  If isspace('\\0') behaves sensibly,\nthen we wouldn't have had to check \"*p &&\" in the previous while()\nloop.  If not, then while() on the next line needs the same \"*p &&\"\ncheck.  I think ou r isspace('\\0') is reliably false, so we could\ndrop \"*p &&\" but I do not mind spelling it out that we care about\nend of string explicitly.  I however think we would want to be\nconsistent inside a single loop which stance we take.\n\n> +\t\twhile (isspace(*p))\n> +\t\t\tp++;\n> +\t\tto_split = p;\n> +\t}\n> +}\n\nI wonder if the initial \"skip spaces\" can come inside the main loop,\nperhaps like so:\n\n\tfor (;;) {\n\t\tconst char *p = to_split;\n\n\t\twhile (*p && isspace(*p++))\n\t\t\t;\n\t\tif (!*p)\n\t\t\tbreak;\n\t\tfor (to_split = p; *p && !isspace(*p); p++)\n\t\t\t;\n\t\targv_array_push_nodup(array, xstrdup(to_split, p - to_split));\n\t}\n\nor something.\n\nDuplicated \"skip over spaces\" loop is not a big deal, though.\n\n"},{"id":"345599","messageId":"xmqqefj572gt.fsf@gitster-ct.c.googlers.com","threadId":"48278","inReplyTo":"xmqq8t9d8kvm.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v4 01/11] argv_array: offer to split a string by whitespace","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-04-24T02:38:58Z","receivedAt":"2018-04-24T02:39:06Z","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> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n>\n>> +void argv_array_split(struct argv_array *array, const char *to_split)\n>> +{\n>> +\twhile (isspace(*to_split))\n>> +\t\tto_split++;\n>> +\tfor (;;) {\n>> +\t\tconst char *p = to_split;\n>> +\n>> +\t\tif (!*p)\n>> +\t\t\tbreak;\n>> +\n>> +\t\twhile (*p && !isspace(*p))\n>> +\t\t\tp++;\n>> +\t\targv_array_push_nodup(array, xstrndup(to_split, p - to_split));\n>\n> Can *p be '\\0' at this point?\n\nMy thinko.  We do want to stop on NUL even though our isspace() says\nNUL is not a space.  And using while (isspace(*p)) below without\nchecking for NUL also makes sense.\n\nSorry for the noise.\n\n\n\n"},{"id":"345665","messageId":"nycvar.QRO.7.76.6.1804242048360.64@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"48278","inReplyTo":"CAGZ79ka=BLGCCTOw848m0SE9O+ZKhQfiW9RUz99W4=Gdg+7ofQ@mail.gmail.com","subject":"Re: [PATCH v4 04/11] replace: \"libify\" create_graft() and callees","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-24T18:51:21Z","receivedAt":"2018-04-24T18:51:38Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Mon, 23 Apr 2018, Stefan Beller wrote:\n\n> On Fri, Apr 20, 2018 at 3:21 PM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > @@ -250,27 +257,38 @@ static void import_object(struct object_id *oid, enum object_type type,\n> > -               if (strbuf_read(&result, cmd.out, 41) < 0)\n> > -                       die_errno(\"unable to read from mktree\");\n> > +               if (strbuf_read(&result, cmd.out, 41) < 0) {\n> > +                       close(fd);\n> > +                       close(cmd.out);\n> > +                       return error_errno(\"unable to read from mktree\");\n> \n> So before the errno is coming directly from strbuf_read,\n> which will set errno on error to the desired errno.\n> (It will come from an underlying read())\n\nYes, you are right!\n\n> However close() may fail and clobber errno,\n> so I would think we'd need to\n> \n>     if (strbuf_read(&result, cmd.out, 41) < 0) {\n>       int err =  errno; /* close shall not clobber errno */\n>       close(fd);\n>       close(cmd.out);\n>       errno = err;\n>       return error_errno(...);\n>     }\n\nI went for the easier route: call error_errno() before close(fd), and then\nreturn -1 after close(cmd.out). Since error_errno() always returns -1, the\nresult is pretty much the same (I do not think that we want the caller of\nimport_object() to rely on the errno).\n\n> > -               if (fstat(fd, &st) < 0)\n> > -                       die_errno(\"unable to fstat %s\", filename);\n> > +               if (fstat(fd, &st) < 0) {\n> > +                       close(fd);\n> > +                       return error_errno(\"unable to fstat %s\", filename);\n> > +               }\n> \n> Same here?\n\nYep.\n\n> An alternative would be to do\n>     ret = error_errno(...)\n>     close (..)\n>     return ret;\n\nI even saved one variable ;-)\n\n> > @@ -288,19 +307,23 @@ static int edit_and_replace(const char *object_ref, int force, int raw)\n> >         struct strbuf ref = STRBUF_INIT;\n> >\n> >         if (get_oid(object_ref, &old_oid) < 0)\n> > -               die(\"Not a valid object name: '%s'\", object_ref);\n> > +               return error(\"Not a valid object name: '%s'\", object_ref);\n> >\n> >         type = oid_object_info(&old_oid, NULL);\n> >         if (type < 0)\n> > -               die(\"unable to get object type for %s\", oid_to_hex(&old_oid));\n> > +               return error(\"unable to get object type for %s\",\n> > +                            oid_to_hex(&old_oid));\n> >\n> > -       check_ref_valid(&old_oid, &prev, &ref, force);\n> > +       if (check_ref_valid(&old_oid, &prev, &ref, force))\n> > +               return -1;\n> >         strbuf_release(&ref);\n> >\n> > -       export_object(&old_oid, type, raw, tmpfile);\n> > +       if (export_object(&old_oid, type, raw, tmpfile))\n> > +               return -1;\n> >         if (launch_editor(tmpfile, NULL, NULL) < 0)\n> > -               die(\"editing object file failed\");\n> > -       import_object(&new_oid, type, raw, tmpfile);\n> > +               return error(\"editing object file failed\");\n> > +       if (import_object(&new_oid, type, raw, tmpfile))\n> > +               return -1;\n> >\n> >         free(tmpfile);\n> \n> Do we need to free tmpfile in previous returns?\n\nOy vey. How many more mistakes can I introduce in one commit...\n\n> > @@ -394,24 +422,29 @@ static int create_graft(int argc, const char **argv, int force)\n> >         unsigned long size;\n> >\n> >         if (get_oid(old_ref, &old_oid) < 0)\n> > -               die(_(\"Not a valid object name: '%s'\"), old_ref);\n> > -       commit = lookup_commit_or_die(&old_oid, old_ref);\n> > +               return error(_(\"Not a valid object name: '%s'\"), old_ref);\n> > +       commit = lookup_commit_reference(&old_oid);\n> > +       if (!commit)\n> > +               return error(_(\"could not parse %s\"), old_ref);\n> >\n> >         buffer = get_commit_buffer(commit, &size);\n> >         strbuf_add(&buf, buffer, size);\n> >         unuse_commit_buffer(commit, buffer);\n> >\n> > -       replace_parents(&buf, argc - 1, &argv[1]);\n> > +       if (replace_parents(&buf, argc - 1, &argv[1]) < 0)\n> > +               return -1;\n> >\n> >         if (remove_signature(&buf)) {\n> >                 warning(_(\"the original commit '%s' has a gpg signature.\"), old_ref);\n> >                 warning(_(\"the signature will be removed in the replacement commit!\"));\n> >         }\n> >\n> > -       check_mergetags(commit, argc, argv);\n> > +       if (check_mergetags(commit, argc, argv))\n> > +               return -1;\n> >\n> >         if (write_object_file(buf.buf, buf.len, commit_type, &new_oid))\n> > -               die(_(\"could not write replacement commit for: '%s'\"), old_ref);\n> > +               return error(_(\"could not write replacement commit for: '%s'\"),\n> > +                            old_ref);\n> >\n> >         strbuf_release(&buf);\n> \n> Release &buf in the other return cases, too?\n\nAbsolutely.\n\nThank you for helping me improve these patches,\nDscho\n"},{"id":"345669","messageId":"CAGZ79kZgpvcAzpttwHyjnSDcfGYa0gfKRBG9zBqiXfBZetGLtg@mail.gmail.com","threadId":"48278","inReplyTo":"nycvar.QRO.7.76.6.1804242048360.64@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH v4 04/11] replace: \"libify\" create_graft() and callees","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-04-24T19:03:27Z","receivedAt":"2018-04-24T19:03:32Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"Hi Johannes,\n\nOn Tue, Apr 24, 2018 at 11:51 AM, Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n\n>\n> Oy vey. How many more mistakes can I introduce in one commit...\n>\n\nI ask this myself all the time, but Software is hard when not having\ncomputer assisted checks. The test suite doesn't quite count here,\nas it doesn't yell loudly enough for leaks in corner cases.\n\nThanks for taking these seriously, I was unsure if the\nfirst issues (close() clobbering the errno) were sever enough\nto bother. It complicates the code, but the effect is theoretical)\n(for EBADF) or a real niche corner case (EINTR).\n\nSpeaking of that, I wonder if we eventually want to have\na wrapper\n\nint xclose(int fd)\n{\n    int err = errno;\n    int ret = close(fd)\n    if (errno == EINTR)\n        /* on linux we don't care about this, other OSes? */\n        ;\n    errno = err;\n    return ret;\n}\n\nThough not in this series.\n\nThanks,\nStefan\n"},{"id":"345670","messageId":"nycvar.QRO.7.76.6.1804242103590.64@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"48278","inReplyTo":"CAPig+cTDbTtUeFYmkNtM773EBgE14Tpic4g4XEFuSVwSypdMjw@mail.gmail.com","subject":"Re: [PATCH v4 05/11] replace: introduce --convert-graft-file","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-24T19:04:17Z","receivedAt":"2018-04-24T19:04:35Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Eric,\n\nOn Sat, 21 Apr 2018, Eric Sunshine wrote:\n\n> On Sat, Apr 21, 2018 at 5:48 AM, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > This option is intended to help with the transition away from the\n> > now-deprecated graft file.\n> >\n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> > diff --git a/builtin/replace.c b/builtin/replace.c\n> > @@ -454,6 +455,38 @@ static int create_graft(int argc, const char **argv, int force)\n> > +static int convert_graft_file(int force)\n> > +{\n> > +       const char *graft_file = get_graft_file();\n> > +       FILE *fp = fopen_or_warn(graft_file, \"r\");\n> > +       struct strbuf buf = STRBUF_INIT, err = STRBUF_INIT;\n> > +       struct argv_array args = ARGV_ARRAY_INIT;\n> > +\n> > +       if (!fp)\n> > +               return -1;\n> > +\n> > +       while (strbuf_getline(&buf, fp) != EOF) {\n> > +               if (*buf.buf == '#')\n> > +                       continue;\n> > +\n> > +               argv_array_split(&args, buf.buf);\n> > +               if (args.argc && create_graft(args.argc, args.argv, force))\n> > +                       strbuf_addf(&err, \"\\n\\t%s\", buf.buf);\n> > +               argv_array_clear(&args);\n> > +       }\n> > +\n> > +       strbuf_release(&buf);\n> > +       argv_array_clear(&args);\n> \n> This argv_array_clear() is redundant, isn't it?\n\nIt sure is!\n\nThank you for the review,\nDscho\n"},{"id":"345671","messageId":"nycvar.QRO.7.76.6.1804242106220.64@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","threadId":"48278","inReplyTo":"52C2C5A51FE7400E937F06B7641F4319@PhilipOakley","subject":"Re: [PATCH v3 09/11] technical/shallow: describe the relationship with replace refs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-24T19:10:32Z","receivedAt":"2018-04-24T19:10:49Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Philip,\n\nOn Sun, 22 Apr 2018, Philip Oakley wrote:\n\n> From: \"Johannes Schindelin\" <johannes.schindelin@gmx.de>\n> > Now that grafts are deprecated, we should start to assume that readers\n> > have no idea what grafts are. So it makes more sense to describe the\n> > \"shallow\" feature in terms of replace refs.\n> >\n> > Suggested-by: Eric Sunshine <sunshine@sunshineco.com>\n> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> > ---\n> > Documentation/technical/shallow.txt | 19 +++++++++++--------\n> > 1 file changed, 11 insertions(+), 8 deletions(-)\n> >\n> > diff --git a/Documentation/technical/shallow.txt\n> > b/Documentation/technical/shallow.txt\n> > index 5183b154229..b3ff23c25f6 100644\n> > --- a/Documentation/technical/shallow.txt\n> > +++ b/Documentation/technical/shallow.txt\n> > @@ -9,14 +9,17 @@ these commits have no parents.\n> > *********************************************************\n> >\n> > The basic idea is to write the SHA-1s of shallow commits into\n> > -$GIT_DIR/shallow, and handle its contents like the contents\n> > -of $GIT_DIR/info/grafts (with the difference that shallow\n> > -cannot contain parent information).\n> > -\n> > -This information is stored in a new file instead of grafts, or\n> > -even the config, since the user should not touch that file\n> > -at all (even throughout development of the shallow clone, it\n> > -was never manually edited!).\n> > +$GIT_DIR/shallow, and handle its contents similar to replace\n> > +refs (with the difference that shallow does not actually\n> > +create those replace refs) and\n> \n> If grafts are deprecated, why not alse get rid of this mention and simply\n> leave the 'what it does' part.\n\nInternally, shallow commits are implemented using the graft code path, and\nthey always will be: we will always need a list of the shallow commits,\nand we will always need to be able to lift the \"shallow\" attribute\nquickly, when deepening a shallow clone.\n\nSo it makes sense to mention that here, because we are deep in technical\ndetails in Documentation/technical/.\n\n> >                                               very much like the deprecated\n> > +graft file (with\n> \n> >                           the difference that shallow commits will\n> > +always have their parents grafted away, not replaced by\n> s/their parents grafted away/no parents/ (rather than being replaced..)\n\nBut the commits will typically have parents. So they really will have\ntheir parents grafted away as long as they are marked \"shallow\"...\n\nThank you for reviewing!\nDscho\n"},{"id":"345686","messageId":"538CF27C224E4AE0A1C45374AE367C3A@PhilipOakley","threadId":"48278","inReplyTo":"nycvar.QRO.7.76.6.1804242106220.64@ZVAVAG-6OXH6DA.rhebcr.pbec.zvpebfbsg.pbz","subject":"Re: [PATCH v3 09/11] technical/shallow: describe the relationship with replace refs","fromName":"Philip Oakley","fromEmail":"philipoakley@iee.org","sentAt":"2018-04-24T21:34:16Z","receivedAt":"2018-04-24T21:34:30Z","isPatch":true,"sender":{"key":"philipoakley@iee.email","avatar":"https://avatars.githubusercontent.com/u/914343?v=4"},"body":"Hi dscho\n\nFrom: \"Johannes Schindelin\" <Johannes.Schindelin@gmx.de> : Tuesday, April \n24, 2018 8:10 PM\n> On Sun, 22 Apr 2018, Philip Oakley wrote:\n>\n>> From: \"Johannes Schindelin\" <johannes.schindelin@gmx.de>\n>> > Now that grafts are deprecated, we should start to assume that readers\n>> > have no idea what grafts are. So it makes more sense to describe the\n>> > \"shallow\" feature in terms of replace refs.\n\nHere we say we should drop the term \"grafts\"\n>> >\n>> > Suggested-by: Eric Sunshine <sunshine@sunshineco.com>\n>> > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n>> > ---\n>> > Documentation/technical/shallow.txt | 19 +++++++++++--------\n>> > 1 file changed, 11 insertions(+), 8 deletions(-)\n>> >\n>> > diff --git a/Documentation/technical/shallow.txt\n>> > b/Documentation/technical/shallow.txt\n>> > index 5183b154229..b3ff23c25f6 100644\n>> > --- a/Documentation/technical/shallow.txt\n>> > +++ b/Documentation/technical/shallow.txt\n>> > @@ -9,14 +9,17 @@ these commits have no parents.\n>> > *********************************************************\n>> >\n>> > The basic idea is to write the SHA-1s of shallow commits into\n>> > -$GIT_DIR/shallow, and handle its contents like the contents\n>> > -of $GIT_DIR/info/grafts (with the difference that shallow\n>> > -cannot contain parent information).\n>> > -\n>> > -This information is stored in a new file instead of grafts, or\n>> > -even the config, since the user should not touch that file\n>> > -at all (even throughout development of the shallow clone, it\n>> > -was never manually edited!).\n>> > +$GIT_DIR/shallow, and handle its contents similar to replace\n>> > +refs (with the difference that shallow does not actually\n>> > +create those replace refs) and\n>>\n>> If grafts are deprecated, why not alse get rid of this mention and simply\n>> leave the 'what it does' part.\n>\n> Internally, shallow commits are implemented using the graft code path, and\n\nhowever the change here is just to the documentation, independent of th code \npath's name.\n\n> they always will be: we will always need a list of the shallow commits,\n> and we will always need to be able to lift the \"shallow\" attribute\n> quickly, when deepening a shallow clone.\n>\n> So it makes sense to mention that here, because we are deep in technical\n> details in Documentation/technical/.\n>\n>> >                                               very much like the \n>> > deprecated\n>> > +graft file (with\n\nI was looking to snip this 'graft' reference, as per the commit message..\n\n>>\n>> >                           the difference that shallow commits will\n>> > +always have their parents grafted away, not replaced by\n>> s/their parents grafted away/no parents/ (rather than being replaced..)\n\nThen I botched this substitution\n\n>\n> But the commits will typically have parents. So they really will have\n> their parents grafted away as long as they are marked \"shallow\"...\n\nOK, maybe I mis-used the figurative 'no parents', when it means the literal \n'parents not present'.\n\nPerhaps something like:\n+$GIT_DIR/shallow, and handle its contents similar to replace\n+refs (with the difference that shallow does not actually\n+create those replace refs) with the difference that shallow commits will\n+always have their parents not present.\n\n--\nPhilip \n\n"},{"id":"345723","messageId":"xmqq4lk05dau.fsf@gitster-ct.c.googlers.com","threadId":"48278","inReplyTo":"538CF27C224E4AE0A1C45374AE367C3A@PhilipOakley","subject":"Re: [PATCH v3 09/11] technical/shallow: describe the relationship with replace refs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-04-25T00:40:09Z","receivedAt":"2018-04-25T00:40:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Philip Oakley\" <philipoakley@iee.org> writes:\n\n> Perhaps something like:\n> +$GIT_DIR/shallow, and handle its contents similar to replace\n> +refs (with the difference that shallow does not actually\n> +create those replace refs) with the difference that shallow commits will\n> +always have their parents not present.\n\nI am not sure if there is enough similarity to replace mechanism to\nmention that.  It has lines of text, each of which records a commit\nobject for which Git is told to pretend that it has no parent.\n\nTo those who are familiar with \"graft\" format, it is possible to\nexplain the format as \"it is similar to graft\", as a line with a\nsingle commit object name tells Git to pretend that it has no parent\nin the \"graft\" format, but because we are getting rid of graft, it\nprobably makes sense to just explain it without reference to replace\n(which may serve a similar purpose, but is certainly very far from\n\"similar\" as a mechanism when you explain how the contents of shallow\nis handled).\n\n    $GIT_DIR/shallow lists commit object names and tells Git to\n    pretend as if they are root commits (e.g. \"git log\" traversal\n    stops after showing them; \"git fsck\" does not complain saying\n    the commits listed on their \"parent\" lines do not exist).\n\n\n\n"},{"id":"345735","messageId":"nycvar.QRO.7.76.6.1804250905110.4978@tvgsbejvaqbjf.bet","threadId":"48278","inReplyTo":"CAGZ79kZgpvcAzpttwHyjnSDcfGYa0gfKRBG9zBqiXfBZetGLtg@mail.gmail.com","subject":"Re: [PATCH v4 04/11] replace: \"libify\" create_graft() and callees","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T07:10:08Z","receivedAt":"2018-04-25T07:10:20Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Stefan,\n\nOn Tue, 24 Apr 2018, Stefan Beller wrote:\n\n> On Tue, Apr 24, 2018 at 11:51 AM, Johannes Schindelin\n> <Johannes.Schindelin@gmx.de> wrote:\n> \n> >\n> > Oy vey. How many more mistakes can I introduce in one commit...\n> >\n> \n> I ask this myself all the time, but Software is hard when not having\n> computer assisted checks.\n\nRight. I still hope to find some time to play with Infer (Open Source\nalternative to Coverity, started at Facebook). Windows support is not\nthere yet, which is the main hold-up for me (if I had the time, I would\nspend it on adding Windows support to Infer). I could imagine that Infer\nmight be flexible enough to ask these questions programmatically:\n\n- is there a code path that forgets to close() file handles?\n\n- is there a code path that forgets to release strbufs?\n\n- are there redundant strbuf_release() calls?\n\n> The test suite doesn't quite count here, as it doesn't yell loudly\n> enough for leaks in corner cases.\n\nRight, and running everything through valgrind is not fast enough.\nBesides, this misses non-Linux code paths.\n\n> Thanks for taking these seriously, I was unsure if the\n> first issues (close() clobbering the errno) were sever enough\n> to bother. It complicates the code, but the effect is theoretical)\n> (for EBADF) or a real niche corner case (EINTR).\n\nMaybe it might not be *that* serious currently. It is incorrect, though,\nand makes the code less reusable. I like to copy-edit my code a lot when\nrefactoring is not an option.\n\n> Speaking of that, I wonder if we eventually want to have\n> a wrapper\n> \n> int xclose(int fd)\n> {\n>     int err = errno;\n>     int ret = close(fd)\n>     if (errno == EINTR)\n>         /* on linux we don't care about this, other OSes? */\n>         ;\n>     errno = err;\n>     return ret;\n> }\n> \n> Though not in this series.\n\nOr maybe a Coccinelle rule? (Still not in this series, though.)\n\nCiao,\nDscho\n"},{"id":"345738","messageId":"nycvar.QRO.7.76.6.1804250917090.4978@tvgsbejvaqbjf.bet","threadId":"48278","inReplyTo":"xmqq4lk05dau.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 09/11] technical/shallow: describe the relationship with replace refs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T07:17:33Z","receivedAt":"2018-04-25T07:17:45Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Philip & Junio,\n\nOn Wed, 25 Apr 2018, Junio C Hamano wrote:\n\n> \"Philip Oakley\" <philipoakley@iee.org> writes:\n> \n> > Perhaps something like:\n> > +$GIT_DIR/shallow, and handle its contents similar to replace\n> > +refs (with the difference that shallow does not actually\n> > +create those replace refs) with the difference that shallow commits will\n> > +always have their parents not present.\n> \n> I am not sure if there is enough similarity to replace mechanism to\n> mention that.  It has lines of text, each of which records a commit\n> object for which Git is told to pretend that it has no parent.\n> \n> To those who are familiar with \"graft\" format, it is possible to\n> explain the format as \"it is similar to graft\", as a line with a\n> single commit object name tells Git to pretend that it has no parent\n> in the \"graft\" format, but because we are getting rid of graft, it\n> probably makes sense to just explain it without reference to replace\n> (which may serve a similar purpose, but is certainly very far from\n> \"similar\" as a mechanism when you explain how the contents of shallow\n> is handled).\n> \n>     $GIT_DIR/shallow lists commit object names and tells Git to\n>     pretend as if they are root commits (e.g. \"git log\" traversal\n>     stops after showing them; \"git fsck\" does not complain saying\n>     the commits listed on their \"parent\" lines do not exist).\n\nGood point. Thank you for that suggestion, I'll take it as-is.\n\nCiao,\nDscho\n"},{"id":"345747","messageId":"cover.1524650028.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524303776.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 00/11] Deprecate .git/info/grafts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T09:53:49Z","receivedAt":"2018-04-25T09:54:16Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"It is fragile, as there is no way for the revision machinery to say \"but\nnow I want to traverse the graph ignoring the graft file\" e.g. when\npushing commits to a remote repository (which, as a consequence, can\nmiss commits).\n\nAnd we already have a better solution with `git replace --graft <comit>\n[<parent>...]`.\n\nChanges since v4:\n\n- Fixed resource leaks (missing free() and strbuf_release() calls when\n  returning an error).\n\n- Avoided error_errno() mistakenly using close()'s errno.\n\n- Actually close the handle to the graft file after converting it.\n\n- Changed the description of Documentation/technical/shallow to not even\n  bother to describe how it might be construed to be similar to replace\n  refs.\n\n\nJohannes Schindelin (11):\n  argv_array: offer to split a string by whitespace\n  commit: Let the callback of for_each_mergetag return on error\n  replace: avoid using die() to indicate a bug\n  replace: \"libify\" create_graft() and callees\n  replace: introduce --convert-graft-file\n  Add a test for `git replace --convert-graft-file`\n  Deprecate support for .git/info/grafts\n  filter-branch: stop suggesting to use grafts\n  technical/shallow: stop referring to grafts\n  technical/shallow: describe why shallow cannot use replace refs\n  Remove obsolete script to convert grafts to replace refs\n\n Documentation/git-filter-branch.txt       |   2 +-\n Documentation/git-replace.txt             |  11 +-\n Documentation/technical/shallow.txt       |  20 +-\n advice.c                                  |   2 +\n advice.h                                  |   1 +\n argv-array.c                              |  20 ++\n argv-array.h                              |   2 +\n builtin/replace.c                         | 218 +++++++++++++++-------\n commit.c                                  |  18 +-\n commit.h                                  |   4 +-\n contrib/convert-grafts-to-replace-refs.sh |  28 ---\n log-tree.c                                |  13 +-\n t/t6001-rev-list-graft.sh                 |   9 +\n t/t6050-replace.sh                        |  20 ++\n 14 files changed, 253 insertions(+), 115 deletions(-)\n delete mode 100755 contrib/convert-grafts-to-replace-refs.sh\n\n\nbase-commit: 1f1cddd558b54bb0ce19c8ace353fd07b758510d\nPublished-As: https://github.com/dscho/git/releases/tag/deprecate-grafts-v5\nFetch-It-Via: git fetch https://github.com/dscho/git deprecate-grafts-v5\n\nInterdiff vs v4:\n diff --git a/Documentation/technical/shallow.txt b/Documentation/technical/shallow.txt\n index cb79181c2bb..01dedfe9ffe 100644\n --- a/Documentation/technical/shallow.txt\n +++ b/Documentation/technical/shallow.txt\n @@ -8,18 +8,10 @@ repo, and therefore grafts are introduced pretending that\n  these commits have no parents.\n  *********************************************************\n  \n -The basic idea is to write the SHA-1s of shallow commits into\n -$GIT_DIR/shallow, and handle its contents similar to replace\n -refs (with the difference that shallow does not actually\n -create those replace refs) and very much like the deprecated\n -graft file (with the difference that shallow commits will\n -always have their parents grafted away, not replaced by\n -different parents).\n -\n -This information is stored in a special-purpose file because the\n -user should not touch that file at all (even throughout\n -development of the shallow clone, it was never manually\n -edited!).\n +$GIT_DIR/shallow lists commit object names and tells Git to\n +pretend as if they are root commits (e.g. \"git log\" traversal\n +stops after showing them; \"git fsck\" does not complain saying\n +the commits listed on their \"parent\" lines do not exist).\n  \n  Each line contains exactly one SHA-1. When read, a commit_graft\n  will be constructed, which has nr_parent < 0 to make it easier\n diff --git a/builtin/replace.c b/builtin/replace.c\n index 6b3e0301e90..acd30e3d824 100644\n --- a/builtin/replace.c\n +++ b/builtin/replace.c\n @@ -175,8 +175,10 @@ static int replace_object_oid(const char *object_ref,\n  \t\t\t     object_ref, type_name(obj_type),\n  \t\t\t     replace_ref, type_name(repl_type));\n  \n -\tif (check_ref_valid(object, &prev, &ref, force))\n +\tif (check_ref_valid(object, &prev, &ref, force)) {\n +\t\tstrbuf_release(&ref);\n  \t\treturn -1;\n +\t}\n  \n  \ttransaction = ref_transaction_begin(&err);\n  \tif (!transaction ||\n @@ -264,9 +266,10 @@ static int import_object(struct object_id *oid, enum object_type type,\n  \t\t}\n  \n  \t\tif (strbuf_read(&result, cmd.out, 41) < 0) {\n +\t\t\terror_errno(\"unable to read from mktree\");\n  \t\t\tclose(fd);\n  \t\t\tclose(cmd.out);\n -\t\t\treturn error_errno(\"unable to read from mktree\");\n +\t\t\treturn -1;\n  \t\t}\n  \t\tclose(cmd.out);\n  \n @@ -285,8 +288,9 @@ static int import_object(struct object_id *oid, enum object_type type,\n  \t\tint flags = HASH_FORMAT_CHECK | HASH_WRITE_OBJECT;\n  \n  \t\tif (fstat(fd, &st) < 0) {\n +\t\t\terror_errno(\"unable to fstat %s\", filename);\n  \t\t\tclose(fd);\n -\t\t\treturn error_errno(\"unable to fstat %s\", filename);\n +\t\t\treturn -1;\n  \t\t}\n  \t\tif (index_fd(oid, fd, &st, type, NULL, flags) < 0)\n  \t\t\treturn error(\"unable to write object to database\");\n @@ -302,7 +306,7 @@ static int import_object(struct object_id *oid, enum object_type type,\n  \n  static int edit_and_replace(const char *object_ref, int force, int raw)\n  {\n -\tchar *tmpfile = git_pathdup(\"REPLACE_EDITOBJ\");\n +\tchar *tmpfile;\n  \tenum object_type type;\n  \tstruct object_id old_oid, new_oid, prev;\n  \tstruct strbuf ref = STRBUF_INIT;\n @@ -315,17 +319,20 @@ static int edit_and_replace(const char *object_ref, int force, int raw)\n  \t\treturn error(\"unable to get object type for %s\",\n  \t\t\t     oid_to_hex(&old_oid));\n  \n -\tif (check_ref_valid(&old_oid, &prev, &ref, force))\n +\tif (check_ref_valid(&old_oid, &prev, &ref, force)) {\n +\t\tstrbuf_release(&ref);\n  \t\treturn -1;\n +\t}\n  \tstrbuf_release(&ref);\n  \n -\tif (export_object(&old_oid, type, raw, tmpfile))\n -\t\treturn -1;\n -\tif (launch_editor(tmpfile, NULL, NULL) < 0)\n -\t\treturn error(\"editing object file failed\");\n -\tif (import_object(&new_oid, type, raw, tmpfile))\n +\ttmpfile = git_pathdup(\"REPLACE_EDITOBJ\");\n +\tif (export_object(&old_oid, type, raw, tmpfile) ||\n +\t    (launch_editor(tmpfile, NULL, NULL) < 0 &&\n +\t     error(\"editing object file failed\")) ||\n +\t    import_object(&new_oid, type, raw, tmpfile)) {\n +\t\tfree(tmpfile);\n  \t\treturn -1;\n -\n +\t}\n  \tfree(tmpfile);\n  \n  \tif (!oidcmp(&old_oid, &new_oid))\n @@ -351,11 +358,15 @@ static int replace_parents(struct strbuf *buf, int argc, const char **argv)\n  \t/* prepare new parents */\n  \tfor (i = 0; i < argc; i++) {\n  \t\tstruct object_id oid;\n -\t\tif (get_oid(argv[i], &oid) < 0)\n +\t\tif (get_oid(argv[i], &oid) < 0) {\n +\t\t\tstrbuf_release(&new_parents);\n  \t\t\treturn error(_(\"Not a valid object name: '%s'\"),\n  \t\t\t\t     argv[i]);\n -\t\tif (!lookup_commit_reference(&oid))\n +\t\t}\n +\t\tif (!lookup_commit_reference(&oid)) {\n +\t\t\tstrbuf_release(&new_parents);\n  \t\t\treturn error(_(\"could not parse %s\"), argv[i]);\n +\t\t}\n  \t\tstrbuf_addf(&new_parents, \"parent %s\\n\", oid_to_hex(&oid));\n  \t}\n  \n @@ -432,20 +443,26 @@ static int create_graft(int argc, const char **argv, int force)\n  \tstrbuf_add(&buf, buffer, size);\n  \tunuse_commit_buffer(commit, buffer);\n  \n -\tif (replace_parents(&buf, argc - 1, &argv[1]) < 0)\n +\tif (replace_parents(&buf, argc - 1, &argv[1]) < 0) {\n +\t\tstrbuf_release(&buf);\n  \t\treturn -1;\n +\t}\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 (check_mergetags(commit, argc, argv))\n +\tif (check_mergetags(commit, argc, argv)) {\n +\t\tstrbuf_release(&buf);\n  \t\treturn -1;\n +\t}\n  \n -\tif (write_object_file(buf.buf, buf.len, commit_type, &new_oid))\n +\tif (write_object_file(buf.buf, buf.len, commit_type, &new_oid)) {\n +\t\tstrbuf_release(&buf);\n  \t\treturn error(_(\"could not write replacement commit for: '%s'\"),\n  \t\t\t     old_ref);\n +\t}\n  \n  \tstrbuf_release(&buf);\n  \n @@ -474,9 +491,9 @@ static int convert_graft_file(int force)\n  \t\t\tstrbuf_addf(&err, \"\\n\\t%s\", buf.buf);\n  \t\targv_array_clear(&args);\n  \t}\n +\tfclose(fp);\n  \n  \tstrbuf_release(&buf);\n -\targv_array_clear(&args);\n  \n  \tif (!err.len)\n  \t\treturn unlink_or_warn(graft_file);\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n"},{"id":"345748","messageId":"178058d887281fc87e5008367bbee5677a619320.1524650028.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524650028.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 01/11] argv_array: offer to split a string by whitespace","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T09:53:57Z","receivedAt":"2018-04-25T09:54:16Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This is a simple function that will interpret a string as a whitespace\ndelimited list of values, and add those values into the array.\n\nNote: this function does not (yet) offer to split by arbitrary delimiters,\nor keep empty values in case of runs of whitespace, or de-quote Unix shell\nstyle. All fo this functionality can be added later, when and if needed.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n argv-array.c | 20 ++++++++++++++++++++\n argv-array.h |  2 ++\n 2 files changed, 22 insertions(+)\n\ndiff --git a/argv-array.c b/argv-array.c\nindex 5d370fa3366..cb5bcd2c064 100644\n--- a/argv-array.c\n+++ b/argv-array.c\n@@ -64,6 +64,26 @@ void argv_array_pop(struct argv_array *array)\n \tarray->argc--;\n }\n \n+void argv_array_split(struct argv_array *array, const char *to_split)\n+{\n+\twhile (isspace(*to_split))\n+\t\tto_split++;\n+\tfor (;;) {\n+\t\tconst char *p = to_split;\n+\n+\t\tif (!*p)\n+\t\t\tbreak;\n+\n+\t\twhile (*p && !isspace(*p))\n+\t\t\tp++;\n+\t\targv_array_push_nodup(array, xstrndup(to_split, p - to_split));\n+\n+\t\twhile (isspace(*p))\n+\t\t\tp++;\n+\t\tto_split = p;\n+\t}\n+}\n+\n void argv_array_clear(struct argv_array *array)\n {\n \tif (array->argv != empty_argv) {\ndiff --git a/argv-array.h b/argv-array.h\nindex 29056e49a12..750c30d2f2c 100644\n--- a/argv-array.h\n+++ b/argv-array.h\n@@ -19,6 +19,8 @@ LAST_ARG_MUST_BE_NULL\n void argv_array_pushl(struct argv_array *, ...);\n void argv_array_pushv(struct argv_array *, const char **);\n void argv_array_pop(struct argv_array *);\n+/* Splits by whitespace; does not handle quoted arguments! */\n+void argv_array_split(struct argv_array *, const char *);\n void argv_array_clear(struct argv_array *);\n const char **argv_array_detach(struct argv_array *);\n \n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345749","messageId":"05642a8fb58086c9cc638fec1b4e61ffa8cc7506.1524650029.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524650028.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 03/11] replace: avoid using die() to indicate a bug","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T09:54:06Z","receivedAt":"2018-04-25T09:54:22Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"We have the BUG() macro for that purpose.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/replace.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 245d3f4164e..e345a5a0f1c 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -501,6 +501,6 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n \t\treturn list_replace_refs(argv[0], format);\n \n \tdefault:\n-\t\tdie(\"BUG: invalid cmdmode %d\", (int)cmdmode);\n+\t\tBUG(\"invalid cmdmode %d\", (int)cmdmode);\n \t}\n }\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345750","messageId":"f9ee6d8248d9a3fb0f48ae4e3fd82b2655380168.1524650029.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524650028.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 05/11] replace: introduce --convert-graft-file","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T09:54:15Z","receivedAt":"2018-04-25T09:54:26Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This option is intended to help with the transition away from the\nnow-deprecated graft file.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-replace.txt | 11 ++++++---\n builtin/replace.c             | 45 ++++++++++++++++++++++++++++++++++-\n 2 files changed, 52 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-replace.txt b/Documentation/git-replace.txt\nindex e5c57ae6ef4..246dc9943c2 100644\n--- a/Documentation/git-replace.txt\n+++ b/Documentation/git-replace.txt\n@@ -11,6 +11,7 @@ SYNOPSIS\n 'git replace' [-f] <object> <replacement>\n 'git replace' [-f] --edit <object>\n 'git replace' [-f] --graft <commit> [<parent>...]\n+'git replace' [-f] --convert-graft-file\n 'git replace' -d <object>...\n 'git replace' [--format=<format>] [-l [<pattern>]]\n \n@@ -87,9 +88,13 @@ 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. 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+\tcommit. Use `--convert-graft-file` to convert a\n+\t`$GIT_DIR/info/grafts` file and use replace refs instead.\n+\n+--convert-graft-file::\n+\tCreates graft commits for all entries in `$GIT_DIR/info/grafts`\n+\tand deletes that file upon success. The purpose is to help users\n+\twith transitioning off of the now-deprecated graft file.\n \n -l <pattern>::\n --list <pattern>::\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex e92f364e29a..fdd12c5ffc0 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -20,6 +20,7 @@ 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 [-f] --convert-graft-file\"),\n \tN_(\"git replace -d <object>...\"),\n \tN_(\"git replace [--format=<format>] [-l [<pattern>]]\"),\n \tNULL\n@@ -471,6 +472,39 @@ static int create_graft(int argc, const char **argv, int force)\n \treturn replace_object_oid(old_ref, &old_oid, \"replacement\", &new_oid, force);\n }\n \n+static int convert_graft_file(int force)\n+{\n+\tconst char *graft_file = get_graft_file();\n+\tFILE *fp = fopen_or_warn(graft_file, \"r\");\n+\tstruct strbuf buf = STRBUF_INIT, err = STRBUF_INIT;\n+\tstruct argv_array args = ARGV_ARRAY_INIT;\n+\n+\tif (!fp)\n+\t\treturn -1;\n+\n+\twhile (strbuf_getline(&buf, fp) != EOF) {\n+\t\tif (*buf.buf == '#')\n+\t\t\tcontinue;\n+\n+\t\targv_array_split(&args, buf.buf);\n+\t\tif (args.argc && create_graft(args.argc, args.argv, force))\n+\t\t\tstrbuf_addf(&err, \"\\n\\t%s\", buf.buf);\n+\t\targv_array_clear(&args);\n+\t}\n+\tfclose(fp);\n+\n+\tstrbuf_release(&buf);\n+\targv_array_clear(&args);\n+\n+\tif (!err.len)\n+\t\treturn unlink_or_warn(graft_file);\n+\n+\twarning(_(\"could not convert the following graft(s):\\n%s\"), err.buf);\n+\tstrbuf_release(&err);\n+\n+\treturn -1;\n+}\n+\n int cmd_replace(int argc, const char **argv, const char *prefix)\n {\n \tint force = 0;\n@@ -482,6 +516,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n \t\tMODE_DELETE,\n \t\tMODE_EDIT,\n \t\tMODE_GRAFT,\n+\t\tMODE_CONVERT_GRAFT_FILE,\n \t\tMODE_REPLACE\n \t} cmdmode = MODE_UNSPECIFIED;\n \tstruct option options[] = {\n@@ -489,6 +524,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\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_CMDMODE(0, \"convert-graft-file\", &cmdmode, N_(\"convert existing graft file\"), MODE_CONVERT_GRAFT_FILE),\n \t\tOPT_BOOL_F('f', \"force\", &force, N_(\"replace the ref if it exists\"),\n \t\t\t   PARSE_OPT_NOCOMPLETE),\n \t\tOPT_BOOL(0, \"raw\", &raw, N_(\"do not pretty-print contents for --edit\")),\n@@ -511,7 +547,8 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n \tif (force &&\n \t    cmdmode != MODE_REPLACE &&\n \t    cmdmode != MODE_EDIT &&\n-\t    cmdmode != MODE_GRAFT)\n+\t    cmdmode != MODE_GRAFT &&\n+\t    cmdmode != MODE_CONVERT_GRAFT_FILE)\n \t\tusage_msg_opt(\"-f only makes sense when writing a replacement\",\n \t\t\t      git_replace_usage, options);\n \n@@ -544,6 +581,12 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n \t\t\t\t      git_replace_usage, options);\n \t\treturn create_graft(argc, argv, force);\n \n+\tcase MODE_CONVERT_GRAFT_FILE:\n+\t\tif (argc != 0)\n+\t\t\tusage_msg_opt(\"--convert-graft-file takes no argument\",\n+\t\t\t\t      git_replace_usage, options);\n+\t\treturn !!convert_graft_file(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.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345751","messageId":"97428f56890413d16f1487309972c446b61f784f.1524650028.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524650028.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 02/11] commit: Let the callback of for_each_mergetag return on error","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T09:54:04Z","receivedAt":"2018-04-25T09:54:28Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This is yet another patch to be filed under the keyword \"libification\".\n\nThere is one subtle change in behavior here, where a `git log` that has\nbeen asked to show the mergetags would now stop reporting the mergetags\nupon the first failure, whereas previously, it would have continued to the\nnext mergetag, if any.\n\nIn practice, that change should not matter, as it is 1) uncommon to\nperform octopus merges using multiple tags as merge heads, and 2) when the\nuser asks to be shown those tags, they really should be there.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/replace.c |  8 ++++----\n commit.c          |  8 +++++---\n commit.h          |  4 ++--\n log-tree.c        | 13 +++++++------\n 4 files changed, 18 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 935647be6bd..245d3f4164e 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -345,7 +345,7 @@ struct check_mergetag_data {\n \tconst char **argv;\n };\n \n-static void check_one_mergetag(struct commit *commit,\n+static int check_one_mergetag(struct commit *commit,\n \t\t\t       struct commit_extra_header *extra,\n \t\t\t       void *data)\n {\n@@ -368,20 +368,20 @@ static void check_one_mergetag(struct commit *commit,\n \t\tif (get_oid(mergetag_data->argv[i], &oid) < 0)\n \t\t\tdie(_(\"Not a valid object name: '%s'\"), mergetag_data->argv[i]);\n \t\tif (!oidcmp(&tag->tagged->oid, &oid))\n-\t\t\treturn; /* found */\n+\t\t\treturn 0; /* found */\n \t}\n \n \tdie(_(\"original commit '%s' contains mergetag '%s' that is discarded; \"\n \t      \"use --edit instead of --graft\"), ref, oid_to_hex(&tag_oid));\n }\n \n-static void check_mergetags(struct commit *commit, int argc, const char **argv)\n+static int 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+\treturn for_each_mergetag(check_one_mergetag, commit, &mergetag_data);\n }\n \n static int create_graft(int argc, const char **argv, int force)\ndiff --git a/commit.c b/commit.c\nindex ca474a7c112..2952ec987c5 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -1288,17 +1288,19 @@ struct commit_extra_header *read_commit_extra_headers(struct commit *commit,\n \treturn extra;\n }\n \n-void for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data)\n+int for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data)\n {\n \tstruct commit_extra_header *extra, *to_free;\n+\tint res = 0;\n \n \tto_free = read_commit_extra_headers(commit, NULL);\n-\tfor (extra = to_free; extra; extra = extra->next) {\n+\tfor (extra = to_free; !res && extra; extra = extra->next) {\n \t\tif (strcmp(extra->key, \"mergetag\"))\n \t\t\tcontinue; /* not a merge tag */\n-\t\tfn(commit, extra, data);\n+\t\tres = fn(commit, extra, data);\n \t}\n \tfree_commit_extra_headers(to_free);\n+\treturn res;\n }\n \n static inline int standard_header_field(const char *field, size_t len)\ndiff --git a/commit.h b/commit.h\nindex 0fb8271665c..9000895ad91 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -291,10 +291,10 @@ extern const char *find_commit_header(const char *msg, const char *key,\n /* Find the end of the log message, the right place for a new trailer. */\n extern int ignore_non_trailer(const char *buf, size_t len);\n \n-typedef void (*each_mergetag_fn)(struct commit *commit, struct commit_extra_header *extra,\n+typedef int (*each_mergetag_fn)(struct commit *commit, struct commit_extra_header *extra,\n \t\t\t\t void *cb_data);\n \n-extern void for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data);\n+extern int for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data);\n \n struct merge_remote_desc {\n \tstruct object *obj; /* the named object, could be a tag */\ndiff --git a/log-tree.c b/log-tree.c\nindex d1c0bedf244..f3a51a6e726 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -488,9 +488,9 @@ static int is_common_merge(const struct commit *commit)\n \t\t&& !commit->parents->next->next);\n }\n \n-static void show_one_mergetag(struct commit *commit,\n-\t\t\t      struct commit_extra_header *extra,\n-\t\t\t      void *data)\n+static int show_one_mergetag(struct commit *commit,\n+\t\t\t     struct commit_extra_header *extra,\n+\t\t\t     void *data)\n {\n \tstruct rev_info *opt = (struct rev_info *)data;\n \tstruct object_id oid;\n@@ -502,7 +502,7 @@ static void show_one_mergetag(struct commit *commit,\n \thash_object_file(extra->value, extra->len, type_name(OBJ_TAG), &oid);\n \ttag = lookup_tag(&oid);\n \tif (!tag)\n-\t\treturn; /* error message already given */\n+\t\treturn -1; /* error message already given */\n \n \tstrbuf_init(&verify_message, 256);\n \tif (parse_tag_buffer(tag, extra->value, extra->len))\n@@ -536,11 +536,12 @@ static void show_one_mergetag(struct commit *commit,\n \n \tshow_sig_lines(opt, status, verify_message.buf);\n \tstrbuf_release(&verify_message);\n+\treturn 0;\n }\n \n-static void show_mergetag(struct rev_info *opt, struct commit *commit)\n+static int show_mergetag(struct rev_info *opt, struct commit *commit)\n {\n-\tfor_each_mergetag(show_one_mergetag, commit, opt);\n+\treturn for_each_mergetag(show_one_mergetag, commit, opt);\n }\n \n void show_log(struct rev_info *opt)\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345752","messageId":"bd3a15bb41680b99bc888d3a775cd40508ec89f7.1524650029.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524650028.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 04/11] replace: \"libify\" create_graft() and callees","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T09:54:08Z","receivedAt":"2018-04-25T09:54:32Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"File this away as yet another patch in the \"libification\" category.\n\nAs with all useful functions, in the next commit we want to use\ncreate_graft() from a higher-level function where it would be\ninconvenient if the called function simply die()s: if there is a\nproblem, we want to let the user know how to proceed, and the callee\nsimply has no way of knowing what to say.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/replace.c | 164 ++++++++++++++++++++++++++++++----------------\n 1 file changed, 107 insertions(+), 57 deletions(-)\n\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex e345a5a0f1c..e92f364e29a 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -79,9 +79,9 @@ static int list_replace_refs(const char *pattern, const char *format)\n \telse if (!strcmp(format, \"long\"))\n \t\tdata.format = REPLACE_FORMAT_LONG;\n \telse\n-\t\tdie(\"invalid replace format '%s'\\n\"\n-\t\t    \"valid formats are 'short', 'medium' and 'long'\\n\",\n-\t\t    format);\n+\t\treturn error(\"invalid replace format '%s'\\n\"\n+\t\t\t     \"valid formats are 'short', 'medium' and 'long'\\n\",\n+\t\t\t     format);\n \n \tfor_each_replace_ref(show_reference, (void *)&data);\n \n@@ -134,7 +134,7 @@ static int delete_replace_ref(const char *name, const char *ref,\n \treturn 0;\n }\n \n-static void check_ref_valid(struct object_id *object,\n+static int check_ref_valid(struct object_id *object,\n \t\t\t    struct object_id *prev,\n \t\t\t    struct strbuf *ref,\n \t\t\t    int force)\n@@ -142,12 +142,13 @@ static void check_ref_valid(struct object_id *object,\n \tstrbuf_reset(ref);\n \tstrbuf_addf(ref, \"%s%s\", git_replace_ref_base, oid_to_hex(object));\n \tif (check_refname_format(ref->buf, 0))\n-\t\tdie(\"'%s' is not a valid ref name.\", ref->buf);\n+\t\treturn error(\"'%s' is not a valid ref name.\", ref->buf);\n \n \tif (read_ref(ref->buf, prev))\n \t\toidclr(prev);\n \telse if (!force)\n-\t\tdie(\"replace ref '%s' already exists\", ref->buf);\n+\t\treturn error(\"replace ref '%s' already exists\", ref->buf);\n+\treturn 0;\n }\n \n static int replace_object_oid(const char *object_ref,\n@@ -161,28 +162,33 @@ static int replace_object_oid(const char *object_ref,\n \tstruct strbuf ref = STRBUF_INIT;\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n+\tint res = 0;\n \n \tobj_type = oid_object_info(object, NULL);\n \trepl_type = oid_object_info(repl, NULL);\n \tif (!force && obj_type != repl_type)\n-\t\tdie(\"Objects must be of the same type.\\n\"\n-\t\t    \"'%s' points to a replaced object of type '%s'\\n\"\n-\t\t    \"while '%s' points to a replacement object of type '%s'.\",\n-\t\t    object_ref, type_name(obj_type),\n-\t\t    replace_ref, type_name(repl_type));\n-\n-\tcheck_ref_valid(object, &prev, &ref, force);\n+\t\treturn error(\"Objects must be of the same type.\\n\"\n+\t\t\t     \"'%s' points to a replaced object of type '%s'\\n\"\n+\t\t\t     \"while '%s' points to a replacement object of \"\n+\t\t\t     \"type '%s'.\",\n+\t\t\t     object_ref, type_name(obj_type),\n+\t\t\t     replace_ref, type_name(repl_type));\n+\n+\tif (check_ref_valid(object, &prev, &ref, force)) {\n+\t\tstrbuf_release(&ref);\n+\t\treturn -1;\n+\t}\n \n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, ref.buf, repl, &prev,\n \t\t\t\t   0, NULL, &err) ||\n \t    ref_transaction_commit(transaction, &err))\n-\t\tdie(\"%s\", err.buf);\n+\t\tres = error(\"%s\", err.buf);\n \n \tref_transaction_free(transaction);\n \tstrbuf_release(&ref);\n-\treturn 0;\n+\treturn res;\n }\n \n static int replace_object(const char *object_ref, const char *replace_ref, int force)\n@@ -190,9 +196,11 @@ static int replace_object(const char *object_ref, const char *replace_ref, int f\n \tstruct object_id object, repl;\n \n \tif (get_oid(object_ref, &object))\n-\t\tdie(\"Failed to resolve '%s' as a valid ref.\", object_ref);\n+\t\treturn error(\"Failed to resolve '%s' as a valid ref.\",\n+\t\t\t     object_ref);\n \tif (get_oid(replace_ref, &repl))\n-\t\tdie(\"Failed to resolve '%s' as a valid ref.\", replace_ref);\n+\t\treturn error(\"Failed to resolve '%s' as a valid ref.\",\n+\t\t\t     replace_ref);\n \n \treturn replace_object_oid(object_ref, &object, replace_ref, &repl, force);\n }\n@@ -202,7 +210,7 @@ static int replace_object(const char *object_ref, const char *replace_ref, int f\n  * If \"raw\" is true, then the object's raw contents are printed according to\n  * \"type\". Otherwise, we pretty-print the contents for human editing.\n  */\n-static void export_object(const struct object_id *oid, enum object_type type,\n+static int export_object(const struct object_id *oid, enum object_type type,\n \t\t\t  int raw, const char *filename)\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n@@ -210,7 +218,7 @@ static void export_object(const struct object_id *oid, enum object_type type,\n \n \tfd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);\n \tif (fd < 0)\n-\t\tdie_errno(\"unable to open %s for writing\", filename);\n+\t\treturn error_errno(\"unable to open %s for writing\", filename);\n \n \targv_array_push(&cmd.args, \"--no-replace-objects\");\n \targv_array_push(&cmd.args, \"cat-file\");\n@@ -223,7 +231,8 @@ static void export_object(const struct object_id *oid, enum object_type type,\n \tcmd.out = fd;\n \n \tif (run_command(&cmd))\n-\t\tdie(\"cat-file reported failure\");\n+\t\treturn error(\"cat-file reported failure\");\n+\treturn 0;\n }\n \n /*\n@@ -231,14 +240,14 @@ static void export_object(const struct object_id *oid, enum object_type type,\n  * interpreting it as \"type\", and writing the result to the object database.\n  * The sha1 of the written object is returned via sha1.\n  */\n-static void import_object(struct object_id *oid, enum object_type type,\n+static int import_object(struct object_id *oid, enum object_type type,\n \t\t\t  int raw, const char *filename)\n {\n \tint fd;\n \n \tfd = open(filename, O_RDONLY);\n \tif (fd < 0)\n-\t\tdie_errno(\"unable to open %s for reading\", filename);\n+\t\treturn error_errno(\"unable to open %s for reading\", filename);\n \n \tif (!raw && type == OBJ_TREE) {\n \t\tconst char *argv[] = { \"mktree\", NULL };\n@@ -250,27 +259,40 @@ static void import_object(struct object_id *oid, enum object_type type,\n \t\tcmd.in = fd;\n \t\tcmd.out = -1;\n \n-\t\tif (start_command(&cmd))\n-\t\t\tdie(\"unable to spawn mktree\");\n+\t\tif (start_command(&cmd)) {\n+\t\t\tclose(fd);\n+\t\t\treturn error(\"unable to spawn mktree\");\n+\t\t}\n \n-\t\tif (strbuf_read(&result, cmd.out, 41) < 0)\n-\t\t\tdie_errno(\"unable to read from mktree\");\n+\t\tif (strbuf_read(&result, cmd.out, 41) < 0) {\n+\t\t\terror_errno(\"unable to read from mktree\");\n+\t\t\tclose(fd);\n+\t\t\tclose(cmd.out);\n+\t\t\treturn -1;\n+\t\t}\n \t\tclose(cmd.out);\n \n-\t\tif (finish_command(&cmd))\n-\t\t\tdie(\"mktree reported failure\");\n-\t\tif (get_oid_hex(result.buf, oid) < 0)\n-\t\t\tdie(\"mktree did not return an object name\");\n+\t\tif (finish_command(&cmd)) {\n+\t\t\tstrbuf_release(&result);\n+\t\t\treturn error(\"mktree reported failure\");\n+\t\t}\n+\t\tif (get_oid_hex(result.buf, oid) < 0) {\n+\t\t\tstrbuf_release(&result);\n+\t\t\treturn error(\"mktree did not return an object name\");\n+\t\t}\n \n \t\tstrbuf_release(&result);\n \t} else {\n \t\tstruct stat st;\n \t\tint flags = HASH_FORMAT_CHECK | HASH_WRITE_OBJECT;\n \n-\t\tif (fstat(fd, &st) < 0)\n-\t\t\tdie_errno(\"unable to fstat %s\", filename);\n+\t\tif (fstat(fd, &st) < 0) {\n+\t\t\terror_errno(\"unable to fstat %s\", filename);\n+\t\t\tclose(fd);\n+\t\t\treturn -1;\n+\t\t}\n \t\tif (index_fd(oid, fd, &st, type, NULL, flags) < 0)\n-\t\t\tdie(\"unable to write object to database\");\n+\t\t\treturn error(\"unable to write object to database\");\n \t\t/* index_fd close()s fd for us */\n \t}\n \n@@ -278,30 +300,38 @@ static void import_object(struct object_id *oid, enum object_type type,\n \t * No need to close(fd) here; both run-command and index-fd\n \t * will have done it for us.\n \t */\n+\treturn 0;\n }\n \n static int edit_and_replace(const char *object_ref, int force, int raw)\n {\n-\tchar *tmpfile = git_pathdup(\"REPLACE_EDITOBJ\");\n+\tchar *tmpfile;\n \tenum object_type type;\n \tstruct object_id old_oid, new_oid, prev;\n \tstruct strbuf ref = STRBUF_INIT;\n \n \tif (get_oid(object_ref, &old_oid) < 0)\n-\t\tdie(\"Not a valid object name: '%s'\", object_ref);\n+\t\treturn error(\"Not a valid object name: '%s'\", object_ref);\n \n \ttype = oid_object_info(&old_oid, NULL);\n \tif (type < 0)\n-\t\tdie(\"unable to get object type for %s\", oid_to_hex(&old_oid));\n+\t\treturn error(\"unable to get object type for %s\",\n+\t\t\t     oid_to_hex(&old_oid));\n \n-\tcheck_ref_valid(&old_oid, &prev, &ref, force);\n+\tif (check_ref_valid(&old_oid, &prev, &ref, force)) {\n+\t\tstrbuf_release(&ref);\n+\t\treturn -1;\n+\t}\n \tstrbuf_release(&ref);\n \n-\texport_object(&old_oid, type, raw, tmpfile);\n-\tif (launch_editor(tmpfile, NULL, NULL) < 0)\n-\t\tdie(\"editing object file failed\");\n-\timport_object(&new_oid, type, raw, tmpfile);\n-\n+\ttmpfile = git_pathdup(\"REPLACE_EDITOBJ\");\n+\tif (export_object(&old_oid, type, raw, tmpfile) ||\n+\t    (launch_editor(tmpfile, NULL, NULL) < 0 &&\n+\t     error(\"editing object file failed\")) ||\n+\t    import_object(&new_oid, type, raw, tmpfile)) {\n+\t\tfree(tmpfile);\n+\t\treturn -1;\n+\t}\n \tfree(tmpfile);\n \n \tif (!oidcmp(&old_oid, &new_oid))\n@@ -310,7 +340,7 @@ static int edit_and_replace(const char *object_ref, int force, int raw)\n \treturn replace_object_oid(object_ref, &old_oid, \"replacement\", &new_oid, force);\n }\n \n-static void replace_parents(struct strbuf *buf, int argc, const char **argv)\n+static int 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@@ -327,9 +357,15 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv)\n \t/* prepare new parents */\n \tfor (i = 0; i < argc; i++) {\n \t\tstruct object_id oid;\n-\t\tif (get_oid(argv[i], &oid) < 0)\n-\t\t\tdie(_(\"Not a valid object name: '%s'\"), argv[i]);\n-\t\tlookup_commit_or_die(&oid, argv[i]);\n+\t\tif (get_oid(argv[i], &oid) < 0) {\n+\t\t\tstrbuf_release(&new_parents);\n+\t\t\treturn error(_(\"Not a valid object name: '%s'\"),\n+\t\t\t\t     argv[i]);\n+\t\t}\n+\t\tif (!lookup_commit_reference(&oid)) {\n+\t\t\tstrbuf_release(&new_parents);\n+\t\t\treturn error(_(\"could not parse %s\"), argv[i]);\n+\t\t}\n \t\tstrbuf_addf(&new_parents, \"parent %s\\n\", oid_to_hex(&oid));\n \t}\n \n@@ -338,6 +374,7 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv)\n \t\t      new_parents.buf, new_parents.len);\n \n \tstrbuf_release(&new_parents);\n+\treturn 0;\n }\n \n struct check_mergetag_data {\n@@ -358,21 +395,23 @@ static int check_one_mergetag(struct commit *commit,\n \thash_object_file(extra->value, extra->len, type_name(OBJ_TAG), &tag_oid);\n \ttag = lookup_tag(&tag_oid);\n \tif (!tag)\n-\t\tdie(_(\"bad mergetag in commit '%s'\"), ref);\n+\t\treturn error(_(\"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+\t\treturn error(_(\"malformed mergetag in commit '%s'\"), ref);\n \n \t/* iterate over new parents */\n \tfor (i = 1; i < mergetag_data->argc; i++) {\n \t\tstruct object_id oid;\n \t\tif (get_oid(mergetag_data->argv[i], &oid) < 0)\n-\t\t\tdie(_(\"Not a valid object name: '%s'\"), mergetag_data->argv[i]);\n+\t\t\treturn error(_(\"Not a valid object name: '%s'\"),\n+\t\t\t\t     mergetag_data->argv[i]);\n \t\tif (!oidcmp(&tag->tagged->oid, &oid))\n \t\t\treturn 0; /* found */\n \t}\n \n-\tdie(_(\"original commit '%s' contains mergetag '%s' that is discarded; \"\n-\t      \"use --edit instead of --graft\"), ref, oid_to_hex(&tag_oid));\n+\treturn error(_(\"original commit '%s' contains mergetag '%s' that is \"\n+\t\t       \"discarded; use --edit instead of --graft\"), ref,\n+\t\t     oid_to_hex(&tag_oid));\n }\n \n static int check_mergetags(struct commit *commit, int argc, const char **argv)\n@@ -394,24 +433,35 @@ static int create_graft(int argc, const char **argv, int force)\n \tunsigned long size;\n \n \tif (get_oid(old_ref, &old_oid) < 0)\n-\t\tdie(_(\"Not a valid object name: '%s'\"), old_ref);\n-\tcommit = lookup_commit_or_die(&old_oid, old_ref);\n+\t\treturn error(_(\"Not a valid object name: '%s'\"), old_ref);\n+\tcommit = lookup_commit_reference(&old_oid);\n+\tif (!commit)\n+\t\treturn error(_(\"could not parse %s\"), 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+\tif (replace_parents(&buf, argc - 1, &argv[1]) < 0) {\n+\t\tstrbuf_release(&buf);\n+\t\treturn -1;\n+\t}\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-\tcheck_mergetags(commit, argc, argv);\n+\tif (check_mergetags(commit, argc, argv)) {\n+\t\tstrbuf_release(&buf);\n+\t\treturn -1;\n+\t}\n \n-\tif (write_object_file(buf.buf, buf.len, commit_type, &new_oid))\n-\t\tdie(_(\"could not write replacement commit for: '%s'\"), old_ref);\n+\tif (write_object_file(buf.buf, buf.len, commit_type, &new_oid)) {\n+\t\tstrbuf_release(&buf);\n+\t\treturn error(_(\"could not write replacement commit for: '%s'\"),\n+\t\t\t     old_ref);\n+\t}\n \n \tstrbuf_release(&buf);\n \n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345753","messageId":"045d75cb198b7854eb951f314124353673c632c0.1524650029.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524650028.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 07/11] Deprecate support for .git/info/grafts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T09:54:25Z","receivedAt":"2018-04-25T09:54:37Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The grafts feature was a convenient way to \"stitch together\" ancient\nhistory to the fresh start of linux.git.\n\nIts implementation is, however, not up to Git's standards, as there are\ntoo many ways where it can lead to surprising and unwelcome behavior.\n\nFor example, when pushing from a repository with active grafts, it is\npossible to miss commits that have been \"grafted out\", resulting in a\nbroken state on the other side.\n\nAlso, the grafts feature is limited to \"rewriting\" commits' list of\nparents, it cannot replace anything else.\n\nThe much younger feature implemented as `git replace` set out to remedy\nthose limitations and dangerous bugs.\n\nSeeing as `git replace` is pretty mature by now (since 4228e8bc98\n(replace: add --graft option, 2014-07-19) it can perform the graft\nfile's duties), it is time to deprecate support for the graft file, and\nto retire it eventually.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nReviewed-by: Stefan Beller <sbeller@google.com>\n---\n advice.c                  |  2 ++\n advice.h                  |  1 +\n commit.c                  | 10 ++++++++++\n t/t6001-rev-list-graft.sh |  9 +++++++++\n 4 files changed, 22 insertions(+)\n\ndiff --git a/advice.c b/advice.c\nindex 406efc183ba..4411704fd45 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -19,6 +19,7 @@ int advice_rm_hints = 1;\n int advice_add_embedded_repo = 1;\n int advice_ignored_hook = 1;\n int advice_waiting_for_editor = 1;\n+int advice_graft_file_deprecated = 1;\n \n static struct {\n \tconst char *name;\n@@ -42,6 +43,7 @@ static struct {\n \t{ \"addembeddedrepo\", &advice_add_embedded_repo },\n \t{ \"ignoredhook\", &advice_ignored_hook },\n \t{ \"waitingforeditor\", &advice_waiting_for_editor },\n+\t{ \"graftfiledeprecated\", &advice_graft_file_deprecated },\n \n \t/* make this an alias for backward compatibility */\n \t{ \"pushnonfastforward\", &advice_push_update_rejected }\ndiff --git a/advice.h b/advice.h\nindex 70568fa7922..9f5064e82a8 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -21,6 +21,7 @@ extern int advice_rm_hints;\n extern int advice_add_embedded_repo;\n extern int advice_ignored_hook;\n extern int advice_waiting_for_editor;\n+extern int advice_graft_file_deprecated;\n \n int git_default_advice_config(const char *var, const char *value);\n __attribute__((format (printf, 1, 2)))\ndiff --git a/commit.c b/commit.c\nindex 2952ec987c5..451d3ce8dfe 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -12,6 +12,7 @@\n #include \"prio-queue.h\"\n #include \"sha1-lookup.h\"\n #include \"wt-status.h\"\n+#include \"advice.h\"\n \n static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);\n \n@@ -176,6 +177,15 @@ static int read_graft_file(const char *graft_file)\n \tstruct strbuf buf = STRBUF_INIT;\n \tif (!fp)\n \t\treturn -1;\n+\tif (advice_graft_file_deprecated)\n+\t\tadvise(_(\"Support for <GIT_DIR>/info/grafts is deprecated\\n\"\n+\t\t\t \"and will be removed in a future Git version.\\n\"\n+\t\t\t \"\\n\"\n+\t\t\t \"Please use \\\"git replace --convert-graft-file\\\"\\n\"\n+\t\t\t \"to convert the grafts into replace refs.\\n\"\n+\t\t\t \"\\n\"\n+\t\t\t \"Turn this message off by running\\n\"\n+\t\t\t \"\\\"git config advice.graftFileDeprecated false\\\"\"));\n \twhile (!strbuf_getwholeline(&buf, fp, '\\n')) {\n \t\t/* The format is just \"Commit Parent1 Parent2 ...\\n\" */\n \t\tstruct commit_graft *graft = read_graft_line(&buf);\ndiff --git a/t/t6001-rev-list-graft.sh b/t/t6001-rev-list-graft.sh\nindex 05ddc69cf2a..7504ba47511 100755\n--- a/t/t6001-rev-list-graft.sh\n+++ b/t/t6001-rev-list-graft.sh\n@@ -110,4 +110,13 @@ do\n \t\"\n \n done\n+\n+test_expect_success 'show advice that grafts are deprecated' '\n+\tgit show HEAD 2>err &&\n+\ttest_i18ngrep \"git replace\" err &&\n+\ttest_config advice.graftFileDeprecated false &&\n+\tgit show HEAD 2>err &&\n+\ttest_i18ngrep ! \"git replace\" err\n+'\n+\n test_done\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345754","messageId":"07372da0d614a13b0aeb58cac3b5994530e6de5c.1524650029.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524650028.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 06/11] Add a test for `git replace --convert-graft-file`","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T09:54:17Z","receivedAt":"2018-04-25T09:54:40Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The proof, as the saying goes, lies in the pudding. So here is a\nregression test that not only demonstrates what the option is supposed to\naccomplish, but also demonstrates that it does accomplish it.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/replace.c  |  1 -\n t/t6050-replace.sh | 20 ++++++++++++++++++++\n 2 files changed, 20 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex fdd12c5ffc0..acd30e3d824 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -494,7 +494,6 @@ static int convert_graft_file(int force)\n \tfclose(fp);\n \n \tstrbuf_release(&buf);\n-\targv_array_clear(&args);\n \n \tif (!err.len)\n \t\treturn unlink_or_warn(graft_file);\ndiff --git a/t/t6050-replace.sh b/t/t6050-replace.sh\nindex c630aba657e..bed86a0af3d 100755\n--- a/t/t6050-replace.sh\n+++ b/t/t6050-replace.sh\n@@ -444,4 +444,24 @@ test_expect_success GPG '--graft on a commit with a mergetag' '\n \tgit replace -d $HASH10\n '\n \n+test_expect_success '--convert-graft-file' '\n+\t: add and convert graft file &&\n+\tprintf \"%s\\n%s %s\\n\\n# comment\\n%s\\n\" \\\n+\t\t$(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \\\n+\t\t>.git/info/grafts &&\n+\tgit replace --convert-graft-file &&\n+\ttest_path_is_missing .git/info/grafts &&\n+\n+\t: verify that the history is now \"grafted\" &&\n+\tgit rev-list HEAD >out &&\n+\ttest_line_count = 4 out &&\n+\n+\t: create invalid graft file and verify that it is not deleted &&\n+\ttest_when_finished \"rm -f .git/info/grafts\" &&\n+\techo $EMPTY_BLOB $EMPTY_TREE >.git/info/grafts &&\n+\ttest_must_fail git replace --convert-graft-file 2>err &&\n+\ttest_i18ngrep \"$EMPTY_BLOB $EMPTY_TREE\" err &&\n+\ttest_i18ngrep \"$EMPTY_BLOB $EMPTY_TREE\" .git/info/grafts\n+'\n+\n test_done\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345755","messageId":"890a48faf6c77027d63cc5ab3f139a0fcb33eb30.1524650029.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524650028.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 08/11] filter-branch: stop suggesting to use grafts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T09:54:27Z","receivedAt":"2018-04-25T09:54:44Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The graft file is deprecated now, so let's use replace refs in the example\nin filter-branch's man page instead.\n\nSuggested-by: Eric Sunshine <sunshine@sunshineco.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-filter-branch.txt | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt\nindex b634043183b..1d4d2f86045 100644\n--- a/Documentation/git-filter-branch.txt\n+++ b/Documentation/git-filter-branch.txt\n@@ -288,7 +288,7 @@ git filter-branch --parent-filter \\\n or even simpler:\n \n -----------------------------------------------\n-echo \"$commit-id $graft-id\" >> .git/info/grafts\n+git replace --graft $commit-id $graft-id\n git filter-branch $graft-id..HEAD\n -----------------------------------------------\n \n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345756","messageId":"a3403a69b692e4f4b0337800268a16818e790caa.1524650029.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524650028.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 09/11] technical/shallow: stop referring to grafts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T09:54:34Z","receivedAt":"2018-04-25T09:54:47Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Now that grafts are deprecated, we should start to assume that readers\nhave no idea what grafts are. So it makes more sense to make the\ndescription of the \"shallow\" feature stand on its own.\n\nSuggested-by: Eric Sunshine <sunshine@sunshineco.com>\nHelped-by: Junio Hamano <gitster@pobox.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/technical/shallow.txt | 13 ++++---------\n 1 file changed, 4 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/technical/shallow.txt b/Documentation/technical/shallow.txt\nindex 5183b154229..4ec721335d2 100644\n--- a/Documentation/technical/shallow.txt\n+++ b/Documentation/technical/shallow.txt\n@@ -8,15 +8,10 @@ repo, and therefore grafts are introduced pretending that\n these commits have no parents.\n *********************************************************\n \n-The basic idea is to write the SHA-1s of shallow commits into\n-$GIT_DIR/shallow, and handle its contents like the contents\n-of $GIT_DIR/info/grafts (with the difference that shallow\n-cannot contain parent information).\n-\n-This information is stored in a new file instead of grafts, or\n-even the config, since the user should not touch that file\n-at all (even throughout development of the shallow clone, it\n-was never manually edited!).\n+$GIT_DIR/shallow lists commit object names and tells Git to\n+pretend as if they are root commits (e.g. \"git log\" traversal\n+stops after showing them; \"git fsck\" does not complain saying\n+the commits listed on their \"parent\" lines do not exist).\n \n Each line contains exactly one SHA-1. When read, a commit_graft\n will be constructed, which has nr_parent < 0 to make it easier\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345757","messageId":"aa5b64805fdb188bfdca5193ee01c081d9917634.1524650029.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524650028.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 11/11] Remove obsolete script to convert grafts to replace refs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T09:54:44Z","receivedAt":"2018-04-25T09:54:56Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The functionality is now implemented as `git replace\n--convert-graft-file`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n contrib/convert-grafts-to-replace-refs.sh | 28 -----------------------\n 1 file changed, 28 deletions(-)\n delete mode 100755 contrib/convert-grafts-to-replace-refs.sh\n\ndiff --git a/contrib/convert-grafts-to-replace-refs.sh b/contrib/convert-grafts-to-replace-refs.sh\ndeleted file mode 100755\nindex 0cbc917b8cf..00000000000\n--- a/contrib/convert-grafts-to-replace-refs.sh\n+++ /dev/null\n@@ -1,28 +0,0 @@\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.17.0.windows.1.33.gfcbb1fa0445\n"},{"id":"345758","messageId":"4cbe2ebb4ce3ccdeacdea7be394fbe54f95db848.1524650029.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524650028.git.johannes.schindelin@gmx.de","subject":"[PATCH v5 10/11] technical/shallow: describe why shallow cannot use replace refs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-25T09:54:37Z","receivedAt":"2018-04-25T09:55:29Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"It is tempting to do away with commit_graft altogether (in the long\nhaul), now that grafts are deprecated.\n\nHowever, the shallow feature needs a couple of things that the replace\nrefs cannot fulfill. Let's point that out in the documentation.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/technical/shallow.txt | 7 +++++++\n 1 file changed, 7 insertions(+)\n\ndiff --git a/Documentation/technical/shallow.txt b/Documentation/technical/shallow.txt\nindex 4ec721335d2..01dedfe9ffe 100644\n--- a/Documentation/technical/shallow.txt\n+++ b/Documentation/technical/shallow.txt\n@@ -17,6 +17,13 @@ Each line contains exactly one SHA-1. When read, a commit_graft\n will be constructed, which has nr_parent < 0 to make it easier\n to discern from user provided grafts.\n \n+Note that the shallow feature could not be changed easily to\n+use replace refs: a commit containing a `mergetag` is not allowed\n+to be replaced, not even by a root commit. Such a commit can be\n+made shallow, though. Also, having a `shallow` file explicitly\n+listing all the commits made shallow makes it a *lot* easier to\n+do shallow-specific things such as to deepen the history.\n+\n Since fsck-objects relies on the library to read the objects,\n it honours shallow commits automatically.\n \n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345760","messageId":"CANQwDwfJgoXd+iH207S8WoaWOS4BQnrm+GAOtfe1BvW9uyKiYA@mail.gmail.com","threadId":"48278","inReplyTo":"a3403a69b692e4f4b0337800268a16818e790caa.1524650029.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v5 09/11] technical/shallow: stop referring to grafts","fromName":"Jakub Narębski","fromEmail":"jnareb@gmail.com","sentAt":"2018-04-25T12:25:32Z","receivedAt":"2018-04-25T12:26:18Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"On 25 April 2018 at 11:54, Johannes Schindelin\n<johannes.schindelin@gmx.de> wrote:\n> Now that grafts are deprecated, we should start to assume that readers\n> have no idea what grafts are. So it makes more sense to make the\n> description of the \"shallow\" feature stand on its own.\n>\n> Suggested-by: Eric Sunshine <sunshine@sunshineco.com>\n> Helped-by: Junio Hamano <gitster@pobox.com>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  Documentation/technical/shallow.txt | 13 ++++---------\n>  1 file changed, 4 insertions(+), 9 deletions(-)\n>\n> diff --git a/Documentation/technical/shallow.txt b/Documentation/technical/shallow.txt\n> index 5183b154229..4ec721335d2 100644\n> --- a/Documentation/technical/shallow.txt\n> +++ b/Documentation/technical/shallow.txt\n> @@ -8,15 +8,10 @@ repo, and therefore grafts are introduced pretending that\n>  these commits have no parents.\n>  *********************************************************\n>\n> -The basic idea is to write the SHA-1s of shallow commits into\n> -$GIT_DIR/shallow, and handle its contents like the contents\n> -of $GIT_DIR/info/grafts (with the difference that shallow\n> -cannot contain parent information).\n> -\n> -This information is stored in a new file instead of grafts, or\n> -even the config, since the user should not touch that file\n> -at all (even throughout development of the shallow clone, it\n> -was never manually edited!).\n> +$GIT_DIR/shallow lists commit object names and tells Git to\n> +pretend as if they are root commits (e.g. \"git log\" traversal\n> +stops after showing them; \"git fsck\" does not complain saying\n> +the commits listed on their \"parent\" lines do not exist).\n>\n>  Each line contains exactly one SHA-1. When read, a commit_graft\n>  will be constructed, which has nr_parent < 0 to make it easier\n\nIs the removed information (repeated below) important or not?\n\n  the user should not touch that file\n  at all (even throughout development of the shallow clone, it\n  was never manually edited!).\n\n-- \nJakub Narębski\n"},{"id":"345857","messageId":"xmqqin8e38wf.fsf@gitster-ct.c.googlers.com","threadId":"48278","inReplyTo":"cover.1524650028.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v5 00/11] Deprecate .git/info/grafts","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-04-26T04:10:24Z","receivedAt":"2018-04-26T04:10:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n\n>  -\tif (export_object(&old_oid, type, raw, tmpfile))\n>  -\t\treturn -1;\n>  -\tif (launch_editor(tmpfile, NULL, NULL) < 0)\n>  -\t\treturn error(\"editing object file failed\");\n>  -\tif (import_object(&new_oid, type, raw, tmpfile))\n>  +\ttmpfile = git_pathdup(\"REPLACE_EDITOBJ\");\n>  +\tif (export_object(&old_oid, type, raw, tmpfile) ||\n>  +\t    (launch_editor(tmpfile, NULL, NULL) < 0 &&\n>  +\t     error(\"editing object file failed\")) ||\n>  +\t    import_object(&new_oid, type, raw, tmpfile)) {\n>  +\t\tfree(tmpfile);\n>   \t\treturn -1;\n>  -\n>  +\t}\n\nI know the above is to avoid leaking tmpfile, but a single if ()\ncondition that makes multiple calls to functions primarily for their\nside effects is too ugly to live.  Perhaps something like\n\n\tif (export_object(...))\n\t\tgoto clean_fail;\n\tif (launch_editor(...)) {\n\t\terror(\"editing object file failed\");\n\t\tgoto clean_fail;\n\t}\n\tif (import_object(...)) {\n\tclean_fail:\n\t\tfree(tmpfile);\n\t\treturn -1;\n\t}\n\nwould keep the ease-of-reading of the original while plugging the\nleak.  It would even be cleaner to move the body of clean_fail:\ncompletely out of line, instead of having it in the last of three\nsteps like the above.\n\nOther than that, looked cleanly done.  Thanks for a pleasant read.\n\nWill queue.\n"},{"id":"345886","messageId":"nycvar.QRO.7.76.6.1804261117480.4978@tvgsbejvaqbjf.bet","threadId":"48278","inReplyTo":"CANQwDwfJgoXd+iH207S8WoaWOS4BQnrm+GAOtfe1BvW9uyKiYA@mail.gmail.com","subject":"Re: [PATCH v5 09/11] technical/shallow: stop referring to grafts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-26T09:19:21Z","receivedAt":"2018-04-26T09:19:57Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Kuba,\n\nOn Wed, 25 Apr 2018, Jakub Narębski wrote:\n\n> On 25 April 2018 at 11:54, Johannes Schindelin\n> <johannes.schindelin@gmx.de> wrote:\n> > diff --git a/Documentation/technical/shallow.txt b/Documentation/technical/shallow.txt\n> > index 5183b154229..4ec721335d2 100644\n> > --- a/Documentation/technical/shallow.txt\n> > +++ b/Documentation/technical/shallow.txt\n> > @@ -8,15 +8,10 @@ repo, and therefore grafts are introduced pretending that\n> >  these commits have no parents.\n> >  *********************************************************\n> >\n> > -The basic idea is to write the SHA-1s of shallow commits into\n> > -$GIT_DIR/shallow, and handle its contents like the contents\n> > -of $GIT_DIR/info/grafts (with the difference that shallow\n> > -cannot contain parent information).\n> > -\n> > -This information is stored in a new file instead of grafts, or\n> > -even the config, since the user should not touch that file\n> > -at all (even throughout development of the shallow clone, it\n> > -was never manually edited!).\n> > +$GIT_DIR/shallow lists commit object names and tells Git to\n> > +pretend as if they are root commits (e.g. \"git log\" traversal\n> > +stops after showing them; \"git fsck\" does not complain saying\n> > +the commits listed on their \"parent\" lines do not exist).\n> >\n> >  Each line contains exactly one SHA-1. When read, a commit_graft\n> >  will be constructed, which has nr_parent < 0 to make it easier\n> \n> Is the removed information (repeated below) important or not?\n> \n>   the user should not touch that file\n>   at all (even throughout development of the shallow clone, it\n>   was never manually edited!).\n\nBack in the days, it might have been necessary to tell people not to\nmeddle with Git's internals. Nowadays I don't think that'd be necessary\nanymore, hence I removed it.\n\nCiao,\nDscho\n"},{"id":"345991","messageId":"nycvar.QRO.7.76.6.1804272258380.72@tvgsbejvaqbjf.bet","threadId":"48278","inReplyTo":"xmqqin8e38wf.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v5 00/11] Deprecate .git/info/grafts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-27T21:03:02Z","receivedAt":"2018-04-27T21:03:31Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Junio,\n\nOn Thu, 26 Apr 2018, Junio C Hamano wrote:\n\n> Johannes Schindelin <johannes.schindelin@gmx.de> writes:\n> \n> >  -\tif (export_object(&old_oid, type, raw, tmpfile))\n> >  -\t\treturn -1;\n> >  -\tif (launch_editor(tmpfile, NULL, NULL) < 0)\n> >  -\t\treturn error(\"editing object file failed\");\n> >  -\tif (import_object(&new_oid, type, raw, tmpfile))\n> >  +\ttmpfile = git_pathdup(\"REPLACE_EDITOBJ\");\n> >  +\tif (export_object(&old_oid, type, raw, tmpfile) ||\n> >  +\t    (launch_editor(tmpfile, NULL, NULL) < 0 &&\n> >  +\t     error(\"editing object file failed\")) ||\n> >  +\t    import_object(&new_oid, type, raw, tmpfile)) {\n> >  +\t\tfree(tmpfile);\n> >   \t\treturn -1;\n> >  -\n> >  +\t}\n> \n> I know the above is to avoid leaking tmpfile, but a single if ()\n> condition that makes multiple calls to functions primarily for their\n> side effects is too ugly to live.\n\nI changed it back to individual conditional blocks, with every single one\nof them having their own free(tmpfile). That is at least clearer.\n\nCiao,\nDscho\n"},{"id":"345997","messageId":"cover.1524865158.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524650028.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 00/11] Deprecate .git/info/grafts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-27T21:39:18Z","receivedAt":"2018-04-27T21:39:35Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"It is fragile, as there is no way for the revision machinery to say \"but\nnow I want to traverse the graph ignoring the graft file\" e.g. when\npushing commits to a remote repository (which, as a consequence, can\nmiss commits).\n\nAnd we already have a better solution with `git replace --graft <comit>\n[<parent>...]`.\n\nChanges since v5:\n\n- Disentangled the lumped-together conditional blocks in\n  edit_and_replace() again.\n\n- Moved fixup (a superfluous argv_array_clear()) from the patch that\n  adds a test for --convert-graft-file back to the patch that actually\n  introduces that option.\n\n\nJohannes Schindelin (11):\n  argv_array: offer to split a string by whitespace\n  commit: Let the callback of for_each_mergetag return on error\n  replace: avoid using die() to indicate a bug\n  replace: \"libify\" create_graft() and callees\n  replace: introduce --convert-graft-file\n  Add a test for `git replace --convert-graft-file`\n  Deprecate support for .git/info/grafts\n  filter-branch: stop suggesting to use grafts\n  technical/shallow: stop referring to grafts\n  technical/shallow: describe why shallow cannot use replace refs\n  Remove obsolete script to convert grafts to replace refs\n\n Documentation/git-filter-branch.txt       |   2 +-\n Documentation/git-replace.txt             |  11 +-\n Documentation/technical/shallow.txt       |  20 +-\n advice.c                                  |   2 +\n advice.h                                  |   1 +\n argv-array.c                              |  20 ++\n argv-array.h                              |   2 +\n builtin/replace.c                         | 223 ++++++++++++++++------\n commit.c                                  |  18 +-\n commit.h                                  |   4 +-\n contrib/convert-grafts-to-replace-refs.sh |  28 ---\n log-tree.c                                |  13 +-\n t/t6001-rev-list-graft.sh                 |   9 +\n t/t6050-replace.sh                        |  20 ++\n 14 files changed, 258 insertions(+), 115 deletions(-)\n delete mode 100755 contrib/convert-grafts-to-replace-refs.sh\n\n\nbase-commit: 1f1cddd558b54bb0ce19c8ace353fd07b758510d\nPublished-As: https://github.com/dscho/git/releases/tag/deprecate-grafts-v6\nFetch-It-Via: git fetch https://github.com/dscho/git deprecate-grafts-v6\n\nInterdiff vs v5:\n diff --git a/builtin/replace.c b/builtin/replace.c\n index acd30e3d824..35394ec1874 100644\n --- a/builtin/replace.c\n +++ b/builtin/replace.c\n @@ -326,10 +326,15 @@ static int edit_and_replace(const char *object_ref, int force, int raw)\n  \tstrbuf_release(&ref);\n  \n  \ttmpfile = git_pathdup(\"REPLACE_EDITOBJ\");\n -\tif (export_object(&old_oid, type, raw, tmpfile) ||\n -\t    (launch_editor(tmpfile, NULL, NULL) < 0 &&\n -\t     error(\"editing object file failed\")) ||\n -\t    import_object(&new_oid, type, raw, tmpfile)) {\n +\tif (export_object(&old_oid, type, raw, tmpfile)) {\n +\t\tfree(tmpfile);\n +\t\treturn -1;\n +\t}\n +\tif (launch_editor(tmpfile, NULL, NULL) < 0) {\n +\t\tfree(tmpfile);\n +\t\treturn error(\"editing object file failed\");\n +\t}\n +\tif (import_object(&new_oid, type, raw, tmpfile)) {\n  \t\tfree(tmpfile);\n  \t\treturn -1;\n  \t}\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n"},{"id":"345998","messageId":"178058d887281fc87e5008367bbee5677a619320.1524865158.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524865158.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 01/11] argv_array: offer to split a string by whitespace","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-27T21:39:25Z","receivedAt":"2018-04-27T21:39:37Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This is a simple function that will interpret a string as a whitespace\ndelimited list of values, and add those values into the array.\n\nNote: this function does not (yet) offer to split by arbitrary delimiters,\nor keep empty values in case of runs of whitespace, or de-quote Unix shell\nstyle. All fo this functionality can be added later, when and if needed.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n argv-array.c | 20 ++++++++++++++++++++\n argv-array.h |  2 ++\n 2 files changed, 22 insertions(+)\n\ndiff --git a/argv-array.c b/argv-array.c\nindex 5d370fa3366..cb5bcd2c064 100644\n--- a/argv-array.c\n+++ b/argv-array.c\n@@ -64,6 +64,26 @@ void argv_array_pop(struct argv_array *array)\n \tarray->argc--;\n }\n \n+void argv_array_split(struct argv_array *array, const char *to_split)\n+{\n+\twhile (isspace(*to_split))\n+\t\tto_split++;\n+\tfor (;;) {\n+\t\tconst char *p = to_split;\n+\n+\t\tif (!*p)\n+\t\t\tbreak;\n+\n+\t\twhile (*p && !isspace(*p))\n+\t\t\tp++;\n+\t\targv_array_push_nodup(array, xstrndup(to_split, p - to_split));\n+\n+\t\twhile (isspace(*p))\n+\t\t\tp++;\n+\t\tto_split = p;\n+\t}\n+}\n+\n void argv_array_clear(struct argv_array *array)\n {\n \tif (array->argv != empty_argv) {\ndiff --git a/argv-array.h b/argv-array.h\nindex 29056e49a12..750c30d2f2c 100644\n--- a/argv-array.h\n+++ b/argv-array.h\n@@ -19,6 +19,8 @@ LAST_ARG_MUST_BE_NULL\n void argv_array_pushl(struct argv_array *, ...);\n void argv_array_pushv(struct argv_array *, const char **);\n void argv_array_pop(struct argv_array *);\n+/* Splits by whitespace; does not handle quoted arguments! */\n+void argv_array_split(struct argv_array *, const char *);\n void argv_array_clear(struct argv_array *);\n const char **argv_array_detach(struct argv_array *);\n \n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"345999","messageId":"97428f56890413d16f1487309972c446b61f784f.1524865158.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524865158.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 02/11] commit: Let the callback of for_each_mergetag return on error","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-27T21:39:28Z","receivedAt":"2018-04-27T21:39:43Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This is yet another patch to be filed under the keyword \"libification\".\n\nThere is one subtle change in behavior here, where a `git log` that has\nbeen asked to show the mergetags would now stop reporting the mergetags\nupon the first failure, whereas previously, it would have continued to the\nnext mergetag, if any.\n\nIn practice, that change should not matter, as it is 1) uncommon to\nperform octopus merges using multiple tags as merge heads, and 2) when the\nuser asks to be shown those tags, they really should be there.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/replace.c |  8 ++++----\n commit.c          |  8 +++++---\n commit.h          |  4 ++--\n log-tree.c        | 13 +++++++------\n 4 files changed, 18 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 935647be6bd..245d3f4164e 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -345,7 +345,7 @@ struct check_mergetag_data {\n \tconst char **argv;\n };\n \n-static void check_one_mergetag(struct commit *commit,\n+static int check_one_mergetag(struct commit *commit,\n \t\t\t       struct commit_extra_header *extra,\n \t\t\t       void *data)\n {\n@@ -368,20 +368,20 @@ static void check_one_mergetag(struct commit *commit,\n \t\tif (get_oid(mergetag_data->argv[i], &oid) < 0)\n \t\t\tdie(_(\"Not a valid object name: '%s'\"), mergetag_data->argv[i]);\n \t\tif (!oidcmp(&tag->tagged->oid, &oid))\n-\t\t\treturn; /* found */\n+\t\t\treturn 0; /* found */\n \t}\n \n \tdie(_(\"original commit '%s' contains mergetag '%s' that is discarded; \"\n \t      \"use --edit instead of --graft\"), ref, oid_to_hex(&tag_oid));\n }\n \n-static void check_mergetags(struct commit *commit, int argc, const char **argv)\n+static int 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+\treturn for_each_mergetag(check_one_mergetag, commit, &mergetag_data);\n }\n \n static int create_graft(int argc, const char **argv, int force)\ndiff --git a/commit.c b/commit.c\nindex ca474a7c112..2952ec987c5 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -1288,17 +1288,19 @@ struct commit_extra_header *read_commit_extra_headers(struct commit *commit,\n \treturn extra;\n }\n \n-void for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data)\n+int for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data)\n {\n \tstruct commit_extra_header *extra, *to_free;\n+\tint res = 0;\n \n \tto_free = read_commit_extra_headers(commit, NULL);\n-\tfor (extra = to_free; extra; extra = extra->next) {\n+\tfor (extra = to_free; !res && extra; extra = extra->next) {\n \t\tif (strcmp(extra->key, \"mergetag\"))\n \t\t\tcontinue; /* not a merge tag */\n-\t\tfn(commit, extra, data);\n+\t\tres = fn(commit, extra, data);\n \t}\n \tfree_commit_extra_headers(to_free);\n+\treturn res;\n }\n \n static inline int standard_header_field(const char *field, size_t len)\ndiff --git a/commit.h b/commit.h\nindex 0fb8271665c..9000895ad91 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -291,10 +291,10 @@ extern const char *find_commit_header(const char *msg, const char *key,\n /* Find the end of the log message, the right place for a new trailer. */\n extern int ignore_non_trailer(const char *buf, size_t len);\n \n-typedef void (*each_mergetag_fn)(struct commit *commit, struct commit_extra_header *extra,\n+typedef int (*each_mergetag_fn)(struct commit *commit, struct commit_extra_header *extra,\n \t\t\t\t void *cb_data);\n \n-extern void for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data);\n+extern int for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data);\n \n struct merge_remote_desc {\n \tstruct object *obj; /* the named object, could be a tag */\ndiff --git a/log-tree.c b/log-tree.c\nindex d1c0bedf244..f3a51a6e726 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -488,9 +488,9 @@ static int is_common_merge(const struct commit *commit)\n \t\t&& !commit->parents->next->next);\n }\n \n-static void show_one_mergetag(struct commit *commit,\n-\t\t\t      struct commit_extra_header *extra,\n-\t\t\t      void *data)\n+static int show_one_mergetag(struct commit *commit,\n+\t\t\t     struct commit_extra_header *extra,\n+\t\t\t     void *data)\n {\n \tstruct rev_info *opt = (struct rev_info *)data;\n \tstruct object_id oid;\n@@ -502,7 +502,7 @@ static void show_one_mergetag(struct commit *commit,\n \thash_object_file(extra->value, extra->len, type_name(OBJ_TAG), &oid);\n \ttag = lookup_tag(&oid);\n \tif (!tag)\n-\t\treturn; /* error message already given */\n+\t\treturn -1; /* error message already given */\n \n \tstrbuf_init(&verify_message, 256);\n \tif (parse_tag_buffer(tag, extra->value, extra->len))\n@@ -536,11 +536,12 @@ static void show_one_mergetag(struct commit *commit,\n \n \tshow_sig_lines(opt, status, verify_message.buf);\n \tstrbuf_release(&verify_message);\n+\treturn 0;\n }\n \n-static void show_mergetag(struct rev_info *opt, struct commit *commit)\n+static int show_mergetag(struct rev_info *opt, struct commit *commit)\n {\n-\tfor_each_mergetag(show_one_mergetag, commit, opt);\n+\treturn for_each_mergetag(show_one_mergetag, commit, opt);\n }\n \n void show_log(struct rev_info *opt)\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"346000","messageId":"05642a8fb58086c9cc638fec1b4e61ffa8cc7506.1524865158.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524865158.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 03/11] replace: avoid using die() to indicate a bug","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-27T21:39:36Z","receivedAt":"2018-04-27T21:39:46Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"We have the BUG() macro for that purpose.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/replace.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 245d3f4164e..e345a5a0f1c 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -501,6 +501,6 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n \t\treturn list_replace_refs(argv[0], format);\n \n \tdefault:\n-\t\tdie(\"BUG: invalid cmdmode %d\", (int)cmdmode);\n+\t\tBUG(\"invalid cmdmode %d\", (int)cmdmode);\n \t}\n }\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"346001","messageId":"f2f490b41529327b8fb4214ef4f663b21ee43ed8.1524865158.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524865158.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 04/11] replace: \"libify\" create_graft() and callees","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-27T21:39:37Z","receivedAt":"2018-04-27T21:39:52Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"File this away as yet another patch in the \"libification\" category.\n\nAs with all useful functions, in the next commit we want to use\ncreate_graft() from a higher-level function where it would be\ninconvenient if the called function simply die()s: if there is a\nproblem, we want to let the user know how to proceed, and the callee\nsimply has no way of knowing what to say.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/replace.c | 169 ++++++++++++++++++++++++++++++----------------\n 1 file changed, 112 insertions(+), 57 deletions(-)\n\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex e345a5a0f1c..e57d3d187ed 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -79,9 +79,9 @@ static int list_replace_refs(const char *pattern, const char *format)\n \telse if (!strcmp(format, \"long\"))\n \t\tdata.format = REPLACE_FORMAT_LONG;\n \telse\n-\t\tdie(\"invalid replace format '%s'\\n\"\n-\t\t    \"valid formats are 'short', 'medium' and 'long'\\n\",\n-\t\t    format);\n+\t\treturn error(\"invalid replace format '%s'\\n\"\n+\t\t\t     \"valid formats are 'short', 'medium' and 'long'\\n\",\n+\t\t\t     format);\n \n \tfor_each_replace_ref(show_reference, (void *)&data);\n \n@@ -134,7 +134,7 @@ static int delete_replace_ref(const char *name, const char *ref,\n \treturn 0;\n }\n \n-static void check_ref_valid(struct object_id *object,\n+static int check_ref_valid(struct object_id *object,\n \t\t\t    struct object_id *prev,\n \t\t\t    struct strbuf *ref,\n \t\t\t    int force)\n@@ -142,12 +142,13 @@ static void check_ref_valid(struct object_id *object,\n \tstrbuf_reset(ref);\n \tstrbuf_addf(ref, \"%s%s\", git_replace_ref_base, oid_to_hex(object));\n \tif (check_refname_format(ref->buf, 0))\n-\t\tdie(\"'%s' is not a valid ref name.\", ref->buf);\n+\t\treturn error(\"'%s' is not a valid ref name.\", ref->buf);\n \n \tif (read_ref(ref->buf, prev))\n \t\toidclr(prev);\n \telse if (!force)\n-\t\tdie(\"replace ref '%s' already exists\", ref->buf);\n+\t\treturn error(\"replace ref '%s' already exists\", ref->buf);\n+\treturn 0;\n }\n \n static int replace_object_oid(const char *object_ref,\n@@ -161,28 +162,33 @@ static int replace_object_oid(const char *object_ref,\n \tstruct strbuf ref = STRBUF_INIT;\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n+\tint res = 0;\n \n \tobj_type = oid_object_info(object, NULL);\n \trepl_type = oid_object_info(repl, NULL);\n \tif (!force && obj_type != repl_type)\n-\t\tdie(\"Objects must be of the same type.\\n\"\n-\t\t    \"'%s' points to a replaced object of type '%s'\\n\"\n-\t\t    \"while '%s' points to a replacement object of type '%s'.\",\n-\t\t    object_ref, type_name(obj_type),\n-\t\t    replace_ref, type_name(repl_type));\n-\n-\tcheck_ref_valid(object, &prev, &ref, force);\n+\t\treturn error(\"Objects must be of the same type.\\n\"\n+\t\t\t     \"'%s' points to a replaced object of type '%s'\\n\"\n+\t\t\t     \"while '%s' points to a replacement object of \"\n+\t\t\t     \"type '%s'.\",\n+\t\t\t     object_ref, type_name(obj_type),\n+\t\t\t     replace_ref, type_name(repl_type));\n+\n+\tif (check_ref_valid(object, &prev, &ref, force)) {\n+\t\tstrbuf_release(&ref);\n+\t\treturn -1;\n+\t}\n \n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, ref.buf, repl, &prev,\n \t\t\t\t   0, NULL, &err) ||\n \t    ref_transaction_commit(transaction, &err))\n-\t\tdie(\"%s\", err.buf);\n+\t\tres = error(\"%s\", err.buf);\n \n \tref_transaction_free(transaction);\n \tstrbuf_release(&ref);\n-\treturn 0;\n+\treturn res;\n }\n \n static int replace_object(const char *object_ref, const char *replace_ref, int force)\n@@ -190,9 +196,11 @@ static int replace_object(const char *object_ref, const char *replace_ref, int f\n \tstruct object_id object, repl;\n \n \tif (get_oid(object_ref, &object))\n-\t\tdie(\"Failed to resolve '%s' as a valid ref.\", object_ref);\n+\t\treturn error(\"Failed to resolve '%s' as a valid ref.\",\n+\t\t\t     object_ref);\n \tif (get_oid(replace_ref, &repl))\n-\t\tdie(\"Failed to resolve '%s' as a valid ref.\", replace_ref);\n+\t\treturn error(\"Failed to resolve '%s' as a valid ref.\",\n+\t\t\t     replace_ref);\n \n \treturn replace_object_oid(object_ref, &object, replace_ref, &repl, force);\n }\n@@ -202,7 +210,7 @@ static int replace_object(const char *object_ref, const char *replace_ref, int f\n  * If \"raw\" is true, then the object's raw contents are printed according to\n  * \"type\". Otherwise, we pretty-print the contents for human editing.\n  */\n-static void export_object(const struct object_id *oid, enum object_type type,\n+static int export_object(const struct object_id *oid, enum object_type type,\n \t\t\t  int raw, const char *filename)\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n@@ -210,7 +218,7 @@ static void export_object(const struct object_id *oid, enum object_type type,\n \n \tfd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);\n \tif (fd < 0)\n-\t\tdie_errno(\"unable to open %s for writing\", filename);\n+\t\treturn error_errno(\"unable to open %s for writing\", filename);\n \n \targv_array_push(&cmd.args, \"--no-replace-objects\");\n \targv_array_push(&cmd.args, \"cat-file\");\n@@ -223,7 +231,8 @@ static void export_object(const struct object_id *oid, enum object_type type,\n \tcmd.out = fd;\n \n \tif (run_command(&cmd))\n-\t\tdie(\"cat-file reported failure\");\n+\t\treturn error(\"cat-file reported failure\");\n+\treturn 0;\n }\n \n /*\n@@ -231,14 +240,14 @@ static void export_object(const struct object_id *oid, enum object_type type,\n  * interpreting it as \"type\", and writing the result to the object database.\n  * The sha1 of the written object is returned via sha1.\n  */\n-static void import_object(struct object_id *oid, enum object_type type,\n+static int import_object(struct object_id *oid, enum object_type type,\n \t\t\t  int raw, const char *filename)\n {\n \tint fd;\n \n \tfd = open(filename, O_RDONLY);\n \tif (fd < 0)\n-\t\tdie_errno(\"unable to open %s for reading\", filename);\n+\t\treturn error_errno(\"unable to open %s for reading\", filename);\n \n \tif (!raw && type == OBJ_TREE) {\n \t\tconst char *argv[] = { \"mktree\", NULL };\n@@ -250,27 +259,40 @@ static void import_object(struct object_id *oid, enum object_type type,\n \t\tcmd.in = fd;\n \t\tcmd.out = -1;\n \n-\t\tif (start_command(&cmd))\n-\t\t\tdie(\"unable to spawn mktree\");\n+\t\tif (start_command(&cmd)) {\n+\t\t\tclose(fd);\n+\t\t\treturn error(\"unable to spawn mktree\");\n+\t\t}\n \n-\t\tif (strbuf_read(&result, cmd.out, 41) < 0)\n-\t\t\tdie_errno(\"unable to read from mktree\");\n+\t\tif (strbuf_read(&result, cmd.out, 41) < 0) {\n+\t\t\terror_errno(\"unable to read from mktree\");\n+\t\t\tclose(fd);\n+\t\t\tclose(cmd.out);\n+\t\t\treturn -1;\n+\t\t}\n \t\tclose(cmd.out);\n \n-\t\tif (finish_command(&cmd))\n-\t\t\tdie(\"mktree reported failure\");\n-\t\tif (get_oid_hex(result.buf, oid) < 0)\n-\t\t\tdie(\"mktree did not return an object name\");\n+\t\tif (finish_command(&cmd)) {\n+\t\t\tstrbuf_release(&result);\n+\t\t\treturn error(\"mktree reported failure\");\n+\t\t}\n+\t\tif (get_oid_hex(result.buf, oid) < 0) {\n+\t\t\tstrbuf_release(&result);\n+\t\t\treturn error(\"mktree did not return an object name\");\n+\t\t}\n \n \t\tstrbuf_release(&result);\n \t} else {\n \t\tstruct stat st;\n \t\tint flags = HASH_FORMAT_CHECK | HASH_WRITE_OBJECT;\n \n-\t\tif (fstat(fd, &st) < 0)\n-\t\t\tdie_errno(\"unable to fstat %s\", filename);\n+\t\tif (fstat(fd, &st) < 0) {\n+\t\t\terror_errno(\"unable to fstat %s\", filename);\n+\t\t\tclose(fd);\n+\t\t\treturn -1;\n+\t\t}\n \t\tif (index_fd(oid, fd, &st, type, NULL, flags) < 0)\n-\t\t\tdie(\"unable to write object to database\");\n+\t\t\treturn error(\"unable to write object to database\");\n \t\t/* index_fd close()s fd for us */\n \t}\n \n@@ -278,30 +300,43 @@ static void import_object(struct object_id *oid, enum object_type type,\n \t * No need to close(fd) here; both run-command and index-fd\n \t * will have done it for us.\n \t */\n+\treturn 0;\n }\n \n static int edit_and_replace(const char *object_ref, int force, int raw)\n {\n-\tchar *tmpfile = git_pathdup(\"REPLACE_EDITOBJ\");\n+\tchar *tmpfile;\n \tenum object_type type;\n \tstruct object_id old_oid, new_oid, prev;\n \tstruct strbuf ref = STRBUF_INIT;\n \n \tif (get_oid(object_ref, &old_oid) < 0)\n-\t\tdie(\"Not a valid object name: '%s'\", object_ref);\n+\t\treturn error(\"Not a valid object name: '%s'\", object_ref);\n \n \ttype = oid_object_info(&old_oid, NULL);\n \tif (type < 0)\n-\t\tdie(\"unable to get object type for %s\", oid_to_hex(&old_oid));\n+\t\treturn error(\"unable to get object type for %s\",\n+\t\t\t     oid_to_hex(&old_oid));\n \n-\tcheck_ref_valid(&old_oid, &prev, &ref, force);\n+\tif (check_ref_valid(&old_oid, &prev, &ref, force)) {\n+\t\tstrbuf_release(&ref);\n+\t\treturn -1;\n+\t}\n \tstrbuf_release(&ref);\n \n-\texport_object(&old_oid, type, raw, tmpfile);\n-\tif (launch_editor(tmpfile, NULL, NULL) < 0)\n-\t\tdie(\"editing object file failed\");\n-\timport_object(&new_oid, type, raw, tmpfile);\n-\n+\ttmpfile = git_pathdup(\"REPLACE_EDITOBJ\");\n+\tif (export_object(&old_oid, type, raw, tmpfile)) {\n+\t\tfree(tmpfile);\n+\t\treturn -1;\n+\t}\n+\tif (launch_editor(tmpfile, NULL, NULL) < 0) {\n+\t\tfree(tmpfile);\n+\t\treturn error(\"editing object file failed\");\n+\t}\n+\tif (import_object(&new_oid, type, raw, tmpfile)) {\n+\t\tfree(tmpfile);\n+\t\treturn -1;\n+\t}\n \tfree(tmpfile);\n \n \tif (!oidcmp(&old_oid, &new_oid))\n@@ -310,7 +345,7 @@ static int edit_and_replace(const char *object_ref, int force, int raw)\n \treturn replace_object_oid(object_ref, &old_oid, \"replacement\", &new_oid, force);\n }\n \n-static void replace_parents(struct strbuf *buf, int argc, const char **argv)\n+static int 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@@ -327,9 +362,15 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv)\n \t/* prepare new parents */\n \tfor (i = 0; i < argc; i++) {\n \t\tstruct object_id oid;\n-\t\tif (get_oid(argv[i], &oid) < 0)\n-\t\t\tdie(_(\"Not a valid object name: '%s'\"), argv[i]);\n-\t\tlookup_commit_or_die(&oid, argv[i]);\n+\t\tif (get_oid(argv[i], &oid) < 0) {\n+\t\t\tstrbuf_release(&new_parents);\n+\t\t\treturn error(_(\"Not a valid object name: '%s'\"),\n+\t\t\t\t     argv[i]);\n+\t\t}\n+\t\tif (!lookup_commit_reference(&oid)) {\n+\t\t\tstrbuf_release(&new_parents);\n+\t\t\treturn error(_(\"could not parse %s\"), argv[i]);\n+\t\t}\n \t\tstrbuf_addf(&new_parents, \"parent %s\\n\", oid_to_hex(&oid));\n \t}\n \n@@ -338,6 +379,7 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv)\n \t\t      new_parents.buf, new_parents.len);\n \n \tstrbuf_release(&new_parents);\n+\treturn 0;\n }\n \n struct check_mergetag_data {\n@@ -358,21 +400,23 @@ static int check_one_mergetag(struct commit *commit,\n \thash_object_file(extra->value, extra->len, type_name(OBJ_TAG), &tag_oid);\n \ttag = lookup_tag(&tag_oid);\n \tif (!tag)\n-\t\tdie(_(\"bad mergetag in commit '%s'\"), ref);\n+\t\treturn error(_(\"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+\t\treturn error(_(\"malformed mergetag in commit '%s'\"), ref);\n \n \t/* iterate over new parents */\n \tfor (i = 1; i < mergetag_data->argc; i++) {\n \t\tstruct object_id oid;\n \t\tif (get_oid(mergetag_data->argv[i], &oid) < 0)\n-\t\t\tdie(_(\"Not a valid object name: '%s'\"), mergetag_data->argv[i]);\n+\t\t\treturn error(_(\"Not a valid object name: '%s'\"),\n+\t\t\t\t     mergetag_data->argv[i]);\n \t\tif (!oidcmp(&tag->tagged->oid, &oid))\n \t\t\treturn 0; /* found */\n \t}\n \n-\tdie(_(\"original commit '%s' contains mergetag '%s' that is discarded; \"\n-\t      \"use --edit instead of --graft\"), ref, oid_to_hex(&tag_oid));\n+\treturn error(_(\"original commit '%s' contains mergetag '%s' that is \"\n+\t\t       \"discarded; use --edit instead of --graft\"), ref,\n+\t\t     oid_to_hex(&tag_oid));\n }\n \n static int check_mergetags(struct commit *commit, int argc, const char **argv)\n@@ -394,24 +438,35 @@ static int create_graft(int argc, const char **argv, int force)\n \tunsigned long size;\n \n \tif (get_oid(old_ref, &old_oid) < 0)\n-\t\tdie(_(\"Not a valid object name: '%s'\"), old_ref);\n-\tcommit = lookup_commit_or_die(&old_oid, old_ref);\n+\t\treturn error(_(\"Not a valid object name: '%s'\"), old_ref);\n+\tcommit = lookup_commit_reference(&old_oid);\n+\tif (!commit)\n+\t\treturn error(_(\"could not parse %s\"), 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+\tif (replace_parents(&buf, argc - 1, &argv[1]) < 0) {\n+\t\tstrbuf_release(&buf);\n+\t\treturn -1;\n+\t}\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-\tcheck_mergetags(commit, argc, argv);\n+\tif (check_mergetags(commit, argc, argv)) {\n+\t\tstrbuf_release(&buf);\n+\t\treturn -1;\n+\t}\n \n-\tif (write_object_file(buf.buf, buf.len, commit_type, &new_oid))\n-\t\tdie(_(\"could not write replacement commit for: '%s'\"), old_ref);\n+\tif (write_object_file(buf.buf, buf.len, commit_type, &new_oid)) {\n+\t\tstrbuf_release(&buf);\n+\t\treturn error(_(\"could not write replacement commit for: '%s'\"),\n+\t\t\t     old_ref);\n+\t}\n \n \tstrbuf_release(&buf);\n \n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"346002","messageId":"66157d25f78f596d87c8b9eb1e568c896411e764.1524865158.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524865158.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 05/11] replace: introduce --convert-graft-file","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-27T21:39:45Z","receivedAt":"2018-04-27T21:39:54Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This option is intended to help with the transition away from the\nnow-deprecated graft file.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-replace.txt | 11 ++++++---\n builtin/replace.c             | 44 ++++++++++++++++++++++++++++++++++-\n 2 files changed, 51 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-replace.txt b/Documentation/git-replace.txt\nindex e5c57ae6ef4..246dc9943c2 100644\n--- a/Documentation/git-replace.txt\n+++ b/Documentation/git-replace.txt\n@@ -11,6 +11,7 @@ SYNOPSIS\n 'git replace' [-f] <object> <replacement>\n 'git replace' [-f] --edit <object>\n 'git replace' [-f] --graft <commit> [<parent>...]\n+'git replace' [-f] --convert-graft-file\n 'git replace' -d <object>...\n 'git replace' [--format=<format>] [-l [<pattern>]]\n \n@@ -87,9 +88,13 @@ 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. 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+\tcommit. Use `--convert-graft-file` to convert a\n+\t`$GIT_DIR/info/grafts` file and use replace refs instead.\n+\n+--convert-graft-file::\n+\tCreates graft commits for all entries in `$GIT_DIR/info/grafts`\n+\tand deletes that file upon success. The purpose is to help users\n+\twith transitioning off of the now-deprecated graft file.\n \n -l <pattern>::\n --list <pattern>::\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex e57d3d187ed..35394ec1874 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -20,6 +20,7 @@ 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 [-f] --convert-graft-file\"),\n \tN_(\"git replace -d <object>...\"),\n \tN_(\"git replace [--format=<format>] [-l [<pattern>]]\"),\n \tNULL\n@@ -476,6 +477,38 @@ static int create_graft(int argc, const char **argv, int force)\n \treturn replace_object_oid(old_ref, &old_oid, \"replacement\", &new_oid, force);\n }\n \n+static int convert_graft_file(int force)\n+{\n+\tconst char *graft_file = get_graft_file();\n+\tFILE *fp = fopen_or_warn(graft_file, \"r\");\n+\tstruct strbuf buf = STRBUF_INIT, err = STRBUF_INIT;\n+\tstruct argv_array args = ARGV_ARRAY_INIT;\n+\n+\tif (!fp)\n+\t\treturn -1;\n+\n+\twhile (strbuf_getline(&buf, fp) != EOF) {\n+\t\tif (*buf.buf == '#')\n+\t\t\tcontinue;\n+\n+\t\targv_array_split(&args, buf.buf);\n+\t\tif (args.argc && create_graft(args.argc, args.argv, force))\n+\t\t\tstrbuf_addf(&err, \"\\n\\t%s\", buf.buf);\n+\t\targv_array_clear(&args);\n+\t}\n+\tfclose(fp);\n+\n+\tstrbuf_release(&buf);\n+\n+\tif (!err.len)\n+\t\treturn unlink_or_warn(graft_file);\n+\n+\twarning(_(\"could not convert the following graft(s):\\n%s\"), err.buf);\n+\tstrbuf_release(&err);\n+\n+\treturn -1;\n+}\n+\n int cmd_replace(int argc, const char **argv, const char *prefix)\n {\n \tint force = 0;\n@@ -487,6 +520,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n \t\tMODE_DELETE,\n \t\tMODE_EDIT,\n \t\tMODE_GRAFT,\n+\t\tMODE_CONVERT_GRAFT_FILE,\n \t\tMODE_REPLACE\n \t} cmdmode = MODE_UNSPECIFIED;\n \tstruct option options[] = {\n@@ -494,6 +528,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\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_CMDMODE(0, \"convert-graft-file\", &cmdmode, N_(\"convert existing graft file\"), MODE_CONVERT_GRAFT_FILE),\n \t\tOPT_BOOL_F('f', \"force\", &force, N_(\"replace the ref if it exists\"),\n \t\t\t   PARSE_OPT_NOCOMPLETE),\n \t\tOPT_BOOL(0, \"raw\", &raw, N_(\"do not pretty-print contents for --edit\")),\n@@ -516,7 +551,8 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n \tif (force &&\n \t    cmdmode != MODE_REPLACE &&\n \t    cmdmode != MODE_EDIT &&\n-\t    cmdmode != MODE_GRAFT)\n+\t    cmdmode != MODE_GRAFT &&\n+\t    cmdmode != MODE_CONVERT_GRAFT_FILE)\n \t\tusage_msg_opt(\"-f only makes sense when writing a replacement\",\n \t\t\t      git_replace_usage, options);\n \n@@ -549,6 +585,12 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n \t\t\t\t      git_replace_usage, options);\n \t\treturn create_graft(argc, argv, force);\n \n+\tcase MODE_CONVERT_GRAFT_FILE:\n+\t\tif (argc != 0)\n+\t\t\tusage_msg_opt(\"--convert-graft-file takes no argument\",\n+\t\t\t\t      git_replace_usage, options);\n+\t\treturn !!convert_graft_file(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.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"346003","messageId":"1a8c25baec240b0242873aedff8295cee7ba9742.1524865158.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524865158.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 06/11] Add a test for `git replace --convert-graft-file`","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-27T21:39:46Z","receivedAt":"2018-04-27T21:40:01Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The proof, as the saying goes, lies in the pudding. So here is a\nregression test that not only demonstrates what the option is supposed to\naccomplish, but also demonstrates that it does accomplish it.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/t6050-replace.sh | 20 ++++++++++++++++++++\n 1 file changed, 20 insertions(+)\n\ndiff --git a/t/t6050-replace.sh b/t/t6050-replace.sh\nindex c630aba657e..bed86a0af3d 100755\n--- a/t/t6050-replace.sh\n+++ b/t/t6050-replace.sh\n@@ -444,4 +444,24 @@ test_expect_success GPG '--graft on a commit with a mergetag' '\n \tgit replace -d $HASH10\n '\n \n+test_expect_success '--convert-graft-file' '\n+\t: add and convert graft file &&\n+\tprintf \"%s\\n%s %s\\n\\n# comment\\n%s\\n\" \\\n+\t\t$(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \\\n+\t\t>.git/info/grafts &&\n+\tgit replace --convert-graft-file &&\n+\ttest_path_is_missing .git/info/grafts &&\n+\n+\t: verify that the history is now \"grafted\" &&\n+\tgit rev-list HEAD >out &&\n+\ttest_line_count = 4 out &&\n+\n+\t: create invalid graft file and verify that it is not deleted &&\n+\ttest_when_finished \"rm -f .git/info/grafts\" &&\n+\techo $EMPTY_BLOB $EMPTY_TREE >.git/info/grafts &&\n+\ttest_must_fail git replace --convert-graft-file 2>err &&\n+\ttest_i18ngrep \"$EMPTY_BLOB $EMPTY_TREE\" err &&\n+\ttest_i18ngrep \"$EMPTY_BLOB $EMPTY_TREE\" .git/info/grafts\n+'\n+\n test_done\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"346004","messageId":"36532fa9f3812839e27e3ac74d54f4fc5e5a38e4.1524865158.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524865158.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 07/11] Deprecate support for .git/info/grafts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-27T21:39:54Z","receivedAt":"2018-04-27T21:40:06Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The grafts feature was a convenient way to \"stitch together\" ancient\nhistory to the fresh start of linux.git.\n\nIts implementation is, however, not up to Git's standards, as there are\ntoo many ways where it can lead to surprising and unwelcome behavior.\n\nFor example, when pushing from a repository with active grafts, it is\npossible to miss commits that have been \"grafted out\", resulting in a\nbroken state on the other side.\n\nAlso, the grafts feature is limited to \"rewriting\" commits' list of\nparents, it cannot replace anything else.\n\nThe much younger feature implemented as `git replace` set out to remedy\nthose limitations and dangerous bugs.\n\nSeeing as `git replace` is pretty mature by now (since 4228e8bc98\n(replace: add --graft option, 2014-07-19) it can perform the graft\nfile's duties), it is time to deprecate support for the graft file, and\nto retire it eventually.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nReviewed-by: Stefan Beller <sbeller@google.com>\n---\n advice.c                  |  2 ++\n advice.h                  |  1 +\n commit.c                  | 10 ++++++++++\n t/t6001-rev-list-graft.sh |  9 +++++++++\n 4 files changed, 22 insertions(+)\n\ndiff --git a/advice.c b/advice.c\nindex 406efc183ba..4411704fd45 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -19,6 +19,7 @@ int advice_rm_hints = 1;\n int advice_add_embedded_repo = 1;\n int advice_ignored_hook = 1;\n int advice_waiting_for_editor = 1;\n+int advice_graft_file_deprecated = 1;\n \n static struct {\n \tconst char *name;\n@@ -42,6 +43,7 @@ static struct {\n \t{ \"addembeddedrepo\", &advice_add_embedded_repo },\n \t{ \"ignoredhook\", &advice_ignored_hook },\n \t{ \"waitingforeditor\", &advice_waiting_for_editor },\n+\t{ \"graftfiledeprecated\", &advice_graft_file_deprecated },\n \n \t/* make this an alias for backward compatibility */\n \t{ \"pushnonfastforward\", &advice_push_update_rejected }\ndiff --git a/advice.h b/advice.h\nindex 70568fa7922..9f5064e82a8 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -21,6 +21,7 @@ extern int advice_rm_hints;\n extern int advice_add_embedded_repo;\n extern int advice_ignored_hook;\n extern int advice_waiting_for_editor;\n+extern int advice_graft_file_deprecated;\n \n int git_default_advice_config(const char *var, const char *value);\n __attribute__((format (printf, 1, 2)))\ndiff --git a/commit.c b/commit.c\nindex 2952ec987c5..451d3ce8dfe 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -12,6 +12,7 @@\n #include \"prio-queue.h\"\n #include \"sha1-lookup.h\"\n #include \"wt-status.h\"\n+#include \"advice.h\"\n \n static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);\n \n@@ -176,6 +177,15 @@ static int read_graft_file(const char *graft_file)\n \tstruct strbuf buf = STRBUF_INIT;\n \tif (!fp)\n \t\treturn -1;\n+\tif (advice_graft_file_deprecated)\n+\t\tadvise(_(\"Support for <GIT_DIR>/info/grafts is deprecated\\n\"\n+\t\t\t \"and will be removed in a future Git version.\\n\"\n+\t\t\t \"\\n\"\n+\t\t\t \"Please use \\\"git replace --convert-graft-file\\\"\\n\"\n+\t\t\t \"to convert the grafts into replace refs.\\n\"\n+\t\t\t \"\\n\"\n+\t\t\t \"Turn this message off by running\\n\"\n+\t\t\t \"\\\"git config advice.graftFileDeprecated false\\\"\"));\n \twhile (!strbuf_getwholeline(&buf, fp, '\\n')) {\n \t\t/* The format is just \"Commit Parent1 Parent2 ...\\n\" */\n \t\tstruct commit_graft *graft = read_graft_line(&buf);\ndiff --git a/t/t6001-rev-list-graft.sh b/t/t6001-rev-list-graft.sh\nindex 05ddc69cf2a..7504ba47511 100755\n--- a/t/t6001-rev-list-graft.sh\n+++ b/t/t6001-rev-list-graft.sh\n@@ -110,4 +110,13 @@ do\n \t\"\n \n done\n+\n+test_expect_success 'show advice that grafts are deprecated' '\n+\tgit show HEAD 2>err &&\n+\ttest_i18ngrep \"git replace\" err &&\n+\ttest_config advice.graftFileDeprecated false &&\n+\tgit show HEAD 2>err &&\n+\ttest_i18ngrep ! \"git replace\" err\n+'\n+\n test_done\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"346005","messageId":"800556a8d34896b7fd8876cda90846ed5b994d02.1524865158.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524865158.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 08/11] filter-branch: stop suggesting to use grafts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-27T21:39:57Z","receivedAt":"2018-04-27T21:40:15Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The graft file is deprecated now, so let's use replace refs in the example\nin filter-branch's man page instead.\n\nSuggested-by: Eric Sunshine <sunshine@sunshineco.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-filter-branch.txt | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt\nindex b634043183b..1d4d2f86045 100644\n--- a/Documentation/git-filter-branch.txt\n+++ b/Documentation/git-filter-branch.txt\n@@ -288,7 +288,7 @@ git filter-branch --parent-filter \\\n or even simpler:\n \n -----------------------------------------------\n-echo \"$commit-id $graft-id\" >> .git/info/grafts\n+git replace --graft $commit-id $graft-id\n git filter-branch $graft-id..HEAD\n -----------------------------------------------\n \n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"346006","messageId":"7feaf8466df7f6bfade72d02c756f827435a3c8d.1524865158.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524865158.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 09/11] technical/shallow: stop referring to grafts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-27T21:40:09Z","receivedAt":"2018-04-27T21:40:33Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Now that grafts are deprecated, we should start to assume that readers\nhave no idea what grafts are. So it makes more sense to make the\ndescription of the \"shallow\" feature stand on its own.\n\nSuggested-by: Eric Sunshine <sunshine@sunshineco.com>\nHelped-by: Junio Hamano <gitster@pobox.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/technical/shallow.txt | 13 ++++---------\n 1 file changed, 4 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/technical/shallow.txt b/Documentation/technical/shallow.txt\nindex 5183b154229..4ec721335d2 100644\n--- a/Documentation/technical/shallow.txt\n+++ b/Documentation/technical/shallow.txt\n@@ -8,15 +8,10 @@ repo, and therefore grafts are introduced pretending that\n these commits have no parents.\n *********************************************************\n \n-The basic idea is to write the SHA-1s of shallow commits into\n-$GIT_DIR/shallow, and handle its contents like the contents\n-of $GIT_DIR/info/grafts (with the difference that shallow\n-cannot contain parent information).\n-\n-This information is stored in a new file instead of grafts, or\n-even the config, since the user should not touch that file\n-at all (even throughout development of the shallow clone, it\n-was never manually edited!).\n+$GIT_DIR/shallow lists commit object names and tells Git to\n+pretend as if they are root commits (e.g. \"git log\" traversal\n+stops after showing them; \"git fsck\" does not complain saying\n+the commits listed on their \"parent\" lines do not exist).\n \n Each line contains exactly one SHA-1. When read, a commit_graft\n will be constructed, which has nr_parent < 0 to make it easier\n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"346007","messageId":"2152d161b3a85ad90c151ed848d668d1e471b2b4.1524865158.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524865158.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 10/11] technical/shallow: describe why shallow cannot use replace refs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-27T21:40:23Z","receivedAt":"2018-04-27T21:41:01Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"It is tempting to do away with commit_graft altogether (in the long\nhaul), now that grafts are deprecated.\n\nHowever, the shallow feature needs a couple of things that the replace\nrefs cannot fulfill. Let's point that out in the documentation.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/technical/shallow.txt | 7 +++++++\n 1 file changed, 7 insertions(+)\n\ndiff --git a/Documentation/technical/shallow.txt b/Documentation/technical/shallow.txt\nindex 4ec721335d2..01dedfe9ffe 100644\n--- a/Documentation/technical/shallow.txt\n+++ b/Documentation/technical/shallow.txt\n@@ -17,6 +17,13 @@ Each line contains exactly one SHA-1. When read, a commit_graft\n will be constructed, which has nr_parent < 0 to make it easier\n to discern from user provided grafts.\n \n+Note that the shallow feature could not be changed easily to\n+use replace refs: a commit containing a `mergetag` is not allowed\n+to be replaced, not even by a root commit. Such a commit can be\n+made shallow, though. Also, having a `shallow` file explicitly\n+listing all the commits made shallow makes it a *lot* easier to\n+do shallow-specific things such as to deepen the history.\n+\n Since fsck-objects relies on the library to read the objects,\n it honours shallow commits automatically.\n \n-- \n2.17.0.windows.1.33.gfcbb1fa0445\n\n\n"},{"id":"346008","messageId":"b321979f88589e7b006466159c470800db948d66.1524865158.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524865158.git.johannes.schindelin@gmx.de","subject":"[PATCH v6 11/11] Remove obsolete script to convert grafts to replace refs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-27T21:40:54Z","receivedAt":"2018-04-27T21:41:07Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The functionality is now implemented as `git replace\n--convert-graft-file`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n contrib/convert-grafts-to-replace-refs.sh | 28 -----------------------\n 1 file changed, 28 deletions(-)\n delete mode 100755 contrib/convert-grafts-to-replace-refs.sh\n\ndiff --git a/contrib/convert-grafts-to-replace-refs.sh b/contrib/convert-grafts-to-replace-refs.sh\ndeleted file mode 100755\nindex 0cbc917b8cf..00000000000\n--- a/contrib/convert-grafts-to-replace-refs.sh\n+++ /dev/null\n@@ -1,28 +0,0 @@\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.17.0.windows.1.33.gfcbb1fa0445\n"},{"id":"346019","messageId":"20180428012505.32734-1-szeder.dev@gmail.com","threadId":"48278","inReplyTo":"1a8c25baec240b0242873aedff8295cee7ba9742.1524865158.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v6 06/11] Add a test for `git replace --convert-graft-file`","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2018-04-28T01:25:05Z","receivedAt":"2018-04-28T01:25:18Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"Hallo Johannes,\n\n> diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh\n> index c630aba657e..bed86a0af3d 100755\n> --- a/t/t6050-replace.sh\n> +++ b/t/t6050-replace.sh\n> @@ -444,4 +444,24 @@ test_expect_success GPG '--graft on a commit with a mergetag' '\n\nNote the GPG prereq of the previous test.\n\n>  \tgit replace -d $HASH10\n>  '\n>  \n> +test_expect_success '--convert-graft-file' '\n> +\t: add and convert graft file &&\n> +\tprintf \"%s\\n%s %s\\n\\n# comment\\n%s\\n\" \\\n> +\t\t$(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \\\n\nThis test relies on the piece of history that was created two tests\nearlier in 'set up a merge commit with a mergetag', and that test, too,\nhas the GPG prereq.  So if there is no GPG installed, then that test\nwon't be executed, the history needed by this test won't be created,\nHEAD won't have a second parent, and I'm sure you know where this is\ngoing:\n\n  ok 32 # skip set up a merge commit with a mergetag (missing GPG)\n  <... snip ...>\n  ok 33 # skip --graft on a commit with a mergetag (missing GPG)\n  <... snip ...>\n  ++git log --graph --oneline\n  * ffccc9d hello: again 3 more lines\n  * 14ac020 hello: 2 more lines\n  * 093e41a hello: BUG fixed\n  * 40237c8 hello: 1 more line\n  * 8fc2a8e hello: 2 more lines\n  * 4217adb hello: 4 more lines with a BUG\n  * 00ad688 hello: 4 lines\n  ++: add and convert graft file\n  +++git rev-parse 'HEAD^^' 'HEAD^' 'HEAD^^' 'HEAD^2'\n  fatal: ambiguous argument 'HEAD^2': unknown revision or path not in the working tree.\n  Use '--' to separate paths from revisions, like this:\n  'git <command> [<revision>...] -- [<file>...]'\n  ++printf '%s\\n%s %s\\n\\n# comment\\n%s\\n' 093e41a79d4a8bfe3c758a96c95e5bf9e35ed89c 14ac020163ea60a9d683ce68e36c946f31ecc856 093e41a79d4a8bfe3c758a96c95e5bf9e35ed89c 'HEAD^2'\n  ++cat .git/info/grafts\n  093e41a79d4a8bfe3c758a96c95e5bf9e35ed89c\n  14ac020163ea60a9d683ce68e36c946f31ecc856 093e41a79d4a8bfe3c758a96c95e5bf9e35ed89c\n  # comment\n  HEAD^2\n  ++git replace --convert-graft-file\n  hint: Support for <GIT_DIR>/info/grafts is deprecated\n  hint: and will be removed in a future Git version.\n  hint: \n  hint: Please use \"git replace --convert-graft-file\"\n  hint: to convert the grafts into replace refs.\n  hint: \n  hint: Turn this message off by running\n  hint: \"git config advice.graftFileDeprecated false\"\n  error: bad graft data: HEAD^2\n  error: new commit is the same as the old one: '14ac020163ea60a9d683ce68e36c946f31ecc856'\n  error: Not a valid object name: 'HEAD^2'\n  warning: could not convert the following graft(s):\n          14ac020163ea60a9d683ce68e36c946f31ecc856 093e41a79d4a8bfe3c758a96c95e5bf9e35ed89c\n          HEAD^2\n  error: last command exited with $?=1\n  not ok 34 - --convert-graft-file\n\n\n> +\t\t>.git/info/grafts &&\n> +\tgit replace --convert-graft-file &&\n> +\ttest_path_is_missing .git/info/grafts &&\n> +\n> +\t: verify that the history is now \"grafted\" &&\n> +\tgit rev-list HEAD >out &&\n> +\ttest_line_count = 4 out &&\n> +\n> +\t: create invalid graft file and verify that it is not deleted &&\n> +\ttest_when_finished \"rm -f .git/info/grafts\" &&\n> +\techo $EMPTY_BLOB $EMPTY_TREE >.git/info/grafts &&\n> +\ttest_must_fail git replace --convert-graft-file 2>err &&\n> +\ttest_i18ngrep \"$EMPTY_BLOB $EMPTY_TREE\" err &&\n> +\ttest_i18ngrep \"$EMPTY_BLOB $EMPTY_TREE\" .git/info/grafts\n> +'\n> +\n>  test_done\n> -- \n> 2.17.0.windows.1.33.gfcbb1fa0445\n"},{"id":"346027","messageId":"A37AE6B64CF7414ABE41697663B2BEC4@PhilipOakley","threadId":"48278","inReplyTo":"b321979f88589e7b006466159c470800db948d66.1524865158.git.johannes.schindelin@gmx.de","subject":"Re: [PATCH v6 11/11] Remove obsolete script to convert grafts to replace refs","fromName":"Philip Oakley","fromEmail":"philipoakley@iee.org","sentAt":"2018-04-28T09:04:46Z","receivedAt":"2018-04-28T09:04:59Z","isPatch":true,"sender":{"key":"philipoakley@iee.email","avatar":"https://avatars.githubusercontent.com/u/914343?v=4"},"body":"From: \"Johannes Schindelin\" <johannes.schindelin@gmx.de>\n> The functionality is now implemented as `git replace\n> --convert-graft-file`.\n\nA rather late in the day thought: Should this go through the same\ndeprecation dance?\n\nI.e. replace the body of the script with the new `git\nreplace --convert-graft-file` and echo (or die!) a warning message that this\nscript is now deprecated and will be removed?\n\nAt least it will catch those who arrive via random web advice!\n\n--\nPhilip\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n> contrib/convert-grafts-to-replace-refs.sh | 28 -----------------------\n> 1 file changed, 28 deletions(-)\n> delete mode 100755 contrib/convert-grafts-to-replace-refs.sh\n>\n> diff --git a/contrib/convert-grafts-to-replace-refs.sh\n> b/contrib/convert-grafts-to-replace-refs.sh\n> deleted file mode 100755\n> index 0cbc917b8cf..00000000000\n> --- a/contrib/convert-grafts-to-replace-refs.sh\n> +++ /dev/null\n> @@ -1,28 +0,0 @@\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> - if test -n \"$definition\"\n> - then\n> - echo \"Converting: $definition\"\n> - git replace --graft $definition ||\n> - die \"Conversion failed for: $definition\"\n> - fi\n> -done\n> -\n> -mv \"$GRAFTS_FILE\" \"$GRAFTS_FILE.bak\" ||\n> - die \"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\n> refs!\"\n> -echo \"The grafts file '$GRAFTS_FILE' has been renamed:\n> '$GRAFTS_FILE.bak'\"\n> -- \n> 2.17.0.windows.1.33.gfcbb1fa0445\n>\n\n"},{"id":"346037","messageId":"nycvar.QRO.7.76.6.1804281506220.79@tvgsbejvaqbjf.bet","threadId":"48278","inReplyTo":"20180428012505.32734-1-szeder.dev@gmail.com","subject":"Re: [PATCH v6 06/11] Add a test for `git replace --convert-graft-file`","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-28T13:07:12Z","receivedAt":"2018-04-28T13:07:23Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Gábor,\n\nOn Sat, 28 Apr 2018, SZEDER Gábor wrote:\n\n> > diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh\n> > index c630aba657e..bed86a0af3d 100755\n> > --- a/t/t6050-replace.sh\n> > +++ b/t/t6050-replace.sh\n> > @@ -444,4 +444,24 @@ test_expect_success GPG '--graft on a commit with a mergetag' '\n> \n> Note the GPG prereq of the previous test.\n>\n> [... demonstrates how the new test fails without the GPG prereq ...]\n\nOuch. You're right. Will fix,\nDscho"},{"id":"346038","messageId":"nycvar.QRO.7.76.6.1804281513140.79@tvgsbejvaqbjf.bet","threadId":"48278","inReplyTo":"A37AE6B64CF7414ABE41697663B2BEC4@PhilipOakley","subject":"Re: [PATCH v6 11/11] Remove obsolete script to convert grafts to replace refs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-28T13:13:37Z","receivedAt":"2018-04-28T13:13:52Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Philip,\n\nOn Sat, 28 Apr 2018, Philip Oakley wrote:\n\n> From: \"Johannes Schindelin\" <johannes.schindelin@gmx.de>\n> > The functionality is now implemented as `git replace\n> > --convert-graft-file`.\n> \n> A rather late in the day thought: Should this go through the same\n> deprecation dance?\n> \n> I.e. replace the body of the script with the new `git\n> replace --convert-graft-file` and echo (or die!) a warning message that this\n> script is now deprecated and will be removed?\n> \n> At least it will catch those who arrive via random web advice!\n\nOriginally, I did not even want to remove it... I just did as I was told\nby Junio...\n\nCiao,\nDscho\n"},{"id":"346041","messageId":"CAGZ79kb3+TvdHhPkb-PuZFX1RzUq3O2NNVZqa3r5BmOmOXaHdw@mail.gmail.com","threadId":"48278","inReplyTo":"A37AE6B64CF7414ABE41697663B2BEC4@PhilipOakley","subject":"Re: [PATCH v6 11/11] Remove obsolete script to convert grafts to replace refs","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2018-04-28T15:00:43Z","receivedAt":"2018-04-28T15:00:48Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"Hi Philip,\n\nOn Sat, Apr 28, 2018 at 2:04 AM, Philip Oakley <philipoakley@iee.org> wrote:\n> From: \"Johannes Schindelin\" <johannes.schindelin@gmx.de>\n>>\n>> The functionality is now implemented as `git replace\n>> --convert-graft-file`.\n>\n>\n> A rather late in the day thought: Should this go through the same\n> deprecation dance?\n\nI think contrib/ has other rules than the core\n(c.f. `git log --oneline --no-merges --grep \"contrib: remove\"`)\nwhich is why I'd consider the deprecation dance unneeded.\n\n> At least it will catch those who arrive via random web advice!\n\nGood point, unlike the other removals, this is not a dormant\nproject.\n\nI would still think it is ok to remove it. Those who are looking\nfor this script, surely will look at the man page of git-replace\nor are looking for the grafts. `man gitglossary` is already\nsufficient:\n\ngrafts\n           Grafts enables [...]\n\n           Note that the grafts mechanism is outdated and can\n           lead to problems transferring objects between\n           repositories; see git-replace(1) for a more flexible\n           and robust system to do the same thing.\n"},{"id":"346056","messageId":"178058d887281fc87e5008367bbee5677a619320.1524955439.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524955439.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 01/12] argv_array: offer to split a string by whitespace","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-28T22:44:07Z","receivedAt":"2018-04-28T22:44:27Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This is a simple function that will interpret a string as a whitespace\ndelimited list of values, and add those values into the array.\n\nNote: this function does not (yet) offer to split by arbitrary delimiters,\nor keep empty values in case of runs of whitespace, or de-quote Unix shell\nstyle. All fo this functionality can be added later, when and if needed.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n argv-array.c | 20 ++++++++++++++++++++\n argv-array.h |  2 ++\n 2 files changed, 22 insertions(+)\n\ndiff --git a/argv-array.c b/argv-array.c\nindex 5d370fa3366..cb5bcd2c064 100644\n--- a/argv-array.c\n+++ b/argv-array.c\n@@ -64,6 +64,26 @@ void argv_array_pop(struct argv_array *array)\n \tarray->argc--;\n }\n \n+void argv_array_split(struct argv_array *array, const char *to_split)\n+{\n+\twhile (isspace(*to_split))\n+\t\tto_split++;\n+\tfor (;;) {\n+\t\tconst char *p = to_split;\n+\n+\t\tif (!*p)\n+\t\t\tbreak;\n+\n+\t\twhile (*p && !isspace(*p))\n+\t\t\tp++;\n+\t\targv_array_push_nodup(array, xstrndup(to_split, p - to_split));\n+\n+\t\twhile (isspace(*p))\n+\t\t\tp++;\n+\t\tto_split = p;\n+\t}\n+}\n+\n void argv_array_clear(struct argv_array *array)\n {\n \tif (array->argv != empty_argv) {\ndiff --git a/argv-array.h b/argv-array.h\nindex 29056e49a12..750c30d2f2c 100644\n--- a/argv-array.h\n+++ b/argv-array.h\n@@ -19,6 +19,8 @@ LAST_ARG_MUST_BE_NULL\n void argv_array_pushl(struct argv_array *, ...);\n void argv_array_pushv(struct argv_array *, const char **);\n void argv_array_pop(struct argv_array *);\n+/* Splits by whitespace; does not handle quoted arguments! */\n+void argv_array_split(struct argv_array *, const char *);\n void argv_array_clear(struct argv_array *);\n const char **argv_array_detach(struct argv_array *);\n \n-- \n2.17.0.windows.1.36.gdf4ca5fb72a\n\n\n"},{"id":"346057","messageId":"97428f56890413d16f1487309972c446b61f784f.1524955439.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524955439.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 02/12] commit: Let the callback of for_each_mergetag return on error","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-28T22:44:17Z","receivedAt":"2018-04-28T22:44:36Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This is yet another patch to be filed under the keyword \"libification\".\n\nThere is one subtle change in behavior here, where a `git log` that has\nbeen asked to show the mergetags would now stop reporting the mergetags\nupon the first failure, whereas previously, it would have continued to the\nnext mergetag, if any.\n\nIn practice, that change should not matter, as it is 1) uncommon to\nperform octopus merges using multiple tags as merge heads, and 2) when the\nuser asks to be shown those tags, they really should be there.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/replace.c |  8 ++++----\n commit.c          |  8 +++++---\n commit.h          |  4 ++--\n log-tree.c        | 13 +++++++------\n 4 files changed, 18 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 935647be6bd..245d3f4164e 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -345,7 +345,7 @@ struct check_mergetag_data {\n \tconst char **argv;\n };\n \n-static void check_one_mergetag(struct commit *commit,\n+static int check_one_mergetag(struct commit *commit,\n \t\t\t       struct commit_extra_header *extra,\n \t\t\t       void *data)\n {\n@@ -368,20 +368,20 @@ static void check_one_mergetag(struct commit *commit,\n \t\tif (get_oid(mergetag_data->argv[i], &oid) < 0)\n \t\t\tdie(_(\"Not a valid object name: '%s'\"), mergetag_data->argv[i]);\n \t\tif (!oidcmp(&tag->tagged->oid, &oid))\n-\t\t\treturn; /* found */\n+\t\t\treturn 0; /* found */\n \t}\n \n \tdie(_(\"original commit '%s' contains mergetag '%s' that is discarded; \"\n \t      \"use --edit instead of --graft\"), ref, oid_to_hex(&tag_oid));\n }\n \n-static void check_mergetags(struct commit *commit, int argc, const char **argv)\n+static int 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+\treturn for_each_mergetag(check_one_mergetag, commit, &mergetag_data);\n }\n \n static int create_graft(int argc, const char **argv, int force)\ndiff --git a/commit.c b/commit.c\nindex ca474a7c112..2952ec987c5 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -1288,17 +1288,19 @@ struct commit_extra_header *read_commit_extra_headers(struct commit *commit,\n \treturn extra;\n }\n \n-void for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data)\n+int for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data)\n {\n \tstruct commit_extra_header *extra, *to_free;\n+\tint res = 0;\n \n \tto_free = read_commit_extra_headers(commit, NULL);\n-\tfor (extra = to_free; extra; extra = extra->next) {\n+\tfor (extra = to_free; !res && extra; extra = extra->next) {\n \t\tif (strcmp(extra->key, \"mergetag\"))\n \t\t\tcontinue; /* not a merge tag */\n-\t\tfn(commit, extra, data);\n+\t\tres = fn(commit, extra, data);\n \t}\n \tfree_commit_extra_headers(to_free);\n+\treturn res;\n }\n \n static inline int standard_header_field(const char *field, size_t len)\ndiff --git a/commit.h b/commit.h\nindex 0fb8271665c..9000895ad91 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -291,10 +291,10 @@ extern const char *find_commit_header(const char *msg, const char *key,\n /* Find the end of the log message, the right place for a new trailer. */\n extern int ignore_non_trailer(const char *buf, size_t len);\n \n-typedef void (*each_mergetag_fn)(struct commit *commit, struct commit_extra_header *extra,\n+typedef int (*each_mergetag_fn)(struct commit *commit, struct commit_extra_header *extra,\n \t\t\t\t void *cb_data);\n \n-extern void for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data);\n+extern int for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data);\n \n struct merge_remote_desc {\n \tstruct object *obj; /* the named object, could be a tag */\ndiff --git a/log-tree.c b/log-tree.c\nindex d1c0bedf244..f3a51a6e726 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -488,9 +488,9 @@ static int is_common_merge(const struct commit *commit)\n \t\t&& !commit->parents->next->next);\n }\n \n-static void show_one_mergetag(struct commit *commit,\n-\t\t\t      struct commit_extra_header *extra,\n-\t\t\t      void *data)\n+static int show_one_mergetag(struct commit *commit,\n+\t\t\t     struct commit_extra_header *extra,\n+\t\t\t     void *data)\n {\n \tstruct rev_info *opt = (struct rev_info *)data;\n \tstruct object_id oid;\n@@ -502,7 +502,7 @@ static void show_one_mergetag(struct commit *commit,\n \thash_object_file(extra->value, extra->len, type_name(OBJ_TAG), &oid);\n \ttag = lookup_tag(&oid);\n \tif (!tag)\n-\t\treturn; /* error message already given */\n+\t\treturn -1; /* error message already given */\n \n \tstrbuf_init(&verify_message, 256);\n \tif (parse_tag_buffer(tag, extra->value, extra->len))\n@@ -536,11 +536,12 @@ static void show_one_mergetag(struct commit *commit,\n \n \tshow_sig_lines(opt, status, verify_message.buf);\n \tstrbuf_release(&verify_message);\n+\treturn 0;\n }\n \n-static void show_mergetag(struct rev_info *opt, struct commit *commit)\n+static int show_mergetag(struct rev_info *opt, struct commit *commit)\n {\n-\tfor_each_mergetag(show_one_mergetag, commit, opt);\n+\treturn for_each_mergetag(show_one_mergetag, commit, opt);\n }\n \n void show_log(struct rev_info *opt)\n-- \n2.17.0.windows.1.36.gdf4ca5fb72a\n\n\n"},{"id":"346058","messageId":"05642a8fb58086c9cc638fec1b4e61ffa8cc7506.1524955439.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524955439.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 03/12] replace: avoid using die() to indicate a bug","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-28T22:44:24Z","receivedAt":"2018-04-28T22:44:36Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"We have the BUG() macro for that purpose.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/replace.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 245d3f4164e..e345a5a0f1c 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -501,6 +501,6 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n \t\treturn list_replace_refs(argv[0], format);\n \n \tdefault:\n-\t\tdie(\"BUG: invalid cmdmode %d\", (int)cmdmode);\n+\t\tBUG(\"invalid cmdmode %d\", (int)cmdmode);\n \t}\n }\n-- \n2.17.0.windows.1.36.gdf4ca5fb72a\n\n\n"},{"id":"346059","messageId":"f2f490b41529327b8fb4214ef4f663b21ee43ed8.1524955439.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524955439.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 04/12] replace: \"libify\" create_graft() and callees","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-28T22:44:26Z","receivedAt":"2018-04-28T22:44:41Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"File this away as yet another patch in the \"libification\" category.\n\nAs with all useful functions, in the next commit we want to use\ncreate_graft() from a higher-level function where it would be\ninconvenient if the called function simply die()s: if there is a\nproblem, we want to let the user know how to proceed, and the callee\nsimply has no way of knowing what to say.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/replace.c | 169 ++++++++++++++++++++++++++++++----------------\n 1 file changed, 112 insertions(+), 57 deletions(-)\n\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex e345a5a0f1c..e57d3d187ed 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -79,9 +79,9 @@ static int list_replace_refs(const char *pattern, const char *format)\n \telse if (!strcmp(format, \"long\"))\n \t\tdata.format = REPLACE_FORMAT_LONG;\n \telse\n-\t\tdie(\"invalid replace format '%s'\\n\"\n-\t\t    \"valid formats are 'short', 'medium' and 'long'\\n\",\n-\t\t    format);\n+\t\treturn error(\"invalid replace format '%s'\\n\"\n+\t\t\t     \"valid formats are 'short', 'medium' and 'long'\\n\",\n+\t\t\t     format);\n \n \tfor_each_replace_ref(show_reference, (void *)&data);\n \n@@ -134,7 +134,7 @@ static int delete_replace_ref(const char *name, const char *ref,\n \treturn 0;\n }\n \n-static void check_ref_valid(struct object_id *object,\n+static int check_ref_valid(struct object_id *object,\n \t\t\t    struct object_id *prev,\n \t\t\t    struct strbuf *ref,\n \t\t\t    int force)\n@@ -142,12 +142,13 @@ static void check_ref_valid(struct object_id *object,\n \tstrbuf_reset(ref);\n \tstrbuf_addf(ref, \"%s%s\", git_replace_ref_base, oid_to_hex(object));\n \tif (check_refname_format(ref->buf, 0))\n-\t\tdie(\"'%s' is not a valid ref name.\", ref->buf);\n+\t\treturn error(\"'%s' is not a valid ref name.\", ref->buf);\n \n \tif (read_ref(ref->buf, prev))\n \t\toidclr(prev);\n \telse if (!force)\n-\t\tdie(\"replace ref '%s' already exists\", ref->buf);\n+\t\treturn error(\"replace ref '%s' already exists\", ref->buf);\n+\treturn 0;\n }\n \n static int replace_object_oid(const char *object_ref,\n@@ -161,28 +162,33 @@ static int replace_object_oid(const char *object_ref,\n \tstruct strbuf ref = STRBUF_INIT;\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n+\tint res = 0;\n \n \tobj_type = oid_object_info(object, NULL);\n \trepl_type = oid_object_info(repl, NULL);\n \tif (!force && obj_type != repl_type)\n-\t\tdie(\"Objects must be of the same type.\\n\"\n-\t\t    \"'%s' points to a replaced object of type '%s'\\n\"\n-\t\t    \"while '%s' points to a replacement object of type '%s'.\",\n-\t\t    object_ref, type_name(obj_type),\n-\t\t    replace_ref, type_name(repl_type));\n-\n-\tcheck_ref_valid(object, &prev, &ref, force);\n+\t\treturn error(\"Objects must be of the same type.\\n\"\n+\t\t\t     \"'%s' points to a replaced object of type '%s'\\n\"\n+\t\t\t     \"while '%s' points to a replacement object of \"\n+\t\t\t     \"type '%s'.\",\n+\t\t\t     object_ref, type_name(obj_type),\n+\t\t\t     replace_ref, type_name(repl_type));\n+\n+\tif (check_ref_valid(object, &prev, &ref, force)) {\n+\t\tstrbuf_release(&ref);\n+\t\treturn -1;\n+\t}\n \n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, ref.buf, repl, &prev,\n \t\t\t\t   0, NULL, &err) ||\n \t    ref_transaction_commit(transaction, &err))\n-\t\tdie(\"%s\", err.buf);\n+\t\tres = error(\"%s\", err.buf);\n \n \tref_transaction_free(transaction);\n \tstrbuf_release(&ref);\n-\treturn 0;\n+\treturn res;\n }\n \n static int replace_object(const char *object_ref, const char *replace_ref, int force)\n@@ -190,9 +196,11 @@ static int replace_object(const char *object_ref, const char *replace_ref, int f\n \tstruct object_id object, repl;\n \n \tif (get_oid(object_ref, &object))\n-\t\tdie(\"Failed to resolve '%s' as a valid ref.\", object_ref);\n+\t\treturn error(\"Failed to resolve '%s' as a valid ref.\",\n+\t\t\t     object_ref);\n \tif (get_oid(replace_ref, &repl))\n-\t\tdie(\"Failed to resolve '%s' as a valid ref.\", replace_ref);\n+\t\treturn error(\"Failed to resolve '%s' as a valid ref.\",\n+\t\t\t     replace_ref);\n \n \treturn replace_object_oid(object_ref, &object, replace_ref, &repl, force);\n }\n@@ -202,7 +210,7 @@ static int replace_object(const char *object_ref, const char *replace_ref, int f\n  * If \"raw\" is true, then the object's raw contents are printed according to\n  * \"type\". Otherwise, we pretty-print the contents for human editing.\n  */\n-static void export_object(const struct object_id *oid, enum object_type type,\n+static int export_object(const struct object_id *oid, enum object_type type,\n \t\t\t  int raw, const char *filename)\n {\n \tstruct child_process cmd = CHILD_PROCESS_INIT;\n@@ -210,7 +218,7 @@ static void export_object(const struct object_id *oid, enum object_type type,\n \n \tfd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);\n \tif (fd < 0)\n-\t\tdie_errno(\"unable to open %s for writing\", filename);\n+\t\treturn error_errno(\"unable to open %s for writing\", filename);\n \n \targv_array_push(&cmd.args, \"--no-replace-objects\");\n \targv_array_push(&cmd.args, \"cat-file\");\n@@ -223,7 +231,8 @@ static void export_object(const struct object_id *oid, enum object_type type,\n \tcmd.out = fd;\n \n \tif (run_command(&cmd))\n-\t\tdie(\"cat-file reported failure\");\n+\t\treturn error(\"cat-file reported failure\");\n+\treturn 0;\n }\n \n /*\n@@ -231,14 +240,14 @@ static void export_object(const struct object_id *oid, enum object_type type,\n  * interpreting it as \"type\", and writing the result to the object database.\n  * The sha1 of the written object is returned via sha1.\n  */\n-static void import_object(struct object_id *oid, enum object_type type,\n+static int import_object(struct object_id *oid, enum object_type type,\n \t\t\t  int raw, const char *filename)\n {\n \tint fd;\n \n \tfd = open(filename, O_RDONLY);\n \tif (fd < 0)\n-\t\tdie_errno(\"unable to open %s for reading\", filename);\n+\t\treturn error_errno(\"unable to open %s for reading\", filename);\n \n \tif (!raw && type == OBJ_TREE) {\n \t\tconst char *argv[] = { \"mktree\", NULL };\n@@ -250,27 +259,40 @@ static void import_object(struct object_id *oid, enum object_type type,\n \t\tcmd.in = fd;\n \t\tcmd.out = -1;\n \n-\t\tif (start_command(&cmd))\n-\t\t\tdie(\"unable to spawn mktree\");\n+\t\tif (start_command(&cmd)) {\n+\t\t\tclose(fd);\n+\t\t\treturn error(\"unable to spawn mktree\");\n+\t\t}\n \n-\t\tif (strbuf_read(&result, cmd.out, 41) < 0)\n-\t\t\tdie_errno(\"unable to read from mktree\");\n+\t\tif (strbuf_read(&result, cmd.out, 41) < 0) {\n+\t\t\terror_errno(\"unable to read from mktree\");\n+\t\t\tclose(fd);\n+\t\t\tclose(cmd.out);\n+\t\t\treturn -1;\n+\t\t}\n \t\tclose(cmd.out);\n \n-\t\tif (finish_command(&cmd))\n-\t\t\tdie(\"mktree reported failure\");\n-\t\tif (get_oid_hex(result.buf, oid) < 0)\n-\t\t\tdie(\"mktree did not return an object name\");\n+\t\tif (finish_command(&cmd)) {\n+\t\t\tstrbuf_release(&result);\n+\t\t\treturn error(\"mktree reported failure\");\n+\t\t}\n+\t\tif (get_oid_hex(result.buf, oid) < 0) {\n+\t\t\tstrbuf_release(&result);\n+\t\t\treturn error(\"mktree did not return an object name\");\n+\t\t}\n \n \t\tstrbuf_release(&result);\n \t} else {\n \t\tstruct stat st;\n \t\tint flags = HASH_FORMAT_CHECK | HASH_WRITE_OBJECT;\n \n-\t\tif (fstat(fd, &st) < 0)\n-\t\t\tdie_errno(\"unable to fstat %s\", filename);\n+\t\tif (fstat(fd, &st) < 0) {\n+\t\t\terror_errno(\"unable to fstat %s\", filename);\n+\t\t\tclose(fd);\n+\t\t\treturn -1;\n+\t\t}\n \t\tif (index_fd(oid, fd, &st, type, NULL, flags) < 0)\n-\t\t\tdie(\"unable to write object to database\");\n+\t\t\treturn error(\"unable to write object to database\");\n \t\t/* index_fd close()s fd for us */\n \t}\n \n@@ -278,30 +300,43 @@ static void import_object(struct object_id *oid, enum object_type type,\n \t * No need to close(fd) here; both run-command and index-fd\n \t * will have done it for us.\n \t */\n+\treturn 0;\n }\n \n static int edit_and_replace(const char *object_ref, int force, int raw)\n {\n-\tchar *tmpfile = git_pathdup(\"REPLACE_EDITOBJ\");\n+\tchar *tmpfile;\n \tenum object_type type;\n \tstruct object_id old_oid, new_oid, prev;\n \tstruct strbuf ref = STRBUF_INIT;\n \n \tif (get_oid(object_ref, &old_oid) < 0)\n-\t\tdie(\"Not a valid object name: '%s'\", object_ref);\n+\t\treturn error(\"Not a valid object name: '%s'\", object_ref);\n \n \ttype = oid_object_info(&old_oid, NULL);\n \tif (type < 0)\n-\t\tdie(\"unable to get object type for %s\", oid_to_hex(&old_oid));\n+\t\treturn error(\"unable to get object type for %s\",\n+\t\t\t     oid_to_hex(&old_oid));\n \n-\tcheck_ref_valid(&old_oid, &prev, &ref, force);\n+\tif (check_ref_valid(&old_oid, &prev, &ref, force)) {\n+\t\tstrbuf_release(&ref);\n+\t\treturn -1;\n+\t}\n \tstrbuf_release(&ref);\n \n-\texport_object(&old_oid, type, raw, tmpfile);\n-\tif (launch_editor(tmpfile, NULL, NULL) < 0)\n-\t\tdie(\"editing object file failed\");\n-\timport_object(&new_oid, type, raw, tmpfile);\n-\n+\ttmpfile = git_pathdup(\"REPLACE_EDITOBJ\");\n+\tif (export_object(&old_oid, type, raw, tmpfile)) {\n+\t\tfree(tmpfile);\n+\t\treturn -1;\n+\t}\n+\tif (launch_editor(tmpfile, NULL, NULL) < 0) {\n+\t\tfree(tmpfile);\n+\t\treturn error(\"editing object file failed\");\n+\t}\n+\tif (import_object(&new_oid, type, raw, tmpfile)) {\n+\t\tfree(tmpfile);\n+\t\treturn -1;\n+\t}\n \tfree(tmpfile);\n \n \tif (!oidcmp(&old_oid, &new_oid))\n@@ -310,7 +345,7 @@ static int edit_and_replace(const char *object_ref, int force, int raw)\n \treturn replace_object_oid(object_ref, &old_oid, \"replacement\", &new_oid, force);\n }\n \n-static void replace_parents(struct strbuf *buf, int argc, const char **argv)\n+static int 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@@ -327,9 +362,15 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv)\n \t/* prepare new parents */\n \tfor (i = 0; i < argc; i++) {\n \t\tstruct object_id oid;\n-\t\tif (get_oid(argv[i], &oid) < 0)\n-\t\t\tdie(_(\"Not a valid object name: '%s'\"), argv[i]);\n-\t\tlookup_commit_or_die(&oid, argv[i]);\n+\t\tif (get_oid(argv[i], &oid) < 0) {\n+\t\t\tstrbuf_release(&new_parents);\n+\t\t\treturn error(_(\"Not a valid object name: '%s'\"),\n+\t\t\t\t     argv[i]);\n+\t\t}\n+\t\tif (!lookup_commit_reference(&oid)) {\n+\t\t\tstrbuf_release(&new_parents);\n+\t\t\treturn error(_(\"could not parse %s\"), argv[i]);\n+\t\t}\n \t\tstrbuf_addf(&new_parents, \"parent %s\\n\", oid_to_hex(&oid));\n \t}\n \n@@ -338,6 +379,7 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv)\n \t\t      new_parents.buf, new_parents.len);\n \n \tstrbuf_release(&new_parents);\n+\treturn 0;\n }\n \n struct check_mergetag_data {\n@@ -358,21 +400,23 @@ static int check_one_mergetag(struct commit *commit,\n \thash_object_file(extra->value, extra->len, type_name(OBJ_TAG), &tag_oid);\n \ttag = lookup_tag(&tag_oid);\n \tif (!tag)\n-\t\tdie(_(\"bad mergetag in commit '%s'\"), ref);\n+\t\treturn error(_(\"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+\t\treturn error(_(\"malformed mergetag in commit '%s'\"), ref);\n \n \t/* iterate over new parents */\n \tfor (i = 1; i < mergetag_data->argc; i++) {\n \t\tstruct object_id oid;\n \t\tif (get_oid(mergetag_data->argv[i], &oid) < 0)\n-\t\t\tdie(_(\"Not a valid object name: '%s'\"), mergetag_data->argv[i]);\n+\t\t\treturn error(_(\"Not a valid object name: '%s'\"),\n+\t\t\t\t     mergetag_data->argv[i]);\n \t\tif (!oidcmp(&tag->tagged->oid, &oid))\n \t\t\treturn 0; /* found */\n \t}\n \n-\tdie(_(\"original commit '%s' contains mergetag '%s' that is discarded; \"\n-\t      \"use --edit instead of --graft\"), ref, oid_to_hex(&tag_oid));\n+\treturn error(_(\"original commit '%s' contains mergetag '%s' that is \"\n+\t\t       \"discarded; use --edit instead of --graft\"), ref,\n+\t\t     oid_to_hex(&tag_oid));\n }\n \n static int check_mergetags(struct commit *commit, int argc, const char **argv)\n@@ -394,24 +438,35 @@ static int create_graft(int argc, const char **argv, int force)\n \tunsigned long size;\n \n \tif (get_oid(old_ref, &old_oid) < 0)\n-\t\tdie(_(\"Not a valid object name: '%s'\"), old_ref);\n-\tcommit = lookup_commit_or_die(&old_oid, old_ref);\n+\t\treturn error(_(\"Not a valid object name: '%s'\"), old_ref);\n+\tcommit = lookup_commit_reference(&old_oid);\n+\tif (!commit)\n+\t\treturn error(_(\"could not parse %s\"), 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+\tif (replace_parents(&buf, argc - 1, &argv[1]) < 0) {\n+\t\tstrbuf_release(&buf);\n+\t\treturn -1;\n+\t}\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-\tcheck_mergetags(commit, argc, argv);\n+\tif (check_mergetags(commit, argc, argv)) {\n+\t\tstrbuf_release(&buf);\n+\t\treturn -1;\n+\t}\n \n-\tif (write_object_file(buf.buf, buf.len, commit_type, &new_oid))\n-\t\tdie(_(\"could not write replacement commit for: '%s'\"), old_ref);\n+\tif (write_object_file(buf.buf, buf.len, commit_type, &new_oid)) {\n+\t\tstrbuf_release(&buf);\n+\t\treturn error(_(\"could not write replacement commit for: '%s'\"),\n+\t\t\t     old_ref);\n+\t}\n \n \tstrbuf_release(&buf);\n \n-- \n2.17.0.windows.1.36.gdf4ca5fb72a\n\n\n"},{"id":"346060","messageId":"d3d9b0888ba3123dedf7fabfe93f53a539dbecd4.1524955439.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524955439.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 05/12] replace: prepare create_graft() for converting graft files wholesale","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-28T22:44:32Z","receivedAt":"2018-04-28T22:44:45Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"When converting all grafts in a graft file to replace refs, and one of\nthem happens to leave the original commit's parents unchanged, we do not\nwant to error out. Instead, we would like to issue a warning.\n\nPrepare the create_graft() function for such a use case by adding a\n`gentle` parameter. If set, we do not return an error when the replace ref\nis unchanged, but a mere warning.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/replace.c | 11 ++++++++---\n 1 file changed, 8 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex e57d3d187ed..64f58112701 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -428,7 +428,7 @@ static int check_mergetags(struct commit *commit, int argc, const char **argv)\n \treturn for_each_mergetag(check_one_mergetag, commit, &mergetag_data);\n }\n \n-static int create_graft(int argc, const char **argv, int force)\n+static int create_graft(int argc, const char **argv, int force, int gentle)\n {\n \tstruct object_id old_oid, new_oid;\n \tconst char *old_ref = argv[0];\n@@ -470,8 +470,13 @@ static int create_graft(int argc, const char **argv, int force)\n \n \tstrbuf_release(&buf);\n \n-\tif (!oidcmp(&old_oid, &new_oid))\n+\tif (!oidcmp(&old_oid, &new_oid)) {\n+\t\tif (gentle) {\n+\t\t\twarning(\"graft for '%s' unnecessary\", oid_to_hex(&old_oid));\n+\t\t\treturn 0;\n+\t\t}\n \t\treturn error(\"new commit is the same as the old one: '%s'\", oid_to_hex(&old_oid));\n+\t}\n \n \treturn replace_object_oid(old_ref, &old_oid, \"replacement\", &new_oid, force);\n }\n@@ -547,7 +552,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\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+\t\treturn create_graft(argc, argv, force, 0);\n \n \tcase MODE_LIST:\n \t\tif (argc > 1)\n-- \n2.17.0.windows.1.36.gdf4ca5fb72a\n\n\n"},{"id":"346061","messageId":"7bf28028005b483e4d1b22b7e68bdbc042dfcf9d.1524955439.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524955439.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 06/12] replace: introduce --convert-graft-file","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-28T22:44:35Z","receivedAt":"2018-04-28T22:44:50Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"This option is intended to help with the transition away from the\nnow-deprecated graft file.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-replace.txt | 11 ++++++---\n builtin/replace.c             | 44 ++++++++++++++++++++++++++++++++++-\n 2 files changed, 51 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/git-replace.txt b/Documentation/git-replace.txt\nindex e5c57ae6ef4..246dc9943c2 100644\n--- a/Documentation/git-replace.txt\n+++ b/Documentation/git-replace.txt\n@@ -11,6 +11,7 @@ SYNOPSIS\n 'git replace' [-f] <object> <replacement>\n 'git replace' [-f] --edit <object>\n 'git replace' [-f] --graft <commit> [<parent>...]\n+'git replace' [-f] --convert-graft-file\n 'git replace' -d <object>...\n 'git replace' [--format=<format>] [-l [<pattern>]]\n \n@@ -87,9 +88,13 @@ 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. 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+\tcommit. Use `--convert-graft-file` to convert a\n+\t`$GIT_DIR/info/grafts` file and use replace refs instead.\n+\n+--convert-graft-file::\n+\tCreates graft commits for all entries in `$GIT_DIR/info/grafts`\n+\tand deletes that file upon success. The purpose is to help users\n+\twith transitioning off of the now-deprecated graft file.\n \n -l <pattern>::\n --list <pattern>::\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 64f58112701..a87fca045be 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -20,6 +20,7 @@ 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 [-f] --convert-graft-file\"),\n \tN_(\"git replace -d <object>...\"),\n \tN_(\"git replace [--format=<format>] [-l [<pattern>]]\"),\n \tNULL\n@@ -481,6 +482,38 @@ static int create_graft(int argc, const char **argv, int force, int gentle)\n \treturn replace_object_oid(old_ref, &old_oid, \"replacement\", &new_oid, force);\n }\n \n+static int convert_graft_file(int force)\n+{\n+\tconst char *graft_file = get_graft_file();\n+\tFILE *fp = fopen_or_warn(graft_file, \"r\");\n+\tstruct strbuf buf = STRBUF_INIT, err = STRBUF_INIT;\n+\tstruct argv_array args = ARGV_ARRAY_INIT;\n+\n+\tif (!fp)\n+\t\treturn -1;\n+\n+\twhile (strbuf_getline(&buf, fp) != EOF) {\n+\t\tif (*buf.buf == '#')\n+\t\t\tcontinue;\n+\n+\t\targv_array_split(&args, buf.buf);\n+\t\tif (args.argc && create_graft(args.argc, args.argv, force, 1))\n+\t\t\tstrbuf_addf(&err, \"\\n\\t%s\", buf.buf);\n+\t\targv_array_clear(&args);\n+\t}\n+\tfclose(fp);\n+\n+\tstrbuf_release(&buf);\n+\n+\tif (!err.len)\n+\t\treturn unlink_or_warn(graft_file);\n+\n+\twarning(_(\"could not convert the following graft(s):\\n%s\"), err.buf);\n+\tstrbuf_release(&err);\n+\n+\treturn -1;\n+}\n+\n int cmd_replace(int argc, const char **argv, const char *prefix)\n {\n \tint force = 0;\n@@ -492,6 +525,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n \t\tMODE_DELETE,\n \t\tMODE_EDIT,\n \t\tMODE_GRAFT,\n+\t\tMODE_CONVERT_GRAFT_FILE,\n \t\tMODE_REPLACE\n \t} cmdmode = MODE_UNSPECIFIED;\n \tstruct option options[] = {\n@@ -499,6 +533,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\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_CMDMODE(0, \"convert-graft-file\", &cmdmode, N_(\"convert existing graft file\"), MODE_CONVERT_GRAFT_FILE),\n \t\tOPT_BOOL_F('f', \"force\", &force, N_(\"replace the ref if it exists\"),\n \t\t\t   PARSE_OPT_NOCOMPLETE),\n \t\tOPT_BOOL(0, \"raw\", &raw, N_(\"do not pretty-print contents for --edit\")),\n@@ -521,7 +556,8 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n \tif (force &&\n \t    cmdmode != MODE_REPLACE &&\n \t    cmdmode != MODE_EDIT &&\n-\t    cmdmode != MODE_GRAFT)\n+\t    cmdmode != MODE_GRAFT &&\n+\t    cmdmode != MODE_CONVERT_GRAFT_FILE)\n \t\tusage_msg_opt(\"-f only makes sense when writing a replacement\",\n \t\t\t      git_replace_usage, options);\n \n@@ -554,6 +590,12 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\n \t\t\t\t      git_replace_usage, options);\n \t\treturn create_graft(argc, argv, force, 0);\n \n+\tcase MODE_CONVERT_GRAFT_FILE:\n+\t\tif (argc != 0)\n+\t\t\tusage_msg_opt(\"--convert-graft-file takes no argument\",\n+\t\t\t\t      git_replace_usage, options);\n+\t\treturn !!convert_graft_file(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.17.0.windows.1.36.gdf4ca5fb72a\n\n\n"},{"id":"346062","messageId":"fd4edf238230d0ac52d9e7cc3055506cb18d71af.1524955439.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524955439.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 07/12] Add a test for `git replace --convert-graft-file`","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-28T22:44:42Z","receivedAt":"2018-04-28T22:44:52Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The proof, as the saying goes, lies in the pudding. So here is a\nregression test that not only demonstrates what the option is supposed to\naccomplish, but also demonstrates that it does accomplish it.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/t6050-replace.sh | 28 ++++++++++++++++++++++++++++\n 1 file changed, 28 insertions(+)\n\ndiff --git a/t/t6050-replace.sh b/t/t6050-replace.sh\nindex c630aba657e..d174bfed309 100755\n--- a/t/t6050-replace.sh\n+++ b/t/t6050-replace.sh\n@@ -444,4 +444,32 @@ test_expect_success GPG '--graft on a commit with a mergetag' '\n \tgit replace -d $HASH10\n '\n \n+test_expect_success '--convert-graft-file' '\n+\tgit checkout -b with-graft-file &&\n+\ttest_commit root2 &&\n+\tgit reset --hard root2^ &&\n+\ttest_commit root1 &&\n+\ttest_commit after-root1 &&\n+\ttest_tick &&\n+\tgit merge -m merge-root2 root2 &&\n+\n+\t: add and convert graft file &&\n+\tprintf \"%s\\n%s %s\\n\\n# comment\\n%s\\n\" \\\n+\t\t$(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \\\n+\t\t>.git/info/grafts &&\n+\tgit replace --convert-graft-file &&\n+\ttest_path_is_missing .git/info/grafts &&\n+\n+\t: verify that the history is now \"grafted\" &&\n+\tgit rev-list HEAD >out &&\n+\ttest_line_count = 4 out &&\n+\n+\t: create invalid graft file and verify that it is not deleted &&\n+\ttest_when_finished \"rm -f .git/info/grafts\" &&\n+\techo $EMPTY_BLOB $EMPTY_TREE >.git/info/grafts &&\n+\ttest_must_fail git replace --convert-graft-file 2>err &&\n+\ttest_i18ngrep \"$EMPTY_BLOB $EMPTY_TREE\" err &&\n+\ttest_i18ngrep \"$EMPTY_BLOB $EMPTY_TREE\" .git/info/grafts\n+'\n+\n test_done\n-- \n2.17.0.windows.1.36.gdf4ca5fb72a\n\n\n"},{"id":"346063","messageId":"82ccde9ed8a390133852a5d9e07ef471f94db263.1524955439.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524955439.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 08/12] Deprecate support for .git/info/grafts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-28T22:44:44Z","receivedAt":"2018-04-28T22:45:00Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The grafts feature was a convenient way to \"stitch together\" ancient\nhistory to the fresh start of linux.git.\n\nIts implementation is, however, not up to Git's standards, as there are\ntoo many ways where it can lead to surprising and unwelcome behavior.\n\nFor example, when pushing from a repository with active grafts, it is\npossible to miss commits that have been \"grafted out\", resulting in a\nbroken state on the other side.\n\nAlso, the grafts feature is limited to \"rewriting\" commits' list of\nparents, it cannot replace anything else.\n\nThe much younger feature implemented as `git replace` set out to remedy\nthose limitations and dangerous bugs.\n\nSeeing as `git replace` is pretty mature by now (since 4228e8bc98\n(replace: add --graft option, 2014-07-19) it can perform the graft\nfile's duties), it is time to deprecate support for the graft file, and\nto retire it eventually.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nReviewed-by: Stefan Beller <sbeller@google.com>\n---\n advice.c                  |  2 ++\n advice.h                  |  1 +\n commit.c                  | 10 ++++++++++\n t/t6001-rev-list-graft.sh |  9 +++++++++\n 4 files changed, 22 insertions(+)\n\ndiff --git a/advice.c b/advice.c\nindex 406efc183ba..4411704fd45 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -19,6 +19,7 @@ int advice_rm_hints = 1;\n int advice_add_embedded_repo = 1;\n int advice_ignored_hook = 1;\n int advice_waiting_for_editor = 1;\n+int advice_graft_file_deprecated = 1;\n \n static struct {\n \tconst char *name;\n@@ -42,6 +43,7 @@ static struct {\n \t{ \"addembeddedrepo\", &advice_add_embedded_repo },\n \t{ \"ignoredhook\", &advice_ignored_hook },\n \t{ \"waitingforeditor\", &advice_waiting_for_editor },\n+\t{ \"graftfiledeprecated\", &advice_graft_file_deprecated },\n \n \t/* make this an alias for backward compatibility */\n \t{ \"pushnonfastforward\", &advice_push_update_rejected }\ndiff --git a/advice.h b/advice.h\nindex 70568fa7922..9f5064e82a8 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -21,6 +21,7 @@ extern int advice_rm_hints;\n extern int advice_add_embedded_repo;\n extern int advice_ignored_hook;\n extern int advice_waiting_for_editor;\n+extern int advice_graft_file_deprecated;\n \n int git_default_advice_config(const char *var, const char *value);\n __attribute__((format (printf, 1, 2)))\ndiff --git a/commit.c b/commit.c\nindex 2952ec987c5..451d3ce8dfe 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -12,6 +12,7 @@\n #include \"prio-queue.h\"\n #include \"sha1-lookup.h\"\n #include \"wt-status.h\"\n+#include \"advice.h\"\n \n static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);\n \n@@ -176,6 +177,15 @@ static int read_graft_file(const char *graft_file)\n \tstruct strbuf buf = STRBUF_INIT;\n \tif (!fp)\n \t\treturn -1;\n+\tif (advice_graft_file_deprecated)\n+\t\tadvise(_(\"Support for <GIT_DIR>/info/grafts is deprecated\\n\"\n+\t\t\t \"and will be removed in a future Git version.\\n\"\n+\t\t\t \"\\n\"\n+\t\t\t \"Please use \\\"git replace --convert-graft-file\\\"\\n\"\n+\t\t\t \"to convert the grafts into replace refs.\\n\"\n+\t\t\t \"\\n\"\n+\t\t\t \"Turn this message off by running\\n\"\n+\t\t\t \"\\\"git config advice.graftFileDeprecated false\\\"\"));\n \twhile (!strbuf_getwholeline(&buf, fp, '\\n')) {\n \t\t/* The format is just \"Commit Parent1 Parent2 ...\\n\" */\n \t\tstruct commit_graft *graft = read_graft_line(&buf);\ndiff --git a/t/t6001-rev-list-graft.sh b/t/t6001-rev-list-graft.sh\nindex 05ddc69cf2a..7504ba47511 100755\n--- a/t/t6001-rev-list-graft.sh\n+++ b/t/t6001-rev-list-graft.sh\n@@ -110,4 +110,13 @@ do\n \t\"\n \n done\n+\n+test_expect_success 'show advice that grafts are deprecated' '\n+\tgit show HEAD 2>err &&\n+\ttest_i18ngrep \"git replace\" err &&\n+\ttest_config advice.graftFileDeprecated false &&\n+\tgit show HEAD 2>err &&\n+\ttest_i18ngrep ! \"git replace\" err\n+'\n+\n test_done\n-- \n2.17.0.windows.1.36.gdf4ca5fb72a\n\n\n"},{"id":"346064","messageId":"fdda7416d47f145caead0c4282f62853c93ec64d.1524955439.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524955439.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 09/12] filter-branch: stop suggesting to use grafts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-28T22:44:53Z","receivedAt":"2018-04-28T22:45:19Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The graft file is deprecated now, so let's use replace refs in the example\nin filter-branch's man page instead.\n\nSuggested-by: Eric Sunshine <sunshine@sunshineco.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/git-filter-branch.txt | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt\nindex b634043183b..1d4d2f86045 100644\n--- a/Documentation/git-filter-branch.txt\n+++ b/Documentation/git-filter-branch.txt\n@@ -288,7 +288,7 @@ git filter-branch --parent-filter \\\n or even simpler:\n \n -----------------------------------------------\n-echo \"$commit-id $graft-id\" >> .git/info/grafts\n+git replace --graft $commit-id $graft-id\n git filter-branch $graft-id..HEAD\n -----------------------------------------------\n \n-- \n2.17.0.windows.1.36.gdf4ca5fb72a\n\n\n"},{"id":"346065","messageId":"99e43b91665c99c566ebbc598fc30c26e3712abd.1524955439.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524955439.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 10/12] technical/shallow: stop referring to grafts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-28T22:45:13Z","receivedAt":"2018-04-28T22:45:28Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Now that grafts are deprecated, we should start to assume that readers\nhave no idea what grafts are. So it makes more sense to make the\ndescription of the \"shallow\" feature stand on its own.\n\nSuggested-by: Eric Sunshine <sunshine@sunshineco.com>\nHelped-by: Junio Hamano <gitster@pobox.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/technical/shallow.txt | 13 ++++---------\n 1 file changed, 4 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/technical/shallow.txt b/Documentation/technical/shallow.txt\nindex 5183b154229..4ec721335d2 100644\n--- a/Documentation/technical/shallow.txt\n+++ b/Documentation/technical/shallow.txt\n@@ -8,15 +8,10 @@ repo, and therefore grafts are introduced pretending that\n these commits have no parents.\n *********************************************************\n \n-The basic idea is to write the SHA-1s of shallow commits into\n-$GIT_DIR/shallow, and handle its contents like the contents\n-of $GIT_DIR/info/grafts (with the difference that shallow\n-cannot contain parent information).\n-\n-This information is stored in a new file instead of grafts, or\n-even the config, since the user should not touch that file\n-at all (even throughout development of the shallow clone, it\n-was never manually edited!).\n+$GIT_DIR/shallow lists commit object names and tells Git to\n+pretend as if they are root commits (e.g. \"git log\" traversal\n+stops after showing them; \"git fsck\" does not complain saying\n+the commits listed on their \"parent\" lines do not exist).\n \n Each line contains exactly one SHA-1. When read, a commit_graft\n will be constructed, which has nr_parent < 0 to make it easier\n-- \n2.17.0.windows.1.36.gdf4ca5fb72a\n\n\n"},{"id":"346066","messageId":"196c4ec85feb602ce9a89ced5e0ee008a86a1028.1524955439.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524955439.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 11/12] technical/shallow: describe why shallow cannot use replace refs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-28T22:45:21Z","receivedAt":"2018-04-28T22:45:30Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"It is tempting to do away with commit_graft altogether (in the long\nhaul), now that grafts are deprecated.\n\nHowever, the shallow feature needs a couple of things that the replace\nrefs cannot fulfill. Let's point that out in the documentation.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n Documentation/technical/shallow.txt | 7 +++++++\n 1 file changed, 7 insertions(+)\n\ndiff --git a/Documentation/technical/shallow.txt b/Documentation/technical/shallow.txt\nindex 4ec721335d2..01dedfe9ffe 100644\n--- a/Documentation/technical/shallow.txt\n+++ b/Documentation/technical/shallow.txt\n@@ -17,6 +17,13 @@ Each line contains exactly one SHA-1. When read, a commit_graft\n will be constructed, which has nr_parent < 0 to make it easier\n to discern from user provided grafts.\n \n+Note that the shallow feature could not be changed easily to\n+use replace refs: a commit containing a `mergetag` is not allowed\n+to be replaced, not even by a root commit. Such a commit can be\n+made shallow, though. Also, having a `shallow` file explicitly\n+listing all the commits made shallow makes it a *lot* easier to\n+do shallow-specific things such as to deepen the history.\n+\n Since fsck-objects relies on the library to read the objects,\n it honours shallow commits automatically.\n \n-- \n2.17.0.windows.1.36.gdf4ca5fb72a\n\n\n"},{"id":"346067","messageId":"1072d92b32464c29e95eb975a902a86b3603ad58.1524955439.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524955439.git.johannes.schindelin@gmx.de","subject":"[PATCH v7 12/12] Remove obsolete script to convert grafts to replace refs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-28T22:45:22Z","receivedAt":"2018-04-28T22:45:31Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"The functionality is now implemented as `git replace\n--convert-graft-file`.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n contrib/convert-grafts-to-replace-refs.sh | 28 -----------------------\n 1 file changed, 28 deletions(-)\n delete mode 100755 contrib/convert-grafts-to-replace-refs.sh\n\ndiff --git a/contrib/convert-grafts-to-replace-refs.sh b/contrib/convert-grafts-to-replace-refs.sh\ndeleted file mode 100755\nindex 0cbc917b8cf..00000000000\n--- a/contrib/convert-grafts-to-replace-refs.sh\n+++ /dev/null\n@@ -1,28 +0,0 @@\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.17.0.windows.1.36.gdf4ca5fb72a\n"},{"id":"346068","messageId":"cover.1524955439.git.johannes.schindelin@gmx.de","threadId":"48278","inReplyTo":"cover.1524865158.git.johannes.schindelin@gmx.de","subject":"[Re-send PATCH v7 00/12] Deprecate .git/info/grafts","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-04-28T22:47:30Z","receivedAt":"2018-04-28T22:48:07Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"[Resent the cover letter, as I did not see it on public-inbox...]\n\nIt is fragile, as there is no way for the revision machinery to say \"but\nnow I want to traverse the graph ignoring the graft file\" e.g. when\npushing commits to a remote repository (which, as a consequence, can\nmiss commits).\n\nAnd we already have a better solution with `git replace --graft <comit>\n[<parent>...]`.\n\nChanges since v6:\n\n- Made --convert-graft-file issue a mere warning (instead of an error) when\n  a graft leaves the parents unchanged, and is hence unnecessary.\n\n- Modified the regression test for --convert-graft-file to succeed even when\n  the GPG prerequisite is unmet (thanks, Gábor!).\n\n- Reassured by Stefan's feedback, I refrained from reinstating the contrib/\n  script.\n\n\nJohannes Schindelin (12):\n  argv_array: offer to split a string by whitespace\n  commit: Let the callback of for_each_mergetag return on error\n  replace: avoid using die() to indicate a bug\n  replace: \"libify\" create_graft() and callees\n  replace: prepare create_graft() for converting graft files wholesale\n  replace: introduce --convert-graft-file\n  Add a test for `git replace --convert-graft-file`\n  Deprecate support for .git/info/grafts\n  filter-branch: stop suggesting to use grafts\n  technical/shallow: stop referring to grafts\n  technical/shallow: describe why shallow cannot use replace refs\n  Remove obsolete script to convert grafts to replace refs\n\n Documentation/git-filter-branch.txt       |   2 +-\n Documentation/git-replace.txt             |  11 +-\n Documentation/technical/shallow.txt       |  20 +-\n advice.c                                  |   2 +\n advice.h                                  |   1 +\n argv-array.c                              |  20 ++\n argv-array.h                              |   2 +\n builtin/replace.c                         | 234 ++++++++++++++++------\n commit.c                                  |  18 +-\n commit.h                                  |   4 +-\n contrib/convert-grafts-to-replace-refs.sh |  28 ---\n log-tree.c                                |  13 +-\n t/t6001-rev-list-graft.sh                 |   9 +\n t/t6050-replace.sh                        |  28 +++\n 14 files changed, 274 insertions(+), 118 deletions(-)\n delete mode 100755 contrib/convert-grafts-to-replace-refs.sh\n\n\nbase-commit: 1f1cddd558b54bb0ce19c8ace353fd07b758510d\nPublished-As: https://github.com/dscho/git/releases/tag/deprecate-grafts-v7\nFetch-It-Via: git fetch https://github.com/dscho/git deprecate-grafts-v7\n\nInterdiff vs v6:\n diff --git a/builtin/replace.c b/builtin/replace.c\n index 35394ec1874..a87fca045be 100644\n --- a/builtin/replace.c\n +++ b/builtin/replace.c\n @@ -429,7 +429,7 @@ static int check_mergetags(struct commit *commit, int argc, const char **argv)\n  \treturn for_each_mergetag(check_one_mergetag, commit, &mergetag_data);\n  }\n  \n -static int create_graft(int argc, const char **argv, int force)\n +static int create_graft(int argc, const char **argv, int force, int gentle)\n  {\n  \tstruct object_id old_oid, new_oid;\n  \tconst char *old_ref = argv[0];\n @@ -471,8 +471,13 @@ static int create_graft(int argc, const char **argv, int force)\n  \n  \tstrbuf_release(&buf);\n  \n -\tif (!oidcmp(&old_oid, &new_oid))\n +\tif (!oidcmp(&old_oid, &new_oid)) {\n +\t\tif (gentle) {\n +\t\t\twarning(\"graft for '%s' unnecessary\", oid_to_hex(&old_oid));\n +\t\t\treturn 0;\n +\t\t}\n  \t\treturn error(\"new commit is the same as the old one: '%s'\", oid_to_hex(&old_oid));\n +\t}\n  \n  \treturn replace_object_oid(old_ref, &old_oid, \"replacement\", &new_oid, force);\n  }\n @@ -492,7 +497,7 @@ static int convert_graft_file(int force)\n  \t\t\tcontinue;\n  \n  \t\targv_array_split(&args, buf.buf);\n -\t\tif (args.argc && create_graft(args.argc, args.argv, force))\n +\t\tif (args.argc && create_graft(args.argc, args.argv, force, 1))\n  \t\t\tstrbuf_addf(&err, \"\\n\\t%s\", buf.buf);\n  \t\targv_array_clear(&args);\n  \t}\n @@ -583,7 +588,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix)\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 +\t\treturn create_graft(argc, argv, force, 0);\n  \n  \tcase MODE_CONVERT_GRAFT_FILE:\n  \t\tif (argc != 0)\n diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh\n index bed86a0af3d..d174bfed309 100755\n --- a/t/t6050-replace.sh\n +++ b/t/t6050-replace.sh\n @@ -445,6 +445,14 @@ test_expect_success GPG '--graft on a commit with a mergetag' '\n  '\n  \n  test_expect_success '--convert-graft-file' '\n +\tgit checkout -b with-graft-file &&\n +\ttest_commit root2 &&\n +\tgit reset --hard root2^ &&\n +\ttest_commit root1 &&\n +\ttest_commit after-root1 &&\n +\ttest_tick &&\n +\tgit merge -m merge-root2 root2 &&\n +\n  \t: add and convert graft file &&\n  \tprintf \"%s\\n%s %s\\n\\n# comment\\n%s\\n\" \\\n  \t\t$(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \\\n-- \n2.17.0.windows.1.36.gdf4ca5fb72a\n"},{"id":"364161","messageId":"20181127201255.3529-1-avarab@gmail.com","threadId":"48278","inReplyTo":"82ccde9ed8a390133852a5d9e07ef471f94db263.1524955439.git.johannes.schindelin@gmx.de","subject":"[PATCH] advice: don't pointlessly suggest --convert-graft-file","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2018-11-27T20:12:55Z","receivedAt":"2018-11-27T20:13:04Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"The advice to run 'git replace --convert-graft-file' added in\nf9f99b3f7d (\"Deprecate support for .git/info/grafts\", 2018-04-29)\ndidn't add an exception for the 'git replace --convert-graft-file'\ncodepath itself.\n\nAs a result we'd suggest running --convert-graft-file while the user\nwas running --convert-graft-file, which makes no sense. Before:\n\n    $ git replace --convert-graft-file\n    hint: Support for <GIT_DIR>/info/grafts is deprecated\n    hint: and will be removed in a future Git version.\n    hint:\n    hint: Please use \"git replace --convert-graft-file\"\n    hint: to convert the grafts into replace refs.\n    hint:\n    hint: Turn this message off by running\n    hint: \"git config advice.graftFileDeprecated false\"\n\nAdd a check for that case and skip printing the advice while the user\nis busy following our advice.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/replace.c  | 1 +\n t/t6050-replace.sh | 5 ++++-\n 2 files changed, 5 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex a58b9c6d13..affcdfb416 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -495,6 +495,7 @@ static int convert_graft_file(int force)\n \tif (!fp)\n \t\treturn -1;\n \n+\tadvice_graft_file_deprecated = 0;\n \twhile (strbuf_getline(&buf, fp) != EOF) {\n \t\tif (*buf.buf == '#')\n \t\t\tcontinue;\ndiff --git a/t/t6050-replace.sh b/t/t6050-replace.sh\nindex 86374a9c52..5d6d3184ac 100755\n--- a/t/t6050-replace.sh\n+++ b/t/t6050-replace.sh\n@@ -461,7 +461,10 @@ test_expect_success '--convert-graft-file' '\n \tprintf \"%s\\n%s %s\\n\\n# comment\\n%s\\n\" \\\n \t\t$(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \\\n \t\t>.git/info/grafts &&\n-\tgit replace --convert-graft-file &&\n+\tgit status 2>stderr &&\n+\ttest_i18ngrep \"hint:.*grafts is deprecated\" stderr &&\n+\tgit replace --convert-graft-file 2>stderr &&\n+\ttest_i18ngrep ! \"hint:.*grafts is deprecated\" stderr &&\n \ttest_path_is_missing .git/info/grafts &&\n \n \t: verify that the history is now \"grafted\" &&\n-- \n2.20.0.rc1.379.g1dd7ef354c\n\n"},{"id":"364194","messageId":"xmqqy39dr9pt.fsf@gitster-ct.c.googlers.com","threadId":"48278","inReplyTo":"20181127201255.3529-1-avarab@gmail.com","subject":"Re: [PATCH] advice: don't pointlessly suggest --convert-graft-file","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-11-28T06:34:54Z","receivedAt":"2018-11-28T06:35:01Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:\n\n> The advice to run 'git replace --convert-graft-file' added in\n> f9f99b3f7d (\"Deprecate support for .git/info/grafts\", 2018-04-29)\n> didn't add an exception for the 'git replace --convert-graft-file'\n> codepath itself.\n>\n> As a result we'd suggest running --convert-graft-file while the user\n> was running --convert-graft-file, which makes no sense. Before:\n>\n>     $ git replace --convert-graft-file\n>     hint: Support for <GIT_DIR>/info/grafts is deprecated\n>     hint: and will be removed in a future Git version.\n>     hint:\n>     hint: Please use \"git replace --convert-graft-file\"\n>     hint: to convert the grafts into replace refs.\n>     hint:\n>     hint: Turn this message off by running\n>     hint: \"git config advice.graftFileDeprecated false\"\n\nThat's a good one.  The glitch is real, the improvement is obvious,\nand the implementation seems to be straight-forward and sensible.\n\nHow did you find one, is the real question ;-)\n\n"},{"id":"364200","messageId":"nycvar.QRO.7.76.6.1811281001411.41@tvgsbejvaqbjf.bet","threadId":"48278","inReplyTo":"20181127201255.3529-1-avarab@gmail.com","subject":"Re: [PATCH] advice: don't pointlessly suggest --convert-graft-file","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2018-11-28T09:03:11Z","receivedAt":"2018-11-28T09:03:20Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Ævar,\n\nOn Tue, 27 Nov 2018, Ævar Arnfjörð Bjarmason wrote:\n\n> The advice to run 'git replace --convert-graft-file' added in\n> f9f99b3f7d (\"Deprecate support for .git/info/grafts\", 2018-04-29)\n> didn't add an exception for the 'git replace --convert-graft-file'\n> codepath itself.\n> \n> As a result we'd suggest running --convert-graft-file while the user\n> was running --convert-graft-file, which makes no sense. Before:\n> \n>     $ git replace --convert-graft-file\n>     hint: Support for <GIT_DIR>/info/grafts is deprecated\n>     hint: and will be removed in a future Git version.\n>     hint:\n>     hint: Please use \"git replace --convert-graft-file\"\n>     hint: to convert the grafts into replace refs.\n>     hint:\n>     hint: Turn this message off by running\n>     hint: \"git config advice.graftFileDeprecated false\"\n> \n> Add a check for that case and skip printing the advice while the user\n> is busy following our advice.\n> \n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> ---\n>  builtin/replace.c  | 1 +\n>  t/t6050-replace.sh | 5 ++++-\n>  2 files changed, 5 insertions(+), 1 deletion(-)\n> \n> diff --git a/builtin/replace.c b/builtin/replace.c\n> index a58b9c6d13..affcdfb416 100644\n> --- a/builtin/replace.c\n> +++ b/builtin/replace.c\n> @@ -495,6 +495,7 @@ static int convert_graft_file(int force)\n>  \tif (!fp)\n>  \t\treturn -1;\n>  \n> +\tadvice_graft_file_deprecated = 0;\n>  \twhile (strbuf_getline(&buf, fp) != EOF) {\n>  \t\tif (*buf.buf == '#')\n>  \t\t\tcontinue;\n\nAs long as we keep this code in the one-shot code path, it is probably\nokay. Otherwise we'd have to save the original value and restoring it\nbefore returning from this function.\n\nBut then, we might never move `convert_graft_file()` into `libgit.a`.\n\nThanks,\nDscho\n\n> diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh\n> index 86374a9c52..5d6d3184ac 100755\n> --- a/t/t6050-replace.sh\n> +++ b/t/t6050-replace.sh\n> @@ -461,7 +461,10 @@ test_expect_success '--convert-graft-file' '\n>  \tprintf \"%s\\n%s %s\\n\\n# comment\\n%s\\n\" \\\n>  \t\t$(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \\\n>  \t\t>.git/info/grafts &&\n> -\tgit replace --convert-graft-file &&\n> +\tgit status 2>stderr &&\n> +\ttest_i18ngrep \"hint:.*grafts is deprecated\" stderr &&\n> +\tgit replace --convert-graft-file 2>stderr &&\n> +\ttest_i18ngrep ! \"hint:.*grafts is deprecated\" stderr &&\n>  \ttest_path_is_missing .git/info/grafts &&\n>  \n>  \t: verify that the history is now \"grafted\" &&\n> -- \n> 2.20.0.rc1.379.g1dd7ef354c\n> \n> "}]}