Deprecate support for .git/info/grafts
PATCH 1/1 Deprecate support for .git/info/grafts
The grafts feature was a convenient way to "stich together" ancient history to the fresh start of linux.git.
Its implementation is, however, not up to Git's standards, as there are too many ways where it can lead to surprising and unwelcome behavior.
For example, when pushing from a repository with active grafts, it is possible to miss commits that have been "grafted out", resulting in a broken state on the other side.
Also, the grafts feature is limited to "rewriting" commits' list of parents, it cannot replace anything else.
The much younger feature implemented as git replace set out to remedy those limitations and dangerous bugs.
Seeing as git replace is pretty mature by now, it is time to deprecate support for the graft file, and to retire it eventually.
--- advice.c | 2 ++ advice.h | 1 + commit.c | 9 +++++++++ t/t6001-rev-list-graft.sh | 9 +++++++++ 4 files changed, 21 insertions(+)
advice.c +2 -0
@@ -19,6 +19,7 @@ int advice_rm_hints = 1;int advice_add_embedded_repo =1 ;int advice_ignored_hook =1 ;int advice_waiting_for_editor =1 ; +int advice_graft_file_deprecated =1 ; static struct { const char *name;
@@ -42,6 +43,7 @@ static struct { { "addembeddedrepo", &advice_add_embedded_repo }, { "ignoredhook", &advice_ignored_hook }, { "waitingforeditor", &advice_waiting_for_editor }, + { "graftfiledeprecated", &advice_graft_file_deprecated },/* make this an alias for backward compatibility */ { "pushnonfastforward", &advice_push_update_rejected }
advice.h +1 -0
@@ -21,6 +21,7 @@ extern int advice_rm_hints;extern int advice_add_embedded_repo;extern int advice_ignored_hook;extern int advice_waiting_for_editor; +extern int advice_graft_file_deprecated;int git_default_advice_config (const char *var,const char *value); __attribute__((format (printf, 1, 2)))
commit.c +9 -0
@@ -12,6 +12,7 @@#include "prio-queue.h" #include "sha1-lookup.h" #include "wt-status.h" +#include "advice.h" static struct commit_extra_header *read_commit_extra_header_lines (const char *buf,size_t len,const char **);
@@ -176,6 +177,14 @@ static int read_graft_file(const char *graft_file)struct strbuf buf =STRBUF_INIT ;if (!fp)return -1 ; +if (advice_graft_file_deprecated) +advise (_ ("Support for <GIT_DIR>/info/grafts is deprecated\n" +"and will be removed in a future Git version.\n" +"\n" +"Please use \"git replace --graft [...]\" instead.\n" +"\n" +"Turn this message off by running\n" +"\"git config advice.graftFileDeprecated false\"" ));while (!strbuf_getwholeline (&buf, fp,'\n' )) {/* The format is just "Commit Parent1 Parent2 ...\n" */ struct commit_graft *graft =read_graft_line (&buf);
t/t6001-rev-list-graft.sh +9 -0
@@ -110,4 +110,13 @@ do " done + +test_expect_success 'show advice that grafts are deprecated' ' + git show HEAD 2>err && + test_i18ngrep "git replace" err && + test_config advice.graftFileDeprecated false && + git show HEAD 2>err && + test_i18ngrep ! "git replace" err +' + test_done
base-commit: fe0a9eaf31dd0c349ae4308498c33a5c3794b293
Signature · 4 lines
2.17.0.windows.1.4.g7e4058d72e3 Published-As: https://github.com/dscho/git/releases/tag/deprecate-grafts-v1 Fetch-It-Via: git fetch https://github.com/dscho/git deprecate-grafts-v1
Hi Johannes,
Quoted 2 lines · Johannes Schindelin
The grafts feature was a convenient way to "stich together" ancient history to the fresh start of linux.git.
Did you mean: stitch?
Quoted 7 lines
Its implementation is, however, not up to Git's standards, as there are too many ways where it can lead to surprising and unwelcome behavior.
For example, when pushing from a repository with active grafts, it is possible to miss commits that have been "grafted out", resulting in a broken state on the other side.
Quoted 8 lines
Also, the grafts feature is limited to "rewriting" commits' list of parents, it cannot replace anything else.
The much younger feature implemented as
git replaceset out to remedy those limitations and dangerous bugs.Seeing as
git replaceis pretty mature by now, it is time to deprecate support for the graft file, and to retire it eventually.
It seems that the maturity needed for this commit was reached in 4228e8bc98 (replace: add --graft option, 2014-07-19)
commit.c static int read_graft_file(const char *graft_file) · line 187
Quoted 12 lines
Signed-off-by Johannes Schindelin <[email protected]>---
return -1;commit.c +8
-0+if (advice_graft_file_deprecated) +advise (_ ("Support for <GIT_DIR>/info/grafts is deprecated\n" +"and will be removed in a future Git version.\n" +"\n" +"Please use \"git replace --graft [...]\" instead.\n" +"\n" +"Turn this message off by running\n" +"\"git config advice.graftFileDeprecated false\"" ));
So the user would have to run:
for line in <GIT_DIR>/info/grafts:
git replace --graft $line
# The order in the grafts file is the same as the arguments, # but we'd have to pass each as its own argument
rm <GIT_DIR>/info/grafts
I wonder if we want to offer a migration tool or just leave it at this hint.
Thanks,
Stefan
Hi Stefan,
Quoted 6 lines · Stefan Beller
Quoted 2 lines · Johannes Schindelin
The grafts feature was a convenient way to "stich together" ancient history to the fresh start of linux.git.
Did you mean: stitch?
Yes ;-)
Quoted 10 lines
Quoted 10 lines
Its implementation is, however, not up to Git's standards, as there are too many ways where it can lead to surprising and unwelcome behavior.
For example, when pushing from a repository with active grafts, it is possible to miss commits that have been "grafted out", resulting in a broken state on the other side.
Also, the grafts feature is limited to "rewriting" commits' list of parents, it cannot replace anything else.
Quoted 8 lines
Quoted 5 lines
The much younger feature implemented as
git replaceset out to remedy those limitations and dangerous bugs.Seeing as
git replaceis pretty mature by now, it is time to deprecate support for the graft file, and to retire it eventually.It seems that the maturity needed for this commit was reached in
4228e8bc98(replace: add --graft option, 2014-07-19)
Right. I'll add that to the commit message.
Quoted 16 lines
Reviewed-by Stefan Beller <[email protected]>commit.c static int read_graft_file(const char *graft_file) · line 187
Quoted 12 lines
Signed-off-by Johannes Schindelin <[email protected]>---
return -1;commit.c +8
-0+if (advice_graft_file_deprecated) +advise (_ ("Support for <GIT_DIR>/info/grafts is deprecated\n" +"and will be removed in a future Git version.\n" +"\n" +"Please use \"git replace --graft [...]\" instead.\n" +"\n" +"Turn this message off by running\n" +"\"git config advice.graftFileDeprecated false\"" ));So the user would have to run:
Quoted 8 lines
for line in <GIT_DIR>/info/grafts:
git replace --graft $line
# The order in the grafts file is the same as the arguments, # but we'd have to pass each as its own argument
rm <GIT_DIR>/info/graftsI wonder if we want to offer a migration tool or just leave it at this hint.
There is contrib/convert-grafts-to-replace-refs.sh.
I wonder whether we have to care enough to implement a git replace --convert-graft-file...
Ciao,
Dscho
Hi Johannes,
Quoted 6 lines · Johannes Schindelin
Hi Stefan,
Quoted 2 lines
I wonder if we want to offer a migration tool or just leave it at this hint.
There is
contrib/convert-grafts-to-replace-refs.sh.
Oh cool! I wonder if we want to expose it more such that people discover it.
Quoted 2 lines
I wonder whether we have to care enough to implement a
git replace --convert-graft-file...
I don't think so.
Hi Stefan,
Quoted 10 lines · Stefan Beller
Quoted 4 lines · Johannes Schindelin
Quoted 2 lines
I wonder if we want to offer a migration tool or just leave it at this hint.
There is
contrib/convert-grafts-to-replace-refs.sh.Oh cool! I wonder if we want to expose it more such that people discover it.
Nah.
Quoted 4 lines
Quoted 2 lines
I wonder whether we have to care enough to implement a
git replace --convert-graft-file...I don't think so.
After reflecting about this in the back of my mind, I actually do. So I implemented it.
Ciao,
Dscho
Quoted 9 lines · Johannes Schindelin
The grafts feature was a convenient way to "stich together" ancient history to the fresh start of linux.git.
[...]
The much younger feature implemented asgit replaceset out to remedy those limitations and dangerous bugs.Seeing as
git replaceis pretty mature by now, it is time to deprecate support for the graft file, and to retire it eventually.
Quoted 7 lines
Signed-off-by Johannes Schindelin <[email protected]>--- advice.c | 2 ++ advice.h | 1 + commit.c | 9 +++++++++ t/t6001-rev-list-graft.sh | 9 +++++++++ 4 files changed, 21 insertions(+)
Perhaps, as part of this deprecation, the example in Documentation/git-filter-branch.txt should be updated to suggest git-replace instead of .git/info/grafts.
Maybe, also, Documentation/shallow.txt should talk about replace-refs rather than .git/info/grafts.
Hi Eric,
Quoted 19 lines · Eric Sunshine
Quoted 9 lines · Johannes Schindelin
The grafts feature was a convenient way to "stich together" ancient history to the fresh start of linux.git.
[...]
The much younger feature implemented asgit replaceset out to remedy those limitations and dangerous bugs.Seeing as
git replaceis pretty mature by now, it is time to deprecate support for the graft file, and to retire it eventually.Quoted 7 lines
Signed-off-by Johannes Schindelin <[email protected]>--- advice.c | 2 ++ advice.h | 1 + commit.c | 9 +++++++++ t/t6001-rev-list-graft.sh | 9 +++++++++ 4 files changed, 21 insertions(+)
Quoted 3 lines
Perhaps, as part of this deprecation, the example in
Documentation/git-filter-branch.txtshould be updated to suggest git-replace instead of .git/info/grafts.
Good point!
Quoted 2 lines
Maybe, also,
Documentation/shallow.txtshould talk about replace-refs rather than .git/info/grafts.
Sure. Internally, of course, "shallow" is still handled very much like the graft file. In that sense, it is probably a good thing to keep referring to the graft file in technical/shallow *in addition* to replace refs.
The reason why the graft file should be deprecated does not apply to the shallow file at all, either: the entire set of problems with grafts comes from the fact that you can *replace* the parents *with other parents*. That allows for pushing corrupt history to public repositories IIRC. The same problems do not arise with the shallow feature, where you can *cut* history, but not replace it.
There is a notable difference between shallow commits and replace refs which I did not think about earlier (it actually only came up in testing):
we currently disallow completely to replace merge commits when they contain mergetags, i.e. entries in the commit header that record the hash of the *tag* that was merged (if any). That includes the case where you want to replace the commit with a root commit, as would be needed for shallow commits.
However, there are more reasons not to conflate the shallow commits with replaced commits: by nature, the "shallow" attribute is a lot more volatile than the "replaced" one, as we want to keep it easy to deepen the history (or to "unshallow" it).
Ciao,
Dscho
v2 cover Deprecate .git/info/grafts
In reply to Johannes SchindelinIt is fragile, as there is no way for the revision machinery to say "but now I want to traverse the graph ignoring the graft file" e.g. when pushing commits to a remote repository (which, as a consequence, can miss commits).
And we already have a better solution with git replace --graft <comit> [<parent>...].
Changes since v1:
- Fixed typo (stich -> stitch).
- Added reference in the commit message to the patch where git replace reached feature parity with the graft file.
- Added the --convert-graft-file option to git replace (with tests).
- Adjusted the advice to suggest --convert-graft-file instead.
- Adjusted the documentation of filter-branch and technical/shallow to reflect the fact that grafts are deprecated.
Johannes Schindelin (7):
replace: "libify" create_graft()
replace: introduce --convert-graft-file
Add a test for git replace --convert-graft-file Deprecate support for .git/info/grafts
filter-branch: stop suggesting to use grafts technical/shallow: describe the relationship with replace refs technical/shallow: describe why shallow cannot use replace refs
Documentation/git-filter-branch.txt | 2 +- Documentation/git-replace.txt | 11 +++-- Documentation/technical/shallow.txt | 24 +++++++---- advice.c | 2 + advice.h | 1 + builtin/replace.c | 63 ++++++++++++++++++++++++++++- commit.c | 10 +++++ t/t6001-rev-list-graft.sh | 9 +++++ t/t6050-replace.sh | 20 +++++++++ 9 files changed, 129 insertions(+), 13 deletions(-)
base-commit: fe0a9eaf31dd0c349ae4308498c33a5c3794b293
Published-As: https://github.com/dscho/git/releases/tag/deprecate-grafts-v2
Fetch-It-Via: git fetch https://github.com/dscho/git deprecate-grafts-v2
Interdiff vs v1:
diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt index b634043183b..1d4d2f86045 100644
--- a/Documentation/git-filter-branch.txt
+++ b/Documentation/git-filter-branch.txt
@@ -288,7 +288,7 @@ git filter-branch --parent-filter \
or even simpler:
-----------------------------------------------
-echo "$commit-id $graft-id" >> .git/info/grafts
+git replace --graft $commit-id $graft-id
git filter-branch $graft-id..HEAD
-----------------------------------------------
diff --git a/Documentation/git-replace.txt b/Documentation/git-replace.txt index e5c57ae6ef4..4dc0686f7d6 100644
--- a/Documentation/git-replace.txt
+++ b/Documentation/git-replace.txt
@@ -11,6 +11,7 @@ SYNOPSIS
'git replace' [-f] <object> <replacement>
'git replace' [-f] --edit <object>
'git replace' [-f] --graft <commit> [<parent>...]
+'git replace' [-f] --convert-graft-file
'git replace' -d <object>...
'git replace' [--format=<format>] [-l [<pattern>]]
@@ -87,9 +88,13 @@ OPTIONS
content as <commit> except that its parents will be [<parent>...] instead of <commit>'s parents. A replacement ref is then created to replace <commit> with the newly created
- commit. See contrib/convert-grafts-to-replace-refs.sh for an
- example script based on this option that can convert grafts to
- replace refs.
+ commit. Use --convert-graft-file to convert a
+ $GIT_DIR/info/grafts file use replace refs instead.
+
+--convert-graft-file::
+ Creates graft commits for all entries in $GIT_DIR/info/grafts
+ and deletes that file upon success. The purpose is to help users
+ with transitioning off of the now-deprecated graft file.
-l <pattern>::
--list <pattern>::
diff --git a/Documentation/technical/shallow.txt b/Documentation/technical/shallow.txt index 5183b154229..cb79181c2bb 100644
--- a/Documentation/technical/shallow.txt
+++ b/Documentation/technical/shallow.txt
@@ -9,19 +9,29 @@ these commits have no parents.
*********************************************************
The basic idea is to write the SHA-1s of shallow commits into
-$GIT_DIR/shallow, and handle its contents like the contents -of $GIT_DIR/info/grafts (with the difference that shallow -cannot contain parent information). +$GIT_DIR/shallow, and handle its contents similar to replace +refs (with the difference that shallow does not actually +create those replace refs) and very much like the deprecated +graft file (with the difference that shallow commits will +always have their parents grafted away, not replaced by +different parents).
-This information is stored in a new file instead of grafts, or -even the config, since the user should not touch that file -at all (even throughout development of the shallow clone, it -was never manually edited!).
+This information is stored in a special-purpose file because the +user should not touch that file at all (even throughout +development of the shallow clone, it was never manually +edited!).
Each line contains exactly one SHA-1. When read, a commit_graft will be constructed, which has nr_parent < 0 to make it easier to discern from user provided grafts.
+Note that the shallow feature could not be changed easily to +use replace refs: a commit containing a mergetag is not allowed +to be replaced, not even by a root commit. Such a commit can be +made shallow, though. Also, having a shallow file explicitly +listing all the commits made shallow makes it a *lot* easier to +do shallow-specific things such as to deepen the history. +
Since fsck-objects relies on the library to read the objects, it honours shallow commits automatically.
diff --git a/builtin/replace.c b/builtin/replace.c
index 935647be6bd..4cdc00a96df 100644
--- a/builtin/replace.c
+++ b/builtin/replace.c
@@ -20,6 +20,7 @@ static const char * const git_replace_usage[] = {
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>]]"),
NULL
@@ -395,7 +396,9 @@ static int create_graft(int argc, const char **argv, int force)
if (get_oid(old_ref, &old_oid) < 0)
die(_("Not a valid object name: '%s'"), old_ref);
- commit = lookup_commit_or_die(&old_oid, old_ref);
+ commit = lookup_commit_reference(&old_oid);
+ if (!commit)
+ return error(_("could not parse %s"), old_ref);
buffer = get_commit_buffer(commit, &size);
strbuf_add(&buf, buffer, size);
@@ -421,6 +424,53 @@ static int create_graft(int argc, const char **argv, int force)
return replace_object_oid(old_ref, &old_oid, "replacement", &new_oid, force);
}
+static int convert_graft_file(int force)
+{
+ const char *graft_file = get_graft_file();
+ FILE *fp = fopen_or_warn(graft_file, "r");
+ struct strbuf buf = STRBUF_INIT, err = STRBUF_INIT;
+ struct argv_array args = ARGV_ARRAY_INIT;
+
+ if (!fp)
+ return -1;
+
+ while (strbuf_getline(&buf, fp) != EOF) {
+ int i = 0, j;
+
+ while (i != buf.len) {
+ char save;
+
+ for (j = i; j < buf.len && !isspace(buf.buf[j]); j++)
+ ; /* look further */
+ save = buf.buf[j];
+ buf.buf[j] = '\0';
+ argv_array_push(&args, buf.buf + i);
+ buf.buf[j] = save;
+
+ while (j < buf.len && isspace(buf.buf[j]))
+ j++;
+ i = j;
+ }
+
+ if (create_graft(args.argc, args.argv, force))
+ strbuf_addf(&err, "\n\t%s", buf.buf);
+
+ argv_array_clear(&args);
+ strbuf_reset(&buf);
+ }
+
+ strbuf_release(&buf);
+ argv_array_clear(&args);
+
+ if (!err.len)
+ return unlink_or_warn(graft_file);
+
+ warning(_("could not convert the following graft(s):\n%s"), err.buf);
+ strbuf_release(&err);
+
+ return -1;
+}
+
int cmd_replace(int argc, const char **argv, const char *prefix)
{
int force = 0;
@@ -432,6 +482,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix)
MODE_DELETE,
MODE_EDIT,
MODE_GRAFT,
+ MODE_CONVERT_GRAFT_FILE,
MODE_REPLACE
} cmdmode = MODE_UNSPECIFIED;
struct option options[] = {
@@ -439,6 +490,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix)
OPT_CMDMODE('d', "delete", &cmdmode, N_("delete replace refs"), MODE_DELETE), OPT_CMDMODE('e', "edit", &cmdmode, N_("edit existing object"), MODE_EDIT), OPT_CMDMODE('g', "graft", &cmdmode, N_("change a commit's parents"), MODE_GRAFT),
+ OPT_CMDMODE(0, "convert-graft-file", &cmdmode, N_("convert existing graft file"), MODE_CONVERT_GRAFT_FILE),
OPT_BOOL_F('f', "force", &force, N_("replace the ref if it exists"),
PARSE_OPT_NOCOMPLETE),
OPT_BOOL(0, "raw", &raw, N_("do not pretty-print contents for --edit")),
@@ -461,7 +513,8 @@ int cmd_replace(int argc, const char **argv, const char *prefix)
if (force &&
cmdmode != MODE_REPLACE &&
cmdmode != MODE_EDIT &&
- cmdmode != MODE_GRAFT) + cmdmode != MODE_GRAFT && + cmdmode != MODE_CONVERT_GRAFT_FILE)
usage_msg_opt("-f only makes sense when writing a replacement",
git_replace_usage, options);
@@ -494,6 +547,12 @@ int cmd_replace(int argc, const char **argv, const char *prefix)
git_replace_usage, options);
return create_graft(argc, argv, force);
+ case MODE_CONVERT_GRAFT_FILE:
+ if (argc != 0)
+ usage_msg_opt("--convert-graft-file takes no argument",
+ git_replace_usage, options);
+ return !!convert_graft_file(force);
+
case MODE_LIST:
if (argc > 1)
usage_msg_opt("only one pattern can be given with -l",
diff --git a/commit.c b/commit.c
index a96b0a27154..1a5e8777617 100644
--- a/commit.c
+++ b/commit.c
@@ -181,7 +181,8 @@ static int read_graft_file(const char *graft_file)
advise(_("Support for <GIT_DIR>/info/grafts is deprecated\n"
"and will be removed in a future Git version.\n" "\n"
- "Please use \"git replace --graft [...]\" instead.\n"
+ "Please use \"git replace --convert-graft-file\"\n"
+ "to convert the grafts into replace refs.\n"
"\n"
"Turn this message off by running\n"
"\"git config advice.graftFileDeprecated false\""));
diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh
index c630aba657e..77ded6df653 100755
--- a/t/t6050-replace.sh
+++ b/t/t6050-replace.sh
@@ -444,4 +444,24 @@ test_expect_success GPG '--graft on a commit with a mergetag' '
git replace -d $HASH10
'
+test_expect_success '--convert-graft-file' '
+ : add and convert graft file &&
+ printf "%s\n%s %s\n%s\n" \
+ $(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \
+ >.git/info/grafts &&
+ git replace --convert-graft-file &&
+ test_path_is_missing .git/info/grafts &&
+
+ : verify that the history is now "grafted" &&
+ git rev-list HEAD >out &&
+ test_line_count = 4 out &&
+
+ : create invalid graft file and verify that it is not deleted &&
+ test_when_finished "rm -f .git/info/grafts" &&
+ echo $EMPTY_BLOB $EMPTY_TREE >.git/info/grafts &&
+ test_must_fail git replace --convert-graft-file 2>err &&
+ grep "$EMPTY_BLOB $EMPTY_TREE" err &&
+ grep "$EMPTY_BLOB $EMPTY_TREE" .git/info/grafts
+'
+
test_done
Signature · 1 line
2.17.0.windows.1.4.g7e4058d72e3
PATCH v2 1/7 replace: "libify" create_graft()
It is quite convenient to simply die() in builtins, in the absence of proper exception handling, because it allows us to just go belly up without having to implement error handling chains.
Of course, for reusable library functions, this is a big no-no, so we (try to) restrict the usage of die() to one-shot commands, i.e. places where we know that the caller does not want to, say, give the user a useful high-level error message, i.e. a message that the function calling die() could not possibly know.
The problem with this reasoning is that sooner or later, pretty much all useful functions will *need* to be libified: the more useful a function, the more likely it is to be called from a call chain where the outer function implements a high-level operation that needs to provide additional advice to the user in case of failure.
This is the case here: the create_graft() function is useful enough to be called in a loop, say, in the upcoming patch to convert a graft file in one fell swoop. Therefore, this function must not be allowed to die(), nor any of its callees.
--- builtin/replace.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)
builtin/replace.c +3 -1
@@ -395,7 +395,9 @@ static int create_graft(int argc, const char **argv, int force)if (get_oid (old_ref, &old_oid) <0 )die (_ ("Not a valid object name: '%s'" ), old_ref); - commit =lookup_commit_or_die (&old_oid, old_ref); + commit =lookup_commit_reference (&old_oid); +if (!commit) +return error (_ ("could not parse %s" ), old_ref); buffer =get_commit_buffer (commit, &size);strbuf_add (&buf, buffer, size);
Signature · 1 line
2.17.0.windows.1.4.g7e4058d72e3
Re: [PATCH v2 1/7] replace: "libify" create_graft()
Quoted 16 lines · Johannes Schindelin
It is quite convenient to simply
die()in builtins, in the absence of proper exception handling, because it allows us to just go belly up without having to implement error handling chains.Of course, for reusable library functions, this is a big no-no, so we (try to) restrict the usage of
die()to one-shot commands, i.e. places where we know that the caller does not want to, say, give the user a useful high-level error message, i.e. a message that the function callingdie()could not possibly know.The problem with this reasoning is that sooner or later, pretty much all useful functions will *need* to be libified: the more useful a function, the more likely it is to be called from a call chain where the outer function implements a high-level operation that needs to provide additional advice to the user in case of failure.
Quoted 4 lines
This is the case here: the
create_graft()function is useful enough to be called in a loop, say, in the upcoming patch to convert a graft file in one fell swoop. Therefore, this function must not be allowed todie(), nor any of its callees.
All of the first three paragraphs are already widely known to the project, and I do not think you need to verbosely repeat it, if brevity demands it.
One thing that the proposed log message for this step would be far more useful to say is that the current callers of create_graft() is fine with the behaviour change (i.e. prepared to act on an error return).
Also it may want to say why the other die() we see in this function in the pre-context is OK. I actually think it is not OK and should return error(not a valid object name) the same way (I suspect that this is mere omission/mistake, and you did not intend to leave it dying). As long as the caller is prepared to deal with an error return, that is, which was the previous point.
FWIW, I fully agree with the goal of making this (or other pieces that would be useful if they were reusable) reusable.
Quoted 20 lines
Signed-off-by Johannes Schindelin <[email protected]>--- builtin/replace.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)builtin/replace.c +3
-1@@ -395,7 +395,9 @@ static int create_graft(int argc, const char **argv, int force)if (get_oid (old_ref, &old_oid) <0 )die (_ ("Not a valid object name: '%s'" ), old_ref); - commit =lookup_commit_or_die (&old_oid, old_ref); + commit =lookup_commit_reference (&old_oid); +if (!commit) +return error (_ ("could not parse %s" ), old_ref); buffer =get_commit_buffer (commit, &size);strbuf_add (&buf, buffer, size);
Re: [PATCH v2 1/7] replace: "libify" create_graft()
Quoted 18 lines · Junio C Hamano
Quoted 16 lines · Johannes Schindelin
It is quite convenient to simply
die()in builtins, in the absence of proper exception handling, because it allows us to just go belly up without having to implement error handling chains.Of course, for reusable library functions, this is a big no-no, so we (try to) restrict the usage of
die()to one-shot commands, i.e. places where we know that the caller does not want to, say, give the user a useful high-level error message, i.e. a message that the function callingdie()could not possibly know.The problem with this reasoning is that sooner or later, pretty much all useful functions will *need* to be libified: the more useful a function, the more likely it is to be called from a call chain where the outer function implements a high-level operation that needs to provide additional advice to the user in case of failure.
Quoted 8 lines
Quoted 4 lines
This is the case here: the
create_graft()function is useful enough to be called in a loop, say, in the upcoming patch to convert a graft file in one fell swoop. Therefore, this function must not be allowed todie(), nor any of its callees.All of the first three paragraphs are already widely known to the project, and I do not think you need to verbosely repeat it, if brevity demands it.
[taking the role of Advocatus Diaboli]
Well, this information (first three paragraphs) is maybe widely known to the project, but do we have it explicitely documented somwehere, like CodingGuidelines, or FAQ on some web site?
Quoted 4 lines
One thing that the proposed log message for this step would be far more useful to say is that the current callers of
create_graft()is fine with the behaviour change (i.e. prepared to act on an error return).
Right.
Quoted 7 lines
Also it may want to say why the other
die()we see in this function in the pre-context is OK. I actually think it is not OK and should return error(not a valid object name) the same way (I suspect that this is mere omission/mistake, and you did not intend to leave it dying). As long as the caller is prepared to deal with an error return, that is, which was the previous point.
[taking the role of Advocatus Diaboli]
Maybe they are errors that caller cannot handle sanely in any way? If I understand things correctly the problem is with parameter to the --graft option...
Quoted 24 lines
FWIW, I fully agree with the goal of making this (or other pieces that would be useful if they were reusable) reusable.
Quoted 20 lines
Signed-off-by Johannes Schindelin <[email protected]>--- builtin/replace.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)builtin/replace.c +3
-1@@ -395,7 +395,9 @@ static int create_graft(int argc, const char **argv, int force)if (get_oid (old_ref, &old_oid) <0 )die (_ ("Not a valid object name: '%s'" ), old_ref); - commit =lookup_commit_or_die (&old_oid, old_ref); + commit =lookup_commit_reference (&old_oid); +if (!commit) +return error (_ ("could not parse %s" ), old_ref); buffer =get_commit_buffer (commit, &size);strbuf_add (&buf, buffer, size);
Re: [PATCH v2 1/7] replace: "libify" create_graft()
In reply to Junio C HamanoHi Junio,
Quoted 2 lines · Junio C Hamano
FWIW, I fully agree with the goal of making this (or other pieces that would be useful if they were reusable) reusable.
Great! That's something I'll gladly take into my break.
Ciao,
Dscho
PATCH v2 2/7 replace: introduce --convert-graft-file
In reply to Johannes SchindelinThis option is intended to help with the transition away from the now-deprecated graft file.
--- Documentation/git-replace.txt | 11 +++++-- builtin/replace.c | 59 ++++++++++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 4 deletions(-)
Documentation/git-replace.txt +8 -3
@@ -11,6 +11,7 @@ SYNOPSIS 'git replace' [-f] <object> <replacement> 'git replace' [-f] --edit <object> 'git replace' [-f] --graft <commit> [<parent>...] +'git replace' [-f] --convert-graft-file 'git replace' -d <object>... 'git replace' [--format=<format>] [-l [<pattern>]]
@@ -87,9 +88,13 @@ OPTIONS content as <commit> except that its parents will be [<parent>...] instead of <commit>'s parents. A replacement ref is then created to replace <commit> with the newly created - commit. See contrib/convert-grafts-to-replace-refs.sh for an - example script based on this option that can convert grafts to - replace refs. + commit. Use `--convert-graft-file` to convert a + `$GIT_DIR/info/grafts` file use replace refs instead. + +--convert-graft-file:: + Creates graft commits for all entries in `$GIT_DIR/info/grafts` + and deletes that file upon success. The purpose is to help users + with transitioning off of the now-deprecated graft file. -l <pattern>:: --list <pattern>::
builtin/replace.c +58 -1
95 lines not on this page · raw message
Signature · 1 line
2.17.0.windows.1.4.g7e4058d72e3
Re: [PATCH v2 2/7] replace: introduce --convert-graft-file
Documentation/git-replace.txt OPTIONS · line 92
Quoted 9 lines · Johannes Schindelin
Documentation/git-replace.txt +2
-3@@ -87,9 +88,13 @@ OPTIONS content as <commit> except that its parents will be [<parent>...] instead of <commit>'s parents. A replacement ref is then created to replace <commit> with the newly created - commit. See contrib/convert-grafts-to-replace-refs.sh for an - example script based on this option that can convert grafts to - replace refs. + commit. Use `--convert-graft-file` to convert a + `$GIT_DIR/info/grafts` file use replace refs instead.
s/file use replace refs/file to use replace refs/
Documentation/git-replace.txt OPTIONS · line 97
Quoted 4 lines
Documentation/git-replace.txt +4
-0+--convert-graft-file:: + Creates graft commits for all entries in `$GIT_DIR/info/grafts` + and deletes that file upon success. The purpose is to help users + with transitioning off of the now-deprecated graft file.
I wonder if it would be better to rename the file to "old_grafts" or something, and perhaps tell the user that we renamed it and it can now be either deleted or moved somewhere else.
Thanks for working on this,
Christian.
Re: [PATCH v2 2/7] replace: introduce --convert-graft-file
Hi Christian,
Quoted 14 lines · Christian Couder
Documentation/git-replace.txt OPTIONS · line 92
Quoted 9 lines · Johannes Schindelin
Documentation/git-replace.txt +2
-3@@ -87,9 +88,13 @@ OPTIONS content as <commit> except that its parents will be [<parent>...] instead of <commit>'s parents. A replacement ref is then created to replace <commit> with the newly created - commit. See contrib/convert-grafts-to-replace-refs.sh for an - example script based on this option that can convert grafts to - replace refs. + commit. Use `--convert-graft-file` to convert a + `$GIT_DIR/info/grafts` file use replace refs instead.s/file use replace refs/file to use replace refs/
Ouch... thanks for catching this!
Quoted 8 lines
Documentation/git-replace.txt OPTIONS · line 97
Quoted 4 lines
Documentation/git-replace.txt +4
-0+--convert-graft-file:: + Creates graft commits for all entries in `$GIT_DIR/info/grafts` + and deletes that file upon success. The purpose is to help users + with transitioning off of the now-deprecated graft file.I wonder if it would be better to rename the file to "
old_grafts" or something, and perhaps tell the user that we renamed it and it can now be either deleted or moved somewhere else.
I do not think so. Those old_grafts will only lie around uselessly... and are no longer necessary.
Ciao,
Dscho
Re: [PATCH v2 2/7] replace: introduce --convert-graft-file
In reply to Johannes SchindelinQuoted 8 lines · Johannes Schindelin
This option is intended to help with the transition away from the now-deprecated graft file.
Signed-off-by Johannes Schindelin <[email protected]>--- Documentation/git-replace.txt | 11 +++++-- builtin/replace.c | 59 ++++++++++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 4 deletions(-)
I expected you to remove convert-grafts-to-replace-refs.sh from the contrib/ section in the same patch, actually. FWIW, I think it is a much better approach to give the first-class UI for transition like this patch does than "go fish for a good way to transition yourself, we may have something useful in contrib/", which is what we had so far.
Documentation/git-replace.txt OPTIONS · line 93
Quoted 12 lines
Documentation/git-replace.txt +3
-3@@ -87,9 +88,13 @@ OPTIONS content as <commit> except that its parents will be [<parent>...] instead of <commit>'s parents. A replacement ref is then created to replace <commit> with the newly created - commit. See contrib/convert-grafts-to-replace-refs.sh for an - example script based on this option that can convert grafts to - replace refs. + commit. Use `--convert-graft-file` to convert a + `$GIT_DIR/info/grafts` file use replace refs instead. +
Nice.
Quoted 15 lines
builtin/replace.c +1
-0@@ -20,6 +20,7 @@ static const char * const git_replace_usage[] = {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>]]" ),NULL @@ -423,6 +424,53 @@ static int create_graft(int argc, const char **argv, int force)return replace_object_oid (old_ref, &old_oid,"replacement" , &new_oid, force); }
builtin/replace.c static int create_graft(int argc, const char **argv, int force) · line 435
Quoted 10 lines
builtin/replace.c +9
-0+static int convert_graft_file (int force) +{ +const char *graft_file =get_graft_file (); +FILE *fp =fopen_or_warn (graft_file,"r" ); +struct strbuf buf =STRBUF_INIT , err =STRBUF_INIT ; +struct argv_array args =ARGV_ARRAY_INIT ; + +if (!fp) +return -1 ;
Returning silently is fine as fopen_or_warn() would have said something already. Good.
builtin/replace.c static int create_graft(int argc, const char **argv, int force) · line 448
Quoted 12 lines
builtin/replace.c +12
-0+while (strbuf_getline (&buf, fp) !=EOF ) { +int i =0 , j; + +while (i != buf.len) { +char save; + +for (j = i; j < buf.len && !isspace (buf.buf[j]); j++) + ;/* look further */ + save = buf.buf[j]; + buf.buf[j] ='\0' ; +argv_array_push (&args, buf.buf + i); + buf.buf[j] = save;
It's a shame that we do not have a helper that splits the contents of a strbuf at SP and shove the result into an argv_array(). [*1*]
*1* There is one that splits into an array of strbuf but the point of splitting is often that these split pieces are the final thing we want, and placing them in separate strbuf (whose strength is that contents are easily manipulatable) is pointless.
builtin/replace.c static int create_graft(int argc, const char **argv, int force) · line 452
Quoted 4 lines
builtin/replace.c +4
-0+ +while (j < buf.len &&isspace (buf.buf[j])) + j++; + i = j;
One difference I notice while comparing this with what is done by contrib/convert-grafts-to-replace-refs.sh is that this does not skip a line that begins with # or SP. I offhand do not know what the point of skipping a line that begins with a SP, but I suspect that skipping a line that begins with "#" is a desirable thing to do, because commit.c::read_graft_line() does know that such a line is a valid comment.
builtin/replace.c static int create_graft(int argc, const char **argv, int force) · line 459
Quoted 7 lines
builtin/replace.c +7
-0+ } + +if (create_graft (args.argc, args.argv, force)) +strbuf_addf (&err,"\n\t%s" , buf.buf); + +argv_array_clear (&args); +strbuf_reset (&buf);
Strictly speaking, this reset is redundant, as getline() will always stuff the line into a fresh buffer (and after the loop there correctly is a release).
builtin/replace.c static int create_graft(int argc, const char **argv, int force) · line 469
Quoted 9 lines
builtin/replace.c +9
-0+ } + +strbuf_release (&buf); +argv_array_clear (&args); + +if (!err.len) +return unlink_or_warn (graft_file); +warning (_ ("could not convert the following graft(s):\n%s" ), err.buf); +strbuf_release (&err);
commit.c::read_graft_file() seems to ignore a broken graft line and salvages other lines, and this one follows suit, which is good.
The remaining die() I pointed out in 1/2 can safely be turned into return error() for this caller (I didn't check for existing callers, though) and would automatically do the right thing. The real consumer of the graft file, commit.c::read_graft_line(), shows an error when oid cannot be parsed, and the above code, when create_graft() is updated to return an error instead of dying, would append the problematic record to buf.buf in the code above.
Looking basically-good modulo minor nits.
Re: [PATCH v2 2/7] replace: introduce --convert-graft-file
Hi Junio,
Quoted 5 lines · Junio C Hamano
Quoted 3 lines · Johannes Schindelin
This option is intended to help with the transition away from the now-deprecated graft file.
Quoted 8 lines
Quoted 5 lines
Signed-off-by Johannes Schindelin <[email protected]>--- Documentation/git-replace.txt | 11 +++++-- builtin/replace.c | 59 ++++++++++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 4 deletions(-)I expected you to remove
convert-grafts-to-replace-refs.shfrom the contrib/ section in the same patch, actually.
Sorry, I was still under the impression that contrib/ was somewhat off limits when replacing scripts by C code (it used to be the rule, but I did see that the contrib/examples/*.sh files went poof).
Will change.
Quoted 4 lines
FWIW, I think it is a much better approach to give the first-class UI for transition like this patch does than "go fish for a good way to transition yourself, we may have something useful in contrib/", which is what we had so far.
Obviously I agree. It just makes for such a vastly superior user experience to get an explanation that yes, something has been deprecated, but Do Not Panic, this is what you do to get out of this fix.
In the same vein, I considered some real hackery that would make the deprecation notice in commit.c less annoying: I wanted to use some tentative date in the future as cut-off, and then use some arithmetic based on the time left until that date to show the deprecation notice increasingly more often.
However, this would have made it really frustrating for users seeing the notice fly by, say, in a lengthy script's output, and then trying to reproduce the message. So as much fun as it would have been to come up with that formula, I refrained from it.
Quoted 16 lines
builtin/replace.c static int create_graft(int argc, const char **argv, int force) · line 448
Quoted 12 lines
builtin/replace.c +12
-0+while (strbuf_getline (&buf, fp) !=EOF ) { +int i =0 , j; + +while (i != buf.len) { +char save; + +for (j = i; j < buf.len && !isspace (buf.buf[j]); j++) + ;/* look further */ + save = buf.buf[j]; + buf.buf[j] ='\0' ; +argv_array_push (&args, buf.buf + i); + buf.buf[j] = save;It's a shame that we do not have a helper that splits the contents of a strbuf at SP and shove the result into an
argv_array(). [*1*]
Quoted 4 lines
*1* There is one that splits into an array of strbuf but the point of splitting is often that these split pieces are the final thing we want, and placing them in separate strbuf (whose strength is that contents are easily manipulatable) is pointless.
FWIW I considered introducing such a helper. But I really want to have the full line to show to the user, so I went with the current version.
Based on your comment, I realized that since argv_array_push() duplicates the string *anyway*, I could have implemented argv_array_split().
Briefly deterred by the insight that some readers will invariably think that this function performs de-quoting, Unix shell style, I almost decided against that.
But only almost.
If anybody needs a de-quoting version of argv_array_split(), or a version that uses a different delimiter than white-space, my version should provide a neat starting point (new parameters should be added for those purposes, of course, since we really need a non-de-quoting version in --convert-graft-file).
So the next iteration of this patch series will sport a shiny new argv_array_split(). Enjoy.
Quoted 5 lines
builtin/replace.c static int create_graft(int argc, const char **argv, int force) · line 452
Quoted 4 lines
builtin/replace.c +4
-0+ +while (j < buf.len &&isspace (buf.buf[j])) + j++; + i = j;
Quoted 7 lines
One difference I notice while comparing this with what is done by
contrib/convert-grafts-to-replace-refs.shis that this does not skip a line that begins with # or SP. I offhand do not know what the point of skipping a line that begins with a SP, but I suspect that skipping a line that begins with "#" is a desirable thing to do, becausecommit.c::read_graft_line()does know that such a line is a valid comment.
Riiiight! I meant to look at the parser to verify that I do the same, but forgot (I had the incorrect recollection that the graft file cannot contain comments or empty lines).
So I remedied that now. Including correct handling of empty lines.
Quoted 11 lines
builtin/replace.c static int create_graft(int argc, const char **argv, int force) · line 459
Quoted 7 lines
builtin/replace.c +7
-0+ } + +if (create_graft (args.argc, args.argv, force)) +strbuf_addf (&err,"\n\t%s" , buf.buf); + +argv_array_clear (&args); +strbuf_reset (&buf);Strictly speaking, this reset is redundant, as
getline()will always stuff the line into a fresh buffer (and after the loop there correctly is a release).
Good point. I did assume that strbuf_getline() would append, and I was wrong. I dropped the needless strbuf_reset() call.
Quoted 4 lines
Quoted 4 lines
builtin/replace.c +4
-0+ } + +strbuf_release (&buf); +argv_array_clear (&args);
Quoted 8 lines
builtin/replace.c static int create_graft(int argc, const char **argv, int force) · line 469
Quoted 5 lines
builtin/replace.c +5
-0+ +if (!err.len) +return unlink_or_warn (graft_file); +warning (_ ("could not convert the following graft(s):\n%s" ), err.buf); +strbuf_release (&err);
commit.c::read_graft_file()seems to ignore a broken graft line and salvages other lines, and this one follows suit, which is good.
Thanks.
Quoted 7 lines
The remaining
die()I pointed out in 1/2 can safely be turned into returnerror()for this caller (I didn't check for existing callers, though) and would automatically do the right thing. The real consumer of the graft file,commit.c::read_graft_line(), shows an error when oid cannot be parsed, and the above code, whencreate_graft()is updated to return an error instead of dying, would append the problematic record to buf.buf in the code above.
It is a *lot* worse than just one remaining die(), unfortunately:create_graft() calls replace_parents() and check_mergetags(), both without checking their return value. Because neither return anything. Because they all die (cue Dash from The Incredibles: We're gonna DIIIIIE!). Also, replace_object_oid(), while it returns an int indicating an error, chooses to die() in plenty of places. After following down to for_each_mergetag(), export_object() and check_ref_valid(), I decided to just go ahead and libify the entire builtin/replace.c, which seemed to be the vastly more efficient route, and I really wish we would start deprecating die(). Thanks for sending me down that rabbit hole ;-)
Note: there is a call to get_commit_buffer() which still die()s if it cannot read the object or if it is not a commit. If we care enough about the concocted scenario where somebody would have installed a graft file with a line that references, say, a tree object instead of a commit object in its first oid, we need to do something about that: the graft line would simply have been ignored, as grafts were only used when a corresponding commit was parsed (which would never happen for the concocted example I gave). I am rather certain, though, that I do not want to care about such a scenario: a user would have to go out of their way to get into that situation.
Ciao,
Dscho
PATCH v2 3/7 Add a test for `git replace --convert-graft-file`
In reply to Johannes SchindelinThe proof, as the saying goes, lies in the pudding. So here is a regression test that not only demonstrates what the option is supposed to accomplish, but also demonstrates that it does accomplish it.
--- t/t6050-replace.sh | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+)
t/t6050-replace.sh +20 -0
@@ -444,4 +444,24 @@ test_expect_success GPG '--graft on a commit with a mergetag' ' git replace -d $HASH10 ' +test_expect_success '--convert-graft-file' ' + : add and convert graft file && + printf "%s\n%s %s\n%s\n" \ + $(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \ + >.git/info/grafts && + git replace --convert-graft-file && + test_path_is_missing .git/info/grafts && + + : verify that the history is now "grafted" && + git rev-list HEAD >out && + test_line_count = 4 out && + + : create invalid graft file and verify that it is not deleted && + test_when_finished "rm -f .git/info/grafts" && + echo $EMPTY_BLOB $EMPTY_TREE >.git/info/grafts && + test_must_fail git replace --convert-graft-file 2>err && + grep "$EMPTY_BLOB $EMPTY_TREE" err && + grep "$EMPTY_BLOB $EMPTY_TREE" .git/info/grafts +' + test_done
Signature · 1 line
2.17.0.windows.1.4.g7e4058d72e3
Re: [PATCH v2 3/7] Add a test for `git replace --convert-graft-file`
Quoted 3 lines · Johannes Schindelin
The proof, as the saying goes, lies in the pudding. So here is a regression test that not only demonstrates what the option is supposed to accomplish, but also demonstrates that it does accomplish it.
The above spreads the misconception that the value of the test is "what I wrote works, look here". It is not. "Here is how this thing is supposed to work. You are free to improve it, but do not break the basic promises these tests outline" to protect the resulting system is.
Quoted 10 lines
Signed-off-by Johannes Schindelin <[email protected]>--- t/t6050-replace.sh | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+)t/t6050-replace.sh +0
-0
t/t6050-replace.sh test_expect_success GPG '--graft on a commit with a mergetag' ' · line 451
Quoted 8 lines
t/t6050-replace.sh +5
-0@@ -444,4 +444,24 @@ test_expect_success GPG '--graft on a commit with a mergetag' ' git replace -d $HASH10 ' +test_expect_success '--convert-graft-file' ' + : add and convert graft file && + printf "%s\n%s %s\n%s\n" \ + $(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \ + >.git/info/grafts &&
I find the above much less readbale than something like
{
git rev-parse HEAD^^
git rev-parse HEAD^ HEAD^^
git rev-parse HEAD^2
} >.git/info/grafts
because printf formatting string must be deciphered and then matched against the order and number of rev-parse arguments (and printf's ability to happily accept more args than the placeholders does not help in readablity---the reader needs to verify that the code is not doing anything overly clever exploiting that ability) before I can figure out who's parent of whom.
Of course, it saves a few spawning of subprocesses; I am not sure if that is worth the loss of readability in this case, though.
t/t6050-replace.sh test_expect_success GPG '--graft on a commit with a mergetag' ' · line 453
Quoted 2 lines
t/t6050-replace.sh +2
-0+ git replace --convert-graft-file && + test_path_is_missing .git/info/grafts &&
Successful conversion exits with success, and removes the grafts file. Good.
t/t6050-replace.sh test_expect_success GPG '--graft on a commit with a mergetag' ' · line 457
Quoted 3 lines
t/t6050-replace.sh +3
-0+ : verify that the history is now "grafted" && + git rev-list HEAD >out && + test_line_count = 4 out &&
We are pretending that HEAD^ has only one parent HEAD^^ and that HEAD^^ and HEAD^2 are roots with the above graft. We want the resulting "apparent" history to also show that fact, and the traversal should stop after counting HEAD, HEAD^, HEAD^2 (which is root), and HEAD^^ (which is also root). Good.
Quoted 9 lines
t/t6050-replace.sh +8
-0+ : create invalid graft file and verify that it is not deleted && + test_when_finished "rm -f .git/info/grafts" && + echo $EMPTY_BLOB $EMPTY_TREE >.git/info/grafts && + test_must_fail git replace --convert-graft-file 2>err && + grep "$EMPTY_BLOB $EMPTY_TREE" err && + grep "$EMPTY_BLOB $EMPTY_TREE" .git/info/grafts +' + test_done
Re: [PATCH v2 3/7] Add a test for `git replace --convert-graft-file`
Hi Junio,
Quoted 11 lines · Junio C Hamano
Quoted 3 lines · Johannes Schindelin
The proof, as the saying goes, lies in the pudding. So here is a regression test that not only demonstrates what the option is supposed to accomplish, but also demonstrates that it does accomplish it.
The above spreads the misconception that the value of the test is "what I wrote works, look here". It is not. "Here is how this thing is supposed to work. You are free to improve it, but do not break the basic promises these tests outline" to protect the resulting system is.
I am but a lousy foreigner, but to me, basically we said the same.
Quoted 16 lines
t/t6050-replace.sh test_expect_success GPG '--graft on a commit with a mergetag' ' · line 451
Quoted 13 lines
t/t6050-replace.sh +5
-0@@ -444,4 +444,24 @@ test_expect_success GPG '--graft on a commit with a mergetag' ' git replace -d $HASH10 ' +test_expect_success '--convert-graft-file' ' + : add and convert graft file && + printf "%s\n%s %s\n%s\n" \ + $(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \ + >.git/info/grafts &&I find the above much less readbale than something like
Quoted 5 lines
{
git rev-parse HEAD^^
git rev-parse HEAD^ HEAD^^
git rev-parse HEAD^2
} >.git/info/grafts
Well, don't you know, that is how my first version looked. It failed, though, as git rev-parse HEAD^ HEAD^^ outputs two *lines*.
And the version with a here-doc and inlined $(git rev-parse ...) really looks even uglier.
Quoted 9 lines
because printf formatting string must be deciphered and then matched against the order and number of rev-parse arguments (and printf's ability to happily accept more args than the placeholders does not help in readablity---the reader needs to verify that the code is not doing anything overly clever exploiting that ability) before I can figure out who's parent of whom.
Of course, it saves a few spawning of subprocesses; I am not sure if that is worth the loss of readability in this case, though.
I disagree that it is so horrible to read. If a regression occurs, you will have the .git/info/grafts file as a reference anyway, so there is little you need to decipher.
Ciao,
Dscho
PATCH v2 4/7 Deprecate support for .git/info/grafts
In reply to Johannes SchindelinThe grafts feature was a convenient way to "stitch together" ancient history to the fresh start of linux.git.
Its implementation is, however, not up to Git's standards, as there are too many ways where it can lead to surprising and unwelcome behavior.
For example, when pushing from a repository with active grafts, it is possible to miss commits that have been "grafted out", resulting in a broken state on the other side.
Also, the grafts feature is limited to "rewriting" commits' list of parents, it cannot replace anything else.
The much younger feature implemented as git replace set out to remedy those limitations and dangerous bugs.
Seeing as git replace is pretty mature by now (since 4228e8bc98 (replace: add --graft option, 2014-07-19) it can perform the graft file's duties), it is time to deprecate support for the graft file, and to retire it eventually.
--- advice.c | 2 ++ advice.h | 1 + commit.c | 10 ++++++++++ t/t6001-rev-list-graft.sh | 9 +++++++++ 4 files changed, 22 insertions(+)
advice.c +2 -0
@@ -19,6 +19,7 @@ int advice_rm_hints = 1;int advice_add_embedded_repo =1 ;int advice_ignored_hook =1 ;int advice_waiting_for_editor =1 ; +int advice_graft_file_deprecated =1 ; static struct { const char *name;
@@ -42,6 +43,7 @@ static struct { { "addembeddedrepo", &advice_add_embedded_repo }, { "ignoredhook", &advice_ignored_hook }, { "waitingforeditor", &advice_waiting_for_editor }, + { "graftfiledeprecated", &advice_graft_file_deprecated },/* make this an alias for backward compatibility */ { "pushnonfastforward", &advice_push_update_rejected }
advice.h +1 -0
@@ -21,6 +21,7 @@ extern int advice_rm_hints;extern int advice_add_embedded_repo;extern int advice_ignored_hook;extern int advice_waiting_for_editor; +extern int advice_graft_file_deprecated;int git_default_advice_config (const char *var,const char *value); __attribute__((format (printf, 1, 2)))
commit.c +10 -0
@@ -12,6 +12,7 @@#include "prio-queue.h" #include "sha1-lookup.h" #include "wt-status.h" +#include "advice.h" static struct commit_extra_header *read_commit_extra_header_lines (const char *buf,size_t len,const char **);
@@ -176,6 +177,15 @@ static int read_graft_file(const char *graft_file)struct strbuf buf =STRBUF_INIT ;if (!fp)return -1 ; +if (advice_graft_file_deprecated) +advise (_ ("Support for <GIT_DIR>/info/grafts is deprecated\n" +"and will be removed in a future Git version.\n" +"\n" +"Please use \"git replace --convert-graft-file\"\n" +"to convert the grafts into replace refs.\n" +"\n" +"Turn this message off by running\n" +"\"git config advice.graftFileDeprecated false\"" ));while (!strbuf_getwholeline (&buf, fp,'\n' )) {/* The format is just "Commit Parent1 Parent2 ...\n" */ struct commit_graft *graft =read_graft_line (&buf);
t/t6001-rev-list-graft.sh +9 -0
@@ -110,4 +110,13 @@ do " done + +test_expect_success 'show advice that grafts are deprecated' ' + git show HEAD 2>err && + test_i18ngrep "git replace" err && + test_config advice.graftFileDeprecated false && + git show HEAD 2>err && + test_i18ngrep ! "git replace" err +' + test_done
Signature · 1 line
2.17.0.windows.1.4.g7e4058d72e3
PATCH v2 5/7 filter-branch: stop suggesting to use grafts
In reply to Johannes SchindelinThe graft file is deprecated now, so let's use replace refs in the example in filter-branch's man page instead.
--- Documentation/git-filter-branch.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
Documentation/git-filter-branch.txt +1 -1
@@ -288,7 +288,7 @@ git filter-branch --parent-filter \ or even simpler: ----------------------------------------------- -echo "$commit-id $graft-id" >> .git/info/grafts +git replace --graft $commit-id $graft-id git filter-branch $graft-id..HEAD -----------------------------------------------
Signature · 1 line
2.17.0.windows.1.4.g7e4058d72e3
PATCH v2 6/7 technical/shallow: describe the relationship with replace refs
In reply to Johannes SchindelinNow that grafts are deprecated, we should start to assume that readers have no idea what grafts are. So it makes more sense to describe the "shallow" feature in terms of replace refs.
--- Documentation/technical/shallow.txt | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-)
Documentation/technical/shallow.txt +11 -8
@@ -9,14 +9,17 @@ these commits have no parents. ********************************************************* The basic idea is to write the SHA-1s of shallow commits into -$GIT_DIR/shallow, and handle its contents like the contents -of $GIT_DIR/info/grafts (with the difference that shallow -cannot contain parent information). - -This information is stored in a new file instead of grafts, or -even the config, since the user should not touch that file -at all (even throughout development of the shallow clone, it -was never manually edited!). +$GIT_DIR/shallow, and handle its contents similar to replace +refs (with the difference that shallow does not actually +create those replace refs) and very much like the deprecated +graft file (with the difference that shallow commits will +always have their parents grafted away, not replaced by +different parents). + +This information is stored in a special-purpose file because the +user should not touch that file at all (even throughout +development of the shallow clone, it was never manually +edited!). Each line contains exactly one SHA-1. When read, a commit_graft will be constructed, which has nr_parent < 0 to make it easier
Signature · 1 line
2.17.0.windows.1.4.g7e4058d72e3
PATCH v2 7/7 technical/shallow: describe why shallow cannot use replace refs
In reply to Johannes SchindelinIt is tempting to do away with commit_graft altogether (in the long haul), now that grafts are deprecated.
However, the shallow feature needs a couple of things that the replace refs cannot fulfill. Let's point that out in the documentation.
---
By the way, I did not squash this into the previous patch on
purpose: in my mind, describing the relationship with replace refs is different enough from pointing out from the goal of this here patch to merit a separate commit. And that goal is: to describe why "shallow" cannot be implemented on top of "replace refs".
Documentation/technical/shallow.txt | 7 +++++++ 1 file changed, 7 insertions(+)
Documentation/technical/shallow.txt +7 -0
@@ -25,6 +25,13 @@ Each line contains exactly one SHA-1. When read, a commit_graft will be constructed, which has nr_parent < 0 to make it easier to discern from user provided grafts. +Note that the shallow feature could not be changed easily to +use replace refs: a commit containing a `mergetag` is not allowed +to be replaced, not even by a root commit. Such a commit can be +made shallow, though. Also, having a `shallow` file explicitly +listing all the commits made shallow makes it a *lot* easier to +do shallow-specific things such as to deepen the history. + Since fsck-objects relies on the library to read the objects, it honours shallow commits automatically.
Signature · 1 line
2.17.0.windows.1.4.g7e4058d72e3
v3 cover Deprecate .git/info/grafts
In reply to Johannes SchindelinIt is fragile, as there is no way for the revision machinery to say "but now I want to traverse the graph ignoring the graft file" e.g. when pushing commits to a remote repository (which, as a consequence, can miss commits).
And we already have a better solution with git replace --graft <comit> [<parent>...].
Changes since v2:
- Fixed tyop in the description of --graft where it suggests --convert-graft-file instead.
- Dropped the rant from the "libify create_graft()" commit.
- Changed a die("BUG: ...") call to BUG() (instead of libifying it).
- Truly libified create_graft() by replacing every single instance where die() (or lookup_commit_or_die()) was called with a proper error(), adding resource handling to avoid leaks, so that that rant that was dropped from the commit message would now truly be merited.
- Graft files with commented lines are now handled correctly.
- Graft files with empty lines are now handled correctly, too.
- The graft file parser is now based on a shiny new argv_array_split().
- The script in contrib/ whose functionality is superseded by git replace --convert-graft-file was removed.
Johannes Schindelin (11):
argv_array: offer to split a string by whitespace
commit: Let the callback of for_each_mergetag return on error
replace: avoid using die() to indicate a bug
replace: "libify" create_graft() and callees
replace: introduce --convert-graft-file
Add a test for git replace --convert-graft-file Deprecate support for .git/info/grafts
filter-branch: stop suggesting to use grafts technical/shallow: describe the relationship with replace refs technical/shallow: describe why shallow cannot use replace refs Remove obsolete script to convert grafts to replace refs
Documentation/git-filter-branch.txt | 2 +- Documentation/git-replace.txt | 11 +- Documentation/technical/shallow.txt | 24 ++- advice.c | 2 + advice.h | 1 + argv-array.c | 20 +++ argv-array.h | 1 + builtin/replace.c | 189 +++++++++++++++------- commit.c | 18 ++- commit.h | 4 +- contrib/convert-grafts-to-replace-refs.sh | 28 ---- log-tree.c | 13 +- t/t6001-rev-list-graft.sh | 9 ++ t/t6050-replace.sh | 20 +++ 14 files changed, 235 insertions(+), 107 deletions(-) delete mode 100755 contrib/convert-grafts-to-replace-refs.sh
base-commit: fe0a9eaf31dd0c349ae4308498c33a5c3794b293
Published-As: https://github.com/dscho/git/releases/tag/deprecate-grafts-v3
Fetch-It-Via: git fetch https://github.com/dscho/git deprecate-grafts-v3
Interdiff vs v2:
diff --git a/Documentation/git-replace.txt b/Documentation/git-replace.txt index 4dc0686f7d6..246dc9943c2 100644
--- a/Documentation/git-replace.txt
+++ b/Documentation/git-replace.txt
@@ -89,7 +89,7 @@ OPTIONS
[<parent>...] instead of <commit>'s parents. A replacement ref is then created to replace <commit> with the newly created commit. Use --convert-graft-file to convert a
- $GIT_DIR/info/grafts file use replace refs instead.
+ $GIT_DIR/info/grafts file and use replace refs instead.
--convert-graft-file::
Creates graft commits for all entries in $GIT_DIR/info/grafts
diff --git a/argv-array.c b/argv-array.c
index 5d370fa3366..cb5bcd2c064 100644
--- a/argv-array.c
+++ b/argv-array.c
@@ -64,6 +64,26 @@ void argv_array_pop(struct argv_array *array)
array->argc--;
}
+void argv_array_split(struct argv_array *array, const char *to_split) +{
+ while (isspace(*to_split))
+ to_split++;
+ for (;;) {
+ const char *p = to_split;
+
+ if (!*p)
+ break;
+
+ while (*p && !isspace(*p))
+ p++;
+ argv_array_push_nodup(array, xstrndup(to_split, p - to_split));
+
+ while (isspace(*p))
+ p++;
+ to_split = p;
+ }
+}
+
void argv_array_clear(struct argv_array *array)
{
if (array->argv != empty_argv) {
diff --git a/argv-array.h b/argv-array.h
index 29056e49a12..c7c397695df 100644
--- a/argv-array.h
+++ b/argv-array.h
@@ -19,6 +19,7 @@ LAST_ARG_MUST_BE_NULL
void argv_array_pushl(struct argv_array *, ...);
void argv_array_pushv(struct argv_array *, const char **); void argv_array_pop(struct argv_array *);
+void argv_array_split(struct argv_array *, const char *);
void argv_array_clear(struct argv_array *);
const char **argv_array_detach(struct argv_array *);
diff --git a/builtin/replace.c b/builtin/replace.c
index 4cdc00a96df..6b3e0301e90 100644
--- a/builtin/replace.c
+++ b/builtin/replace.c
@@ -80,9 +80,9 @@ static int list_replace_refs(const char *pattern, const char *format)
else if (!strcmp(format, "long"))
data.format = REPLACE_FORMAT_LONG;
else
- die("invalid replace format '%s'\n"
- "valid formats are 'short', 'medium' and 'long'\n",
- format);
+ return error("invalid replace format '%s'\n"
+ "valid formats are 'short', 'medium' and 'long'\n",
+ format);
for_each_replace_ref(show_reference, (void *)&data);
@@ -135,7 +135,7 @@ static int delete_replace_ref(const char *name, const char *ref,
return 0;
}
-static void check_ref_valid(struct object_id *object,
+static int check_ref_valid(struct object_id *object,
struct object_id *prev,
struct strbuf *ref,
int force)
@@ -143,12 +143,13 @@ static void check_ref_valid(struct object_id *object,
strbuf_reset(ref);
strbuf_addf(ref, "%s%s", git_replace_ref_base, oid_to_hex(object)); if (check_refname_format(ref->buf, 0))
- die("'%s' is not a valid ref name.", ref->buf);
+ return error("'%s' is not a valid ref name.", ref->buf);
if (read_ref(ref->buf, prev))
oidclr(prev);
else if (!force)
- die("replace ref '%s' already exists", ref->buf);
+ return error("replace ref '%s' already exists", ref->buf);
+ return 0;
}
static int replace_object_oid(const char *object_ref,
@@ -162,28 +163,31 @@ static int replace_object_oid(const char *object_ref,
struct strbuf ref = STRBUF_INIT;
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
+ int res = 0;
obj_type = oid_object_info(object, NULL);
repl_type = oid_object_info(repl, NULL);
if (!force && obj_type != repl_type)
- die("Objects must be of the same type.\n"
- "'%s' points to a replaced object of type '%s'\n"
- "while '%s' points to a replacement object of type '%s'.",
- object_ref, type_name(obj_type),
- replace_ref, type_name(repl_type));
-
- check_ref_valid(object, &prev, &ref, force);
+ return error("Objects must be of the same type.\n"
+ "'%s' points to a replaced object of type '%s'\n"
+ "while '%s' points to a replacement object of "
+ "type '%s'.",
+ object_ref, type_name(obj_type),
+ replace_ref, type_name(repl_type)); +
+ if (check_ref_valid(object, &prev, &ref, force))
+ return -1;
transaction = ref_transaction_begin(&err);
if (!transaction ||
ref_transaction_update(transaction, ref.buf, repl, &prev,
0, NULL, &err) ||
ref_transaction_commit(transaction, &err))
- die("%s", err.buf);
+ res = error("%s", err.buf);
ref_transaction_free(transaction);
strbuf_release(&ref);
- return 0;
+ return res;
}
static int replace_object(const char *object_ref, const char *replace_ref, int force)
@@ -191,9 +195,11 @@ static int replace_object(const char *object_ref, const char *replace_ref, int f
struct object_id object, repl;
if (get_oid(object_ref, &object))
- die("Failed to resolve '%s' as a valid ref.", object_ref);
+ return error("Failed to resolve '%s' as a valid ref.",
+ object_ref);
if (get_oid(replace_ref, &repl))
- die("Failed to resolve '%s' as a valid ref.", replace_ref);
+ return error("Failed to resolve '%s' as a valid ref.",
+ replace_ref);
return replace_object_oid(object_ref, &object, replace_ref, &repl, force);
}
@@ -203,7 +209,7 @@ static int replace_object(const char *object_ref, const char *replace_ref, int f
* If "raw" is true, then the object's raw contents are printed according to
* "type". Otherwise, we pretty-print the contents for human editing.
*/
-static void export_object(const struct object_id *oid, enum object_type type, +static int export_object(const struct object_id *oid, enum object_type type,
int raw, const char *filename)
{
struct child_process cmd = CHILD_PROCESS_INIT;
@@ -211,7 +217,7 @@ static void export_object(const struct object_id *oid, enum object_type type,
fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd < 0)
- die_errno("unable to open %s for writing", filename);
+ return error_errno("unable to open %s for writing", filename);
argv_array_push(&cmd.args, "--no-replace-objects"); argv_array_push(&cmd.args, "cat-file");
@@ -224,7 +230,8 @@ static void export_object(const struct object_id *oid, enum object_type type,
cmd.out = fd;
if (run_command(&cmd))
- die("cat-file reported failure");
+ return error("cat-file reported failure");
+ return 0;
}
/*
@@ -232,14 +239,14 @@ static void export_object(const struct object_id *oid, enum object_type type,
* interpreting it as "type", and writing the result to the object database.
* The sha1 of the written object is returned via sha1.
*/
-static void import_object(struct object_id *oid, enum object_type type,
+static int import_object(struct object_id *oid, enum object_type type,
int raw, const char *filename)
{
int fd;
fd = open(filename, O_RDONLY);
if (fd < 0)
- die_errno("unable to open %s for reading", filename);
+ return error_errno("unable to open %s for reading", filename);
if (!raw && type == OBJ_TREE) {
const char *argv[] = { "mktree", NULL };
@@ -251,27 +258,38 @@ static void import_object(struct object_id *oid, enum object_type type,
cmd.in = fd;
cmd.out = -1;
- if (start_command(&cmd))
- die("unable to spawn mktree");
+ if (start_command(&cmd)) {
+ close(fd);
+ return error("unable to spawn mktree");
+ }
- if (strbuf_read(&result, cmd.out, 41) < 0)
- die_errno("unable to read from mktree");
+ if (strbuf_read(&result, cmd.out, 41) < 0) {
+ close(fd);
+ close(cmd.out);
+ return error_errno("unable to read from mktree");
+ }
close(cmd.out);
- if (finish_command(&cmd))
- die("mktree reported failure");
- if (get_oid_hex(result.buf, oid) < 0)
- die("mktree did not return an object name");
+ if (finish_command(&cmd)) {
+ strbuf_release(&result);
+ return error("mktree reported failure");
+ }
+ if (get_oid_hex(result.buf, oid) < 0) {
+ strbuf_release(&result);
+ return error("mktree did not return an object name");
+ }
strbuf_release(&result);
} else {
struct stat st;
int flags = HASH_FORMAT_CHECK | HASH_WRITE_OBJECT; - if (fstat(fd, &st) < 0)
- die_errno("unable to fstat %s", filename);
+ if (fstat(fd, &st) < 0) {
+ close(fd);
+ return error_errno("unable to fstat %s", filename);
+ }
if (index_fd(oid, fd, &st, type, NULL, flags) < 0)
- die("unable to write object to database");
+ return error("unable to write object to database");
/* index_fd close()s fd for us */
}
@@ -279,6 +297,7 @@ static void import_object(struct object_id *oid, enum object_type type,
* No need to close(fd) here; both run-command and index-fd
* will have done it for us.
*/
+ return 0;
}
static int edit_and_replace(const char *object_ref, int force, int raw)
@@ -289,19 +308,23 @@ static int edit_and_replace(const char *object_ref, int force, int raw)
struct strbuf ref = STRBUF_INIT;
if (get_oid(object_ref, &old_oid) < 0)
- die("Not a valid object name: '%s'", object_ref);
+ return error("Not a valid object name: '%s'", object_ref);
type = oid_object_info(&old_oid, NULL);
if (type < 0)
- die("unable to get object type for %s", oid_to_hex(&old_oid));
+ return error("unable to get object type for %s",
+ oid_to_hex(&old_oid));
- check_ref_valid(&old_oid, &prev, &ref, force);
+ if (check_ref_valid(&old_oid, &prev, &ref, force))
+ return -1;
strbuf_release(&ref);
- export_object(&old_oid, type, raw, tmpfile);
+ if (export_object(&old_oid, type, raw, tmpfile))
+ return -1;
if (launch_editor(tmpfile, NULL, NULL) < 0)
- die("editing object file failed");
- import_object(&new_oid, type, raw, tmpfile);
+ return error("editing object file failed");
+ if (import_object(&new_oid, type, raw, tmpfile))
+ return -1;
free(tmpfile);
@@ -311,7 +334,7 @@ static int edit_and_replace(const char *object_ref, int force, int raw)
return replace_object_oid(object_ref, &old_oid, "replacement", &new_oid, force);
}
-static void replace_parents(struct strbuf *buf, int argc, const char **argv) +static int replace_parents(struct strbuf *buf, int argc, const char **argv)
{
struct strbuf new_parents = STRBUF_INIT;
const char *parent_start, *parent_end;
@@ -329,8 +352,10 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv)
for (i = 0; i < argc; i++) {
struct object_id oid;
if (get_oid(argv[i], &oid) < 0)
- die(_("Not a valid object name: '%s'"), argv[i]);
- lookup_commit_or_die(&oid, argv[i]);
+ return error(_("Not a valid object name: '%s'"),
+ argv[i]);
+ if (!lookup_commit_reference(&oid))
+ return error(_("could not parse %s"), argv[i]);
strbuf_addf(&new_parents, "parent %s\n", oid_to_hex(&oid));
}
@@ -339,6 +364,7 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv)
new_parents.buf, new_parents.len);
strbuf_release(&new_parents);
+ return 0;
}
struct check_mergetag_data {
@@ -346,7 +372,7 @@ struct check_mergetag_data {
const char **argv;
};
-static void check_one_mergetag(struct commit *commit,
+static int check_one_mergetag(struct commit *commit,
struct commit_extra_header *extra, void *data)
{
@@ -359,30 +385,32 @@ static void check_one_mergetag(struct commit *commit,
hash_object_file(extra->value, extra->len, type_name(OBJ_TAG), &tag_oid); tag = lookup_tag(&tag_oid);
if (!tag)
- die(_("bad mergetag in commit '%s'"), ref);
+ return error(_("bad mergetag in commit '%s'"), ref);
if (parse_tag_buffer(tag, extra->value, extra->len))
- die(_("malformed mergetag in commit '%s'"), ref);
+ return error(_("malformed mergetag in commit '%s'"), ref);
/* iterate over new parents */
for (i = 1; i < mergetag_data->argc; i++) {
struct object_id oid;
if (get_oid(mergetag_data->argv[i], &oid) < 0)
- die(_("Not a valid object name: '%s'"), mergetag_data->argv[i]);
+ return error(_("Not a valid object name: '%s'"),
+ mergetag_data->argv[i]);
if (!oidcmp(&tag->tagged->oid, &oid))
- return; /* found */
+ return 0; /* found */
}
- die(_("original commit '%s' contains mergetag '%s' that is discarded; "
- "use --edit instead of --graft"), ref, oid_to_hex(&tag_oid));
+ return error(_("original commit '%s' contains mergetag '%s' that is "
+ "discarded; use --edit instead of --graft"), ref,
+ oid_to_hex(&tag_oid));
}
-static void check_mergetags(struct commit *commit, int argc, const char **argv) +static int check_mergetags(struct commit *commit, int argc, const char **argv)
{
struct check_mergetag_data mergetag_data;
mergetag_data.argc = argc;
mergetag_data.argv = argv;
- for_each_mergetag(check_one_mergetag, commit, &mergetag_data);
+ return for_each_mergetag(check_one_mergetag, commit, &mergetag_data);
}
static int create_graft(int argc, const char **argv, int force)
@@ -395,7 +423,7 @@ static int create_graft(int argc, const char **argv, int force)
unsigned long size;
if (get_oid(old_ref, &old_oid) < 0)
- die(_("Not a valid object name: '%s'"), old_ref);
+ return error(_("Not a valid object name: '%s'"), old_ref);
commit = lookup_commit_reference(&old_oid);
if (!commit)
return error(_("could not parse %s"), old_ref);
@@ -404,17 +432,20 @@ static int create_graft(int argc, const char **argv, int force)
strbuf_add(&buf, buffer, size);
unuse_commit_buffer(commit, buffer);
- replace_parents(&buf, argc - 1, &argv[1]);
+ if (replace_parents(&buf, argc - 1, &argv[1]) < 0)
+ return -1;
if (remove_signature(&buf)) {
warning(_("the original commit '%s' has a gpg signature."), old_ref); warning(_("the signature will be removed in the replacement commit!"));
}
- check_mergetags(commit, argc, argv);
+ if (check_mergetags(commit, argc, argv))
+ return -1;
if (write_object_file(buf.buf, buf.len, commit_type, &new_oid))
- die(_("could not write replacement commit for: '%s'"), old_ref);
+ return error(_("could not write replacement commit for: '%s'"),
+ old_ref);
strbuf_release(&buf);
@@ -435,28 +466,13 @@ static int convert_graft_file(int force)
return -1;
while (strbuf_getline(&buf, fp) != EOF) {
- int i = 0, j;
-
- while (i != buf.len) {
- char save;
-
- for (j = i; j < buf.len && !isspace(buf.buf[j]); j++)
- ; /* look further */
- save = buf.buf[j];
- buf.buf[j] = '\0';
- argv_array_push(&args, buf.buf + i);
- buf.buf[j] = save;
-
- while (j < buf.len && isspace(buf.buf[j]))
- j++;
- i = j;
- }
+ if (*buf.buf == '#')
+ continue;
- if (create_graft(args.argc, args.argv, force))
+ argv_array_split(&args, buf.buf);
+ if (args.argc && create_graft(args.argc, args.argv, force))
strbuf_addf(&err, "\n\t%s", buf.buf);
-
argv_array_clear(&args);
- strbuf_reset(&buf);
}
strbuf_release(&buf);
@@ -560,6 +576,6 @@ int cmd_replace(int argc, const char **argv, const char *prefix)
return list_replace_refs(argv[0], format);
default:
- die("BUG: invalid cmdmode %d", (int)cmdmode);
+ BUG("invalid cmdmode %d", (int)cmdmode);
}
}
diff --git a/commit.c b/commit.c
index 1a5e8777617..451d3ce8dfe 100644
--- a/commit.c
+++ b/commit.c
@@ -1298,17 +1298,19 @@ struct commit_extra_header *read_commit_extra_headers(struct commit *commit,
return extra;
}
-void for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data) +int for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data)
{
struct commit_extra_header *extra, *to_free;
+ int res = 0;
to_free = read_commit_extra_headers(commit, NULL);
- for (extra = to_free; extra; extra = extra->next) {
+ for (extra = to_free; !res && extra; extra = extra->next) {
if (strcmp(extra->key, "mergetag"))
continue; /* not a merge tag */
- fn(commit, extra, data);
+ res = fn(commit, extra, data);
}
free_commit_extra_headers(to_free);
+ return res;
}
static inline int standard_header_field(const char *field, size_t len)
diff --git a/commit.h b/commit.h
index 0fb8271665c..9000895ad91 100644
--- a/commit.h
+++ b/commit.h
@@ -291,10 +291,10 @@ extern const char *find_commit_header(const char *msg, const char *key,
/* Find the end of the log message, the right place for a new trailer. */ extern int ignore_non_trailer(const char *buf, size_t len);
-typedef void (*each_mergetag_fn)(struct commit *commit, struct commit_extra_header *extra, +typedef int (*each_mergetag_fn)(struct commit *commit, struct commit_extra_header *extra,
void *cb_data);
-extern void for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data); +extern int for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data);
struct merge_remote_desc {
struct object *obj; /* the named object, could be a tag */
diff --git a/contrib/convert-grafts-to-replace-refs.sh b/contrib/convert-grafts-to-replace-refs.sh deleted file mode 100755
index 0cbc917b8cf..00000000000
--- a/contrib/convert-grafts-to-replace-refs.sh
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/sh
-
-# You should execute this script in the repository where you
-# want to convert grafts to replace refs.
-
-GRAFTS_FILE="${GIT_DIR:-.git}/info/grafts"
-
-. $(git --exec-path)/git-sh-setup
-
-test -f "$GRAFTS_FILE" || die "Could not find graft file: '$GRAFTS_FILE'"
-
-grep '^[^# ]' "$GRAFTS_FILE" |
-while read definition
-do
- if test -n "$definition"
- then
- echo "Converting: $definition"
- git replace --graft $definition ||
- die "Conversion failed for: $definition"
- fi
-done
-
-mv "$GRAFTS_FILE" "$GRAFTS_FILE.bak" ||
- die "Could not rename '$GRAFTS_FILE' to '$GRAFTS_FILE.bak'"
-
-echo "Success!"
-echo "All the grafts in '$GRAFTS_FILE' have been converted to replace refs!" -echo "The grafts file '$GRAFTS_FILE' has been renamed: '$GRAFTS_FILE.bak'"
diff --git a/log-tree.c b/log-tree.c
index d1c0bedf244..f3a51a6e726 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -488,9 +488,9 @@ static int is_common_merge(const struct commit *commit)
&& !commit->parents->next->next);
}
-static void show_one_mergetag(struct commit *commit,
- struct commit_extra_header *extra,
- void *data)
+static int show_one_mergetag(struct commit *commit,
+ struct commit_extra_header *extra,
+ void *data)
{
struct rev_info *opt = (struct rev_info *)data;
struct object_id oid;
@@ -502,7 +502,7 @@ static void show_one_mergetag(struct commit *commit,
hash_object_file(extra->value, extra->len, type_name(OBJ_TAG), &oid); tag = lookup_tag(&oid);
if (!tag)
- return; /* error message already given */
+ return -1; /* error message already given */
strbuf_init(&verify_message, 256);
if (parse_tag_buffer(tag, extra->value, extra->len))
@@ -536,11 +536,12 @@ static void show_one_mergetag(struct commit *commit,
show_sig_lines(opt, status, verify_message.buf); strbuf_release(&verify_message);
+ return 0;
}
-static void show_mergetag(struct rev_info *opt, struct commit *commit) +static int show_mergetag(struct rev_info *opt, struct commit *commit)
{
- for_each_mergetag(show_one_mergetag, commit, opt);
+ return for_each_mergetag(show_one_mergetag, commit, opt);
}
void show_log(struct rev_info *opt)
diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh
index 77ded6df653..8a3ee7c3db9 100755
--- a/t/t6050-replace.sh
+++ b/t/t6050-replace.sh
@@ -446,7 +446,7 @@ test_expect_success GPG '--graft on a commit with a mergetag' '
test_expect_success '--convert-graft-file' '
: add and convert graft file &&
- printf "%s\n%s %s\n%s\n" \
+ printf "%s\n%s %s\n\n# comment\n%s\n" \
$(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \
>.git/info/grafts &&
git replace --convert-graft-file &&
Signature · 1 line
2.17.0.windows.1.15.gaa56ade3205
PATCH v3 1/11 argv_array: offer to split a string by whitespace
This is a simple function that will interpret a string as a whitespace delimited list of values, and add those values into the array.
Note: this function does not (yet) offer to split by arbitrary delimiters, or keep empty values in case of runs of whitespace, or de-quote Unix shell style. All fo this functionality can be added later, when and if needed.
--- argv-array.c | 20 ++++++++++++++++++++ argv-array.h | 1 + 2 files changed, 21 insertions(+)
argv-array.c +20 -0
@@ -64,6 +64,26 @@ void argv_array_pop(struct argv_array *array) array->argc--; } +void argv_array_split (struct argv_array *array,const char *to_split) +{ +while (isspace (*to_split)) + to_split++; +for (;;) { +const char *p = to_split; + +if (!*p) +break ; + +while (*p && !isspace (*p)) + p++; +argv_array_push_nodup (array,xstrndup (to_split, p - to_split)); + +while (isspace (*p)) + p++; + to_split = p; + } +} +void argv_array_clear (struct argv_array *array) {if (array->argv != empty_argv) {
argv-array.h +1 -0
@@ -19,6 +19,7 @@ LAST_ARG_MUST_BE_NULLvoid argv_array_pushl (struct argv_array *, ...);void argv_array_pushv (struct argv_array *,const char **);void argv_array_pop (struct argv_array *); +void argv_array_split (struct argv_array *,const char *);void argv_array_clear (struct argv_array *);const char **argv_array_detach (struct argv_array *);
Signature · 1 line
2.17.0.windows.1.15.gaa56ade3205
Re: [PATCH v3 01/11] argv_array: offer to split a string by whitespace
Quoted 30 lines · Johannes Schindelin
This is a simple function that will interpret a string as a whitespace delimited list of values, and add those values into the array.
Note: this function does not (yet) offer to split by arbitrary delimiters, or keep empty values in case of runs of whitespace, or de-quote Unix shell style. All fo this functionality can be added later, when and if needed.
Signed-off-by Johannes Schindelin <[email protected]>--- argv-array.c | 20 ++++++++++++++++++++ argv-array.h | 1 + 2 files changed, 21 insertions(+)argv-array.c +9
-0@@ -64,6 +64,26 @@ void argv_array_pop(struct argv_array *array) array->argc--; } +void argv_array_split (struct argv_array *array,const char *to_split) +{ +while (isspace (*to_split)) + to_split++; +for (;;) { +const char *p = to_split; + +if (!*p) +break ;
argv-array.c void argv_array_pop(struct argv_array *array) · line 85
Quoted 10 lines
argv-array.c +10
-0+ +while (*p && !isspace (*p)) + p++; +argv_array_push_nodup (array,xstrndup (to_split, p - to_split)); + +while (isspace (*p)) + p++; + to_split = p; + } +}
The code looks correct to me.
Though this seems so low level, that I find it hard to accept to implement yet another low level split function. Would reuse of strbuf_split or string_list_split make sense?
Looking at the user in patch 5 you really want to have the argv array, though, so it cannot be pushed to an even higher abstraction layer and be solved there. You really want a string -> argv array split, which would mean we'd have to do the split via string -> {strbufs, stringlist} and then perform a conversion from that to argv array and both conversions look ugly as we'd need to iterate their specific data structure and push each element itself again.
So I guess we rather implement it yet another time.
Looking at their function declarations:
/*
* lots of very good comments for string list splitting */
int string_list_split(struct string_list *list, const char *string,
int delim, int maxsplit);
/*
* lots of very good comments for strbuf splitting
*/
static inline struct strbuf **strbuf_split(const struct strbuf *sb,
int terminator)
I find they have comments in common as well as the terminator. In the commit message you defer the implementation of a terminating symbol, as we can do it later. Also the isspace takes more than one delimiter, which the others do not.
I am debating myself if I want to ask for a comment, as argv-array.h is very short for now and it would be consistent not to add a comment.
Thanks,
Stefan
Re: [PATCH v3 01/11] argv_array: offer to split a string by whitespace
Hi Stefan,
Quoted 49 lines · Stefan Beller
Quoted 30 lines · Johannes Schindelin
This is a simple function that will interpret a string as a whitespace delimited list of values, and add those values into the array.
Note: this function does not (yet) offer to split by arbitrary delimiters, or keep empty values in case of runs of whitespace, or de-quote Unix shell style. All fo this functionality can be added later, when and if needed.
Signed-off-by Johannes Schindelin <[email protected]>--- argv-array.c | 20 ++++++++++++++++++++ argv-array.h | 1 + 2 files changed, 21 insertions(+)argv-array.c +9
-0@@ -64,6 +64,26 @@ void argv_array_pop(struct argv_array *array) array->argc--; } +void argv_array_split (struct argv_array *array,const char *to_split) +{ +while (isspace (*to_split)) + to_split++; +for (;;) { +const char *p = to_split; + +if (!*p) +break ;argv-array.c void argv_array_pop(struct argv_array *array) · line 85
Quoted 10 lines
argv-array.c +10
-0+ +while (*p && !isspace (*p)) + p++; +argv_array_push_nodup (array,xstrndup (to_split, p - to_split)); + +while (isspace (*p)) + p++; + to_split = p; + } +}The code looks correct to me.
Though this seems so low level, that I find it hard to accept to implement yet another low level split function. Would reuse of
strbuf_splitorstring_list_splitmake sense?
Quoted 8 lines
Looking at the user in patch 5 you really want to have the argv array, though, so it cannot be pushed to an even higher abstraction layer and be solved there. You really want a string -> argv array split, which would mean we'd have to do the split via string -> {strbufs, stringlist} and then perform a conversion from that to argv array and both conversions look ugly as we'd need to iterate their specific data structure and push each element itself again.
Maybe we could reconcile all of this by introducing yet another layer of indirection? I am wary of this, as I tend to think that layers of indirection make things tedious to debug.
But I could imagine that we could introduce something like
typedef int (*split_string_fn)(const char *item, size_t len, void *data);
int split_string(const char *string, int delimiter, int maxsplit,
split_string_fn fn, void *fn_data);And then argv_array_split() would be implemented like this:
struct argv_array_split_data {
struct argv_array *array;
};
static int argv_array_split_fn(const char *item, size_t len, void *data)
{
struct argv_array *array =
((struct argv_array_split_data *)data)->array;
argv_array_push_nodup(array, xstrndup(item, len));
return 0;
}
void argv_array_split(struct argv_array *array, const char *string,
int delimiter, int maxsplit)
{
struct argv_array_split_data data;
data.array = array;
split_string(string, delimiter, maxsplit, argv_array_split_fn, &data);
}Possible? Yes. Desirable? Dunno. Looks like a lot of effort for little gain so far.
Quoted 22 lines
So I guess we rather implement it yet another time.
Looking at their function declarations:
/*
* lots of very good comments for string list splitting */
int string_list_split(structstring_list*list, const char *string,int delim, int maxsplit);/*
* lots of very good comments for strbuf splitting
*/
static inline struct strbuf **strbuf_split(const struct strbuf *sb,
int terminator)I find they have comments in common as well as the terminator. In the commit message you defer the implementation of a terminating symbol, as we can do it later. Also the isspace takes more than one delimiter, which the others do not.
Quoted 3 lines
I am debating myself if I want to ask for a comment, as
argv-array.his very short for now and it would be consistent not to add a comment.
That was my thinking.
And I thought further: once argv_array_split() becomes more complex, we would need to consider again whether we want that level of indirection (and consolidation), and then also whether we need a comment.
On the other hand, argv_array's name suggests that it handles command-lines, so I think you are correct that at least a little comment is needed to state that this does not handle quoted arguments.
Ciao,
Dscho
PATCH v3 2/11 commit: Let the callback of for_each_mergetag return on error
In reply to Johannes SchindelinThis is yet another patch to be filed under the keyword "libification".
There is one subtle change in behavior here, where a git log that has been asked to show the mergetags would now stop reporting the mergetags upon the first failure, whereas previously, it would have continued to the next mergetag, if any.
In practice, that change should not matter, as it is 1) uncommon to perform octopus merges using multiple tags as merge heads, and 2) when the user asks to be shown those tags, they really should be there.
--- builtin/replace.c | 8 ++++---- commit.c | 8 +++++--- commit.h | 4 ++-- log-tree.c | 13 +++++++------ 4 files changed, 18 insertions(+), 15 deletions(-)
builtin/replace.c +4 -4
@@ -345,7 +345,7 @@ struct check_mergetag_data {const char **argv; }; -static void check_one_mergetag (struct commit *commit, +static int check_one_mergetag (struct commit *commit,struct commit_extra_header *extra,void *data) {
@@ -368,20 +368,20 @@ static void check_one_mergetag(struct commit *commit,if (get_oid (mergetag_data->argv[i], &oid) <0 )die (_ ("Not a valid object name: '%s'" ), mergetag_data->argv[i]);if (!oidcmp (&tag->tagged->oid, &oid)) -return ;/* found */ +return 0 ;/* found */ }die (_ ("original commit '%s' contains mergetag '%s' that is discarded; " "use --edit instead of --graft" ), ref,oid_to_hex (&tag_oid)); } -static void check_mergetags (struct commit *commit,int argc,const char **argv) +static int check_mergetags (struct commit *commit,int argc,const char **argv) {struct check_mergetag_data mergetag_data; mergetag_data.argc = argc; mergetag_data.argv = argv; -for_each_mergetag (check_one_mergetag, commit, &mergetag_data); +return for_each_mergetag (check_one_mergetag, commit, &mergetag_data); }static int create_graft (int argc,const char **argv,int force)
commit.c +5 -3
@@ -1288,17 +1288,19 @@ struct commit_extra_header *read_commit_extra_headers(struct commit *commit,return extra; } -void for_each_mergetag (each_mergetag_fn fn,struct commit *commit,void *data) +int for_each_mergetag (each_mergetag_fn fn,struct commit *commit,void *data) {struct commit_extra_header *extra, *to_free; +int res =0 ; to_free =read_commit_extra_headers (commit,NULL ); -for (extra = to_free; extra; extra = extra->next) { +for (extra = to_free; !res && extra; extra = extra->next) {if (strcmp (extra->key,"mergetag" ))continue ;/* not a merge tag */ -fn (commit, extra, data); + res =fn (commit, extra, data); }free_commit_extra_headers (to_free); +return res; }static inline int standard_header_field (const char *field,size_t len)
commit.h +2 -2
@@ -291,10 +291,10 @@ extern const char *find_commit_header(const char *msg, const char *key,/* Find the end of the log message, the right place for a new trailer. */ extern int ignore_non_trailer (const char *buf,size_t len); -typedef void (*each_mergetag_fn )(struct commit *commit,struct commit_extra_header *extra, +typedef int (*each_mergetag_fn )(struct commit *commit,struct commit_extra_header *extra,void *cb_data); -extern void for_each_mergetag (each_mergetag_fn fn,struct commit *commit,void *data); +extern int for_each_mergetag (each_mergetag_fn fn,struct commit *commit,void *data);struct merge_remote_desc {struct object *obj;/* the named object, could be a tag */
log-tree.c +7 -6
@@ -488,9 +488,9 @@ static int is_common_merge(const struct commit *commit) && !commit->parents->next->next); } -static void show_one_mergetag (struct commit *commit, -struct commit_extra_header *extra, -void *data) +static int show_one_mergetag (struct commit *commit, +struct commit_extra_header *extra, +void *data) {struct rev_info *opt = (struct rev_info *)data;struct object_id oid;
@@ -502,7 +502,7 @@ static void show_one_mergetag(struct commit *commit,hash_object_file (extra->value, extra->len,type_name (OBJ_TAG ), &oid); tag =lookup_tag (&oid);if (!tag) -return ;/* error message already given */ +return -1 ;/* error message already given */ strbuf_init (&verify_message,256 );if (parse_tag_buffer (tag, extra->value, extra->len))
@@ -536,11 +536,12 @@ static void show_one_mergetag(struct commit *commit,show_sig_lines (opt, status, verify_message.buf);strbuf_release (&verify_message); +return 0 ; } -static void show_mergetag (struct rev_info *opt,struct commit *commit) +static int show_mergetag (struct rev_info *opt,struct commit *commit) { -for_each_mergetag (show_one_mergetag, commit, opt); +return for_each_mergetag (show_one_mergetag, commit, opt); }void show_log (struct rev_info *opt)
Signature · 1 line
2.17.0.windows.1.15.gaa56ade3205
PATCH v3 3/11 replace: avoid using die() to indicate a bug
In reply to Johannes SchindelinWe have the BUG() macro for that purpose.
--- builtin/replace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
builtin/replace.c +1 -1
@@ -501,6 +501,6 @@ int cmd_replace(int argc, const char **argv, const char *prefix)return list_replace_refs (argv[0 ], format);default : -die ("BUG: invalid cmdmode %d" , (int )cmdmode); +BUG ("invalid cmdmode %d" , (int )cmdmode); } }
Signature · 1 line
2.17.0.windows.1.15.gaa56ade3205
PATCH v3 4/11 replace: "libify" create_graft() and callees
In reply to Johannes SchindelinFile this away as yet another patch in the "libification" category.
As with all useful functions, in the next commit we want to use create_graft() from a higher-level function where it would be inconvenient if the called function simply die()s: if there is a problem, we want to let the user know how to proceed, and the callee simply has no way of knowing what to say.
--- builtin/replace.c | 135 ++++++++++++++++++++++++++++------------------ 1 file changed, 84 insertions(+), 51 deletions(-)
builtin/replace.c +84 -51
@@ -79,9 +79,9 @@ static int list_replace_refs(const char *pattern, const char *format)else if (!strcmp (format,"long" )) data.format =REPLACE_FORMAT_LONG ;else -die ("invalid replace format '%s'\n" -"valid formats are 'short', 'medium' and 'long'\n" , - format); +return error ("invalid replace format '%s'\n" +"valid formats are 'short', 'medium' and 'long'\n" , + format);for_each_replace_ref (show_reference, (void *)&data);
@@ -134,7 +134,7 @@ static int delete_replace_ref(const char *name, const char *ref,return 0 ; } -static void check_ref_valid (struct object_id *object, +static int check_ref_valid (struct object_id *object,struct object_id *prev,struct strbuf *ref,int force)
@@ -142,12 +142,13 @@ static void check_ref_valid(struct object_id *object,strbuf_reset (ref);strbuf_addf (ref,"%s%s" , git_replace_ref_base,oid_to_hex (object));if (check_refname_format (ref->buf,0 )) -die ("'%s' is not a valid ref name." , ref->buf); +return error ("'%s' is not a valid ref name." , ref->buf);if (read_ref (ref->buf, prev))oidclr (prev);else if (!force) -die ("replace ref '%s' already exists" , ref->buf); +return error ("replace ref '%s' already exists" , ref->buf); +return 0 ; } static int replace_object_oid(const char *object_ref,
@@ -161,28 +162,31 @@ static int replace_object_oid(const char *object_ref,struct strbuf ref =STRBUF_INIT ;struct ref_transaction *transaction;struct strbuf err =STRBUF_INIT ; +int res =0 ; obj_type =oid_object_info (object,NULL ); repl_type =oid_object_info (repl,NULL );if (!force && obj_type != repl_type) -die ("Objects must be of the same type.\n" -"'%s' points to a replaced object of type '%s'\n" -"while '%s' points to a replacement object of type '%s'." , - object_ref,type_name (obj_type), - replace_ref,type_name (repl_type)); +return error ("Objects must be of the same type.\n" +"'%s' points to a replaced object of type '%s'\n" +"while '%s' points to a replacement object of " +"type '%s'." , + object_ref,type_name (obj_type), + replace_ref,type_name (repl_type)); -check_ref_valid (object, &prev, &ref, force); +if (check_ref_valid (object, &prev, &ref, force)) +return -1 ; transaction =ref_transaction_begin (&err);if (!transaction ||ref_transaction_update (transaction, ref.buf, repl, &prev,0 ,NULL , &err) ||ref_transaction_commit (transaction, &err)) -die ("%s" , err.buf); + res =error ("%s" , err.buf);ref_transaction_free (transaction);strbuf_release (&ref); -return 0 ; +return res; }static int replace_object (const char *object_ref,const char *replace_ref,int force)
@@ -190,9 +194,11 @@ static int replace_object(const char *object_ref, const char *replace_ref, int fstruct object_id object, repl;if (get_oid (object_ref, &object)) -die ("Failed to resolve '%s' as a valid ref." , object_ref); +return error ("Failed to resolve '%s' as a valid ref." , + object_ref);if (get_oid (replace_ref, &repl)) -die ("Failed to resolve '%s' as a valid ref." , replace_ref); +return error ("Failed to resolve '%s' as a valid ref." , + replace_ref);return replace_object_oid (object_ref, &object, replace_ref, &repl, force); }
@@ -202,7 +208,7 @@ static int replace_object(const char *object_ref, const char *replace_ref, int f* If "raw" is true, then the object's raw contents are printed according to * "type". Otherwise, we pretty-print the contents for human editing. */ -static void export_object (const struct object_id *oid,enum object_type type, +static int export_object (const struct object_id *oid,enum object_type type,int raw,const char *filename) {struct child_process cmd =CHILD_PROCESS_INIT ;
@@ -210,7 +216,7 @@ static void export_object(const struct object_id *oid, enum object_type type, fd =open (filename,O_WRONLY |O_CREAT |O_TRUNC ,0666 );if (fd <0 ) -die_errno ("unable to open %s for writing" , filename); +return error_errno ("unable to open %s for writing" , filename);argv_array_push (&cmd.args,"--no-replace-objects" );argv_array_push (&cmd.args,"cat-file" );
@@ -223,7 +229,8 @@ static void export_object(const struct object_id *oid, enum object_type type, cmd.out = fd;if (run_command (&cmd)) -die ("cat-file reported failure" ); +return error ("cat-file reported failure" ); +return 0 ; }/*
@@ -231,14 +238,14 @@ static void export_object(const struct object_id *oid, enum object_type type,* interpreting it as "type", and writing the result to the object database. * The sha1 of the written object is returned via sha1. */ -static void import_object (struct object_id *oid,enum object_type type, +static int import_object (struct object_id *oid,enum object_type type,int raw,const char *filename) {int fd; fd =open (filename,O_RDONLY );if (fd <0 ) -die_errno ("unable to open %s for reading" , filename); +return error_errno ("unable to open %s for reading" , filename);if (!raw && type ==OBJ_TREE ) {const char *argv[] = {"mktree" ,NULL };
@@ -250,27 +257,38 @@ static void import_object(struct object_id *oid, enum object_type type, cmd.in = fd; cmd.out =-1 ; -if (start_command (&cmd)) -die ("unable to spawn mktree" ); +if (start_command (&cmd)) { +close (fd); +return error ("unable to spawn mktree" ); + } -if (strbuf_read (&result, cmd.out,41 ) <0 ) -die_errno ("unable to read from mktree" ); +if (strbuf_read (&result, cmd.out,41 ) <0 ) { +close (fd); +close (cmd.out); +return error_errno ("unable to read from mktree" ); + }close (cmd.out); -if (finish_command (&cmd)) -die ("mktree reported failure" ); -if (get_oid_hex (result.buf, oid) <0 ) -die ("mktree did not return an object name" ); +if (finish_command (&cmd)) { +strbuf_release (&result); +return error ("mktree reported failure" ); + } +if (get_oid_hex (result.buf, oid) <0 ) { +strbuf_release (&result); +return error ("mktree did not return an object name" ); + }strbuf_release (&result); }else {struct stat st;int flags =HASH_FORMAT_CHECK |HASH_WRITE_OBJECT ; -if (fstat (fd, &st) <0 ) -die_errno ("unable to fstat %s" , filename); +if (fstat (fd, &st) <0 ) { +close (fd); +return error_errno ("unable to fstat %s" , filename); + }if (index_fd (oid, fd, &st, type,NULL , flags) <0 ) -die ("unable to write object to database" ); +return error ("unable to write object to database" );/* index_fd close()s fd for us */ }
@@ -278,6 +296,7 @@ static void import_object(struct object_id *oid, enum object_type type,* No need to close(fd) here; both run-command and index-fd * will have done it for us. */ +return 0 ; }static int edit_and_replace (const char *object_ref,int force,int raw)
@@ -288,19 +307,23 @@ static int edit_and_replace(const char *object_ref, int force, int raw)struct strbuf ref =STRBUF_INIT ;if (get_oid (object_ref, &old_oid) <0 ) -die ("Not a valid object name: '%s'" , object_ref); +return error ("Not a valid object name: '%s'" , object_ref); type =oid_object_info (&old_oid,NULL );if (type <0 ) -die ("unable to get object type for %s" ,oid_to_hex (&old_oid)); +return error ("unable to get object type for %s" , +oid_to_hex (&old_oid)); -check_ref_valid (&old_oid, &prev, &ref, force); +if (check_ref_valid (&old_oid, &prev, &ref, force)) +return -1 ;strbuf_release (&ref); -export_object (&old_oid, type, raw, tmpfile); +if (export_object (&old_oid, type, raw, tmpfile)) +return -1 ;if (launch_editor (tmpfile,NULL ,NULL ) <0 ) -die ("editing object file failed" ); -import_object (&new_oid, type, raw, tmpfile); +return error ("editing object file failed" ); +if (import_object (&new_oid, type, raw, tmpfile)) +return -1 ;free (tmpfile);
@@ -310,7 +333,7 @@ static int edit_and_replace(const char *object_ref, int force, int raw)return replace_object_oid (object_ref, &old_oid,"replacement" , &new_oid, force); } -static void replace_parents (struct strbuf *buf,int argc,const char **argv) +static int replace_parents (struct strbuf *buf,int argc,const char **argv) {struct strbuf new_parents =STRBUF_INIT ;const char *parent_start, *parent_end;
@@ -328,8 +351,10 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv)for (i =0 ; i < argc; i++) {struct object_id oid;if (get_oid (argv[i], &oid) <0 ) -die (_ ("Not a valid object name: '%s'" ), argv[i]); -lookup_commit_or_die (&oid, argv[i]); +return error (_ ("Not a valid object name: '%s'" ), + argv[i]); +if (!lookup_commit_reference (&oid)) +return error (_ ("could not parse %s" ), argv[i]);strbuf_addf (&new_parents,"parent %s\n" ,oid_to_hex (&oid)); }
@@ -338,6 +363,7 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv) new_parents.buf, new_parents.len);strbuf_release (&new_parents); +return 0 ; }struct check_mergetag_data {
@@ -358,21 +384,23 @@ static int check_one_mergetag(struct commit *commit,hash_object_file (extra->value, extra->len,type_name (OBJ_TAG ), &tag_oid); tag =lookup_tag (&tag_oid);if (!tag) -die (_ ("bad mergetag in commit '%s'" ), ref); +return error (_ ("bad mergetag in commit '%s'" ), ref);if (parse_tag_buffer (tag, extra->value, extra->len)) -die (_ ("malformed mergetag in commit '%s'" ), ref); +return error (_ ("malformed mergetag in commit '%s'" ), ref);/* iterate over new parents */ for (i =1 ; i < mergetag_data->argc; i++) {struct object_id oid;if (get_oid (mergetag_data->argv[i], &oid) <0 ) -die (_ ("Not a valid object name: '%s'" ), mergetag_data->argv[i]); +return error (_ ("Not a valid object name: '%s'" ), + mergetag_data->argv[i]);if (!oidcmp (&tag->tagged->oid, &oid))return 0 ;/* found */ } -die (_ ("original commit '%s' contains mergetag '%s' that is discarded; " -"use --edit instead of --graft" ), ref,oid_to_hex (&tag_oid)); +return error (_ ("original commit '%s' contains mergetag '%s' that is " +"discarded; use --edit instead of --graft" ), ref, +oid_to_hex (&tag_oid)); }static int check_mergetags (struct commit *commit,int argc,const char **argv)
@@ -394,24 +422,29 @@ static int create_graft(int argc, const char **argv, int force)unsigned long size;if (get_oid (old_ref, &old_oid) <0 ) -die (_ ("Not a valid object name: '%s'" ), old_ref); - commit =lookup_commit_or_die (&old_oid, old_ref); +return error (_ ("Not a valid object name: '%s'" ), old_ref); + commit =lookup_commit_reference (&old_oid); +if (!commit) +return error (_ ("could not parse %s" ), old_ref); buffer =get_commit_buffer (commit, &size);strbuf_add (&buf, buffer, size);unuse_commit_buffer (commit, buffer); -replace_parents (&buf, argc -1 , &argv[1 ]); +if (replace_parents (&buf, argc -1 , &argv[1 ]) <0 ) +return -1 ;if (remove_signature (&buf)) {warning (_ ("the original commit '%s' has a gpg signature." ), old_ref);warning (_ ("the signature will be removed in the replacement commit!" )); } -check_mergetags (commit, argc, argv); +if (check_mergetags (commit, argc, argv)) +return -1 ;if (write_object_file (buf.buf, buf.len, commit_type, &new_oid)) -die (_ ("could not write replacement commit for: '%s'" ), old_ref); +return error (_ ("could not write replacement commit for: '%s'" ), + old_ref);strbuf_release (&buf);
Signature · 1 line
2.17.0.windows.1.15.gaa56ade3205
PATCH v3 5/11 replace: introduce --convert-graft-file
In reply to Johannes SchindelinThis option is intended to help with the transition away from the now-deprecated graft file.
--- Documentation/git-replace.txt | 11 ++++++--- builtin/replace.c | 44 ++++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 4 deletions(-)
Documentation/git-replace.txt +8 -3
@@ -11,6 +11,7 @@ SYNOPSIS 'git replace' [-f] <object> <replacement> 'git replace' [-f] --edit <object> 'git replace' [-f] --graft <commit> [<parent>...] +'git replace' [-f] --convert-graft-file 'git replace' -d <object>... 'git replace' [--format=<format>] [-l [<pattern>]]
@@ -87,9 +88,13 @@ OPTIONS content as <commit> except that its parents will be [<parent>...] instead of <commit>'s parents. A replacement ref is then created to replace <commit> with the newly created - commit. See contrib/convert-grafts-to-replace-refs.sh for an - example script based on this option that can convert grafts to - replace refs. + commit. Use `--convert-graft-file` to convert a + `$GIT_DIR/info/grafts` file and use replace refs instead. + +--convert-graft-file:: + Creates graft commits for all entries in `$GIT_DIR/info/grafts` + and deletes that file upon success. The purpose is to help users + with transitioning off of the now-deprecated graft file. -l <pattern>:: --list <pattern>::
builtin/replace.c +43 -1
80 lines not on this page · raw message
Signature · 1 line
2.17.0.windows.1.15.gaa56ade3205
PATCH v3 6/11 Add a test for `git replace --convert-graft-file`
In reply to Johannes SchindelinThe proof, as the saying goes, lies in the pudding. So here is a regression test that not only demonstrates what the option is supposed to accomplish, but also demonstrates that it does accomplish it.
--- t/t6050-replace.sh | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+)
t/t6050-replace.sh +20 -0
@@ -444,4 +444,24 @@ test_expect_success GPG '--graft on a commit with a mergetag' ' git replace -d $HASH10 ' +test_expect_success '--convert-graft-file' ' + : add and convert graft file && + printf "%s\n%s %s\n\n# comment\n%s\n" \ + $(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \ + >.git/info/grafts && + git replace --convert-graft-file && + test_path_is_missing .git/info/grafts && + + : verify that the history is now "grafted" && + git rev-list HEAD >out && + test_line_count = 4 out && + + : create invalid graft file and verify that it is not deleted && + test_when_finished "rm -f .git/info/grafts" && + echo $EMPTY_BLOB $EMPTY_TREE >.git/info/grafts && + test_must_fail git replace --convert-graft-file 2>err && + grep "$EMPTY_BLOB $EMPTY_TREE" err && + grep "$EMPTY_BLOB $EMPTY_TREE" .git/info/grafts +' + test_done
Signature · 1 line
2.17.0.windows.1.15.gaa56ade3205
Re: [PATCH v3 06/11] Add a test for `git replace --convert-graft-file`
Quoted 24 lines
The proof, as the saying goes, lies in the pudding. So here is a regression test that not only demonstrates what the option is supposed to accomplish, but also demonstrates that it does accomplish it.
Signed-off-by Johannes Schindelin <[email protected]>--- t/t6050-replace.sh | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+)t/t6050-replace.sh +7
-0@@ -444,4 +444,24 @@ test_expect_success GPG '--graft on a commit with a mergetag' ' git replace -d $HASH10 ' +test_expect_success '--convert-graft-file' ' + : add and convert graft file && + printf "%s\n%s %s\n\n# comment\n%s\n" \ + $(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \ + >.git/info/grafts && + git replace --convert-graft-file && + test_path_is_missing .git/info/grafts &&
t/t6050-replace.sh test_expect_success GPG '--graft on a commit with a mergetag' ' · line 463
Quoted 10 lines
t/t6050-replace.sh +10
-0+ + : verify that the history is now "grafted" && + git rev-list HEAD >out && + test_line_count = 4 out && + + : create invalid graft file and verify that it is not deleted && + test_when_finished "rm -f .git/info/grafts" && + echo $EMPTY_BLOB $EMPTY_TREE >.git/info/grafts && + test_must_fail git replace --convert-graft-file 2>err && + grep "$EMPTY_BLOB $EMPTY_TREE" err &&
This should be 'test_i18ngrep'. Apparently this error message is translated, and, consequently, the check fails in a GETTEXT_POISON build.
Quoted 6 lines
t/t6050-replace.sh +3
-0+ grep "$EMPTY_BLOB $EMPTY_TREE" .git/info/grafts +' + test_doneSignature · 1 line
2.17.0.windows.1.15.gaa56ade3205
Re: [PATCH v3 06/11] Add a test for `git replace --convert-graft-file`
Hi Gábor,
Quoted 30 lines · SZEDER Gábor
Quoted 30 lines
The proof, as the saying goes, lies in the pudding. So here is a regression test that not only demonstrates what the option is supposed to accomplish, but also demonstrates that it does accomplish it.
Signed-off-by Johannes Schindelin <[email protected]>--- t/t6050-replace.sh | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+)t/t6050-replace.sh +13
-0@@ -444,4 +444,24 @@ test_expect_success GPG '--graft on a commit with a mergetag' ' git replace -d $HASH10 ' +test_expect_success '--convert-graft-file' ' + : add and convert graft file && + printf "%s\n%s %s\n\n# comment\n%s\n" \ + $(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \ + >.git/info/grafts && + git replace --convert-graft-file && + test_path_is_missing .git/info/grafts && + + : verify that the history is now "grafted" && + git rev-list HEAD >out && + test_line_count = 4 out && + + : create invalid graft file and verify that it is not deleted &&
Quoted 8 lines
t/t6050-replace.sh test_expect_success GPG '--graft on a commit with a mergetag' ' · line 463
Quoted 4 lines
t/t6050-replace.sh +4
-0+ test_when_finished "rm -f .git/info/grafts" && + echo $EMPTY_BLOB $EMPTY_TREE >.git/info/grafts && + test_must_fail git replace --convert-graft-file 2>err && + grep "$EMPTY_BLOB $EMPTY_TREE" err &&This should be '
test_i18ngrep'. Apparently this error message is translated, and, consequently, the check fails in aGETTEXT_POISONbuild.
Sure enough, you're right! I thought it would not be translated, what with being an parameter to a formatted string...
Will fix,
Dscho
PATCH v3 7/11 Deprecate support for .git/info/grafts
In reply to Johannes SchindelinThe grafts feature was a convenient way to "stitch together" ancient history to the fresh start of linux.git.
Its implementation is, however, not up to Git's standards, as there are too many ways where it can lead to surprising and unwelcome behavior.
For example, when pushing from a repository with active grafts, it is possible to miss commits that have been "grafted out", resulting in a broken state on the other side.
Also, the grafts feature is limited to "rewriting" commits' list of parents, it cannot replace anything else.
The much younger feature implemented as git replace set out to remedy those limitations and dangerous bugs.
Seeing as git replace is pretty mature by now (since 4228e8bc98 (replace: add --graft option, 2014-07-19) it can perform the graft file's duties), it is time to deprecate support for the graft file, and to retire it eventually.
--- advice.c | 2 ++ advice.h | 1 + commit.c | 10 ++++++++++ t/t6001-rev-list-graft.sh | 9 +++++++++ 4 files changed, 22 insertions(+)
advice.c +2 -0
@@ -19,6 +19,7 @@ int advice_rm_hints = 1;int advice_add_embedded_repo =1 ;int advice_ignored_hook =1 ;int advice_waiting_for_editor =1 ; +int advice_graft_file_deprecated =1 ; static struct { const char *name;
@@ -42,6 +43,7 @@ static struct { { "addembeddedrepo", &advice_add_embedded_repo }, { "ignoredhook", &advice_ignored_hook }, { "waitingforeditor", &advice_waiting_for_editor }, + { "graftfiledeprecated", &advice_graft_file_deprecated },/* make this an alias for backward compatibility */ { "pushnonfastforward", &advice_push_update_rejected }
advice.h +1 -0
@@ -21,6 +21,7 @@ extern int advice_rm_hints;extern int advice_add_embedded_repo;extern int advice_ignored_hook;extern int advice_waiting_for_editor; +extern int advice_graft_file_deprecated;int git_default_advice_config (const char *var,const char *value); __attribute__((format (printf, 1, 2)))
commit.c +10 -0
@@ -12,6 +12,7 @@#include "prio-queue.h" #include "sha1-lookup.h" #include "wt-status.h" +#include "advice.h" static struct commit_extra_header *read_commit_extra_header_lines (const char *buf,size_t len,const char **);
@@ -176,6 +177,15 @@ static int read_graft_file(const char *graft_file)struct strbuf buf =STRBUF_INIT ;if (!fp)return -1 ; +if (advice_graft_file_deprecated) +advise (_ ("Support for <GIT_DIR>/info/grafts is deprecated\n" +"and will be removed in a future Git version.\n" +"\n" +"Please use \"git replace --convert-graft-file\"\n" +"to convert the grafts into replace refs.\n" +"\n" +"Turn this message off by running\n" +"\"git config advice.graftFileDeprecated false\"" ));while (!strbuf_getwholeline (&buf, fp,'\n' )) {/* The format is just "Commit Parent1 Parent2 ...\n" */ struct commit_graft *graft =read_graft_line (&buf);
t/t6001-rev-list-graft.sh +9 -0
@@ -110,4 +110,13 @@ do " done + +test_expect_success 'show advice that grafts are deprecated' ' + git show HEAD 2>err && + test_i18ngrep "git replace" err && + test_config advice.graftFileDeprecated false && + git show HEAD 2>err && + test_i18ngrep ! "git replace" err +' + test_done
Signature · 1 line
2.17.0.windows.1.15.gaa56ade3205
PATCH v3 8/11 filter-branch: stop suggesting to use grafts
In reply to Johannes SchindelinThe graft file is deprecated now, so let's use replace refs in the example in filter-branch's man page instead.
--- Documentation/git-filter-branch.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
Documentation/git-filter-branch.txt +1 -1
@@ -288,7 +288,7 @@ git filter-branch --parent-filter \ or even simpler: ----------------------------------------------- -echo "$commit-id $graft-id" >> .git/info/grafts +git replace --graft $commit-id $graft-id git filter-branch $graft-id..HEAD -----------------------------------------------
Signature · 1 line
2.17.0.windows.1.15.gaa56ade3205
PATCH v3 9/11 technical/shallow: describe the relationship with replace refs
In reply to Johannes SchindelinNow that grafts are deprecated, we should start to assume that readers have no idea what grafts are. So it makes more sense to describe the "shallow" feature in terms of replace refs.
--- Documentation/technical/shallow.txt | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-)
Documentation/technical/shallow.txt +11 -8
@@ -9,14 +9,17 @@ these commits have no parents. ********************************************************* The basic idea is to write the SHA-1s of shallow commits into -$GIT_DIR/shallow, and handle its contents like the contents -of $GIT_DIR/info/grafts (with the difference that shallow -cannot contain parent information). - -This information is stored in a new file instead of grafts, or -even the config, since the user should not touch that file -at all (even throughout development of the shallow clone, it -was never manually edited!). +$GIT_DIR/shallow, and handle its contents similar to replace +refs (with the difference that shallow does not actually +create those replace refs) and very much like the deprecated +graft file (with the difference that shallow commits will +always have their parents grafted away, not replaced by +different parents). + +This information is stored in a special-purpose file because the +user should not touch that file at all (even throughout +development of the shallow clone, it was never manually +edited!). Each line contains exactly one SHA-1. When read, a commit_graft will be constructed, which has nr_parent < 0 to make it easier
Signature · 1 line
2.17.0.windows.1.15.gaa56ade3205
Re: [PATCH v3 09/11] technical/shallow: describe the relationship with replace refs
From: "Johannes Schindelin" <[email protected]>
Quoted 19 lines
Now that grafts are deprecated, we should start to assume that readers have no idea what grafts are. So it makes more sense to describe the "shallow" feature in terms of replace refs.
Suggested-by Eric Sunshine <[email protected]>Signed-off-by Johannes Schindelin <[email protected]>---
Documentation/technical/shallow.txt| 19 +++++++++++--------1 file changed, 11 insertions(+), 8 deletions(-)Documentation/technical/shallow.txt +0
-0@@ -9,14 +9,17 @@ these commits have no parents. *********************************************************The basic idea is to write the SHA-1s of shallow commits into -$GIT_DIR/shallow, and handle its contents like the contents
Documentation/technical/shallow.txt these commits have no parents.
Quoted 10 lines
Documentation/technical/shallow.txt +3
-7-of $GIT_DIR/info/grafts (with the difference that shallow -cannot contain parent information). - -This information is stored in a new file instead of grafts, or -even the config, since the user should not touch that file -at all (even throughout development of the shallow clone, it -was never manually edited!). +$GIT_DIR/shallow, and handle its contents similar to replace +refs (with the difference that shallow does not actually +create those replace refs) and
If grafts are deprecated, why not alse get rid of this mention and simply leave the 'what it does' part.
Documentation/technical/shallow.txt these commits have no parents. · line 16
Quoted 6 lines
very much like the
deprecatedDocumentation/technical/shallow.txt +2
-0+graft file (with the difference that shallow commits will +always have their parents grafted away, not replaced by
s/their parents grafted away/no parents/ (rather than being replaced..)
Quoted 14 lines
Documentation/technical/shallow.txt +6
-0+different parents). + +This information is stored in a special-purpose file because the +user should not touch that file at all (even throughout +development of the shallow clone, it was never manually +edited!).Each line contains exactly one SHA-1. When read, a
commit_graftwill be constructed, which hasnr_parent< 0 to make it easierSignature · 1 line
2.17.0.windows.1.15.gaa56ade3205
Re: [PATCH v3 09/11] technical/shallow: describe the relationship with replace refs
Hi Philip,
Quoted 19 lines · Philip Oakley
From: "Johannes Schindelin" <[email protected]>
Quoted 18 lines
Now that grafts are deprecated, we should start to assume that readers have no idea what grafts are. So it makes more sense to describe the "shallow" feature in terms of replace refs.
Suggested-by Eric Sunshine <[email protected]>Signed-off-by Johannes Schindelin <[email protected]>---
Documentation/technical/shallow.txt| 19 +++++++++++--------1 file changed, 11 insertions(+), 8 deletions(-)Documentation/technical/shallow.txt +0
-0@@ -9,14 +9,17 @@ these commits have no parents. *********************************************************
Quoted 15 lines
Documentation/technical/shallow.txt these commits have no parents.
Quoted 12 lines
The basic idea is to write the SHA-1s of shallow commits into -$GIT_DIR/shallow, and handle its contents like the contents
Documentation/technical/shallow.txt +3
-7-of $GIT_DIR/info/grafts (with the difference that shallow -cannot contain parent information). - -This information is stored in a new file instead of grafts, or -even the config, since the user should not touch that file -at all (even throughout development of the shallow clone, it -was never manually edited!). +$GIT_DIR/shallow, and handle its contents similar to replace +refs (with the difference that shallow does not actually +create those replace refs) andIf grafts are deprecated, why not alse get rid of this mention and simply leave the 'what it does' part.
Internally, shallow commits are implemented using the graft code path, and they always will be: we will always need a list of the shallow commits, and we will always need to be able to lift the "shallow" attribute quickly, when deepening a shallow clone.
So it makes sense to mention that here, because we are deep in technical details in Documentation/technical/.
Quoted 6 lines
Documentation/technical/shallow.txt these commits have no parents. · line 16
Quoted 5 lines
very much like the deprecated
Documentation/technical/shallow.txt +2
-0+graft file (with the difference that shallow commits will +always have their parents grafted away, not replaced bys/their parents grafted away/no parents/ (rather than being replaced..)
But the commits will typically have parents. So they really will have their parents grafted away as long as they are marked "shallow"...
Thank you for reviewing!
Dscho
Re: [PATCH v3 09/11] technical/shallow: describe the relationship with replace refs
Hi dscho
From: "Johannes Schindelin" <[email protected]> : Tuesday, April 24, 2018 8:10 PM
Quoted 6 lines · Philip Oakley
Quoted 5 lines
From: "Johannes Schindelin" <[email protected]>
Quoted 3 lines
Now that grafts are deprecated, we should start to assume that readers have no idea what grafts are. So it makes more sense to describe the "shallow" feature in terms of replace refs.
Here we say we should drop the term "grafts"
Quoted 31 lines
Quoted 15 lines
Quoted 15 lines
Suggested-by Eric Sunshine <[email protected]>Signed-off-by Johannes Schindelin <[email protected]>---
Documentation/technical/shallow.txt| 19 +++++++++++--------1 file changed, 11 insertions(+), 8 deletions(-)Documentation/technical/shallow.txt +0
-0@@ -9,14 +9,17 @@ these commits have no parents. *********************************************************Quoted 15 lines
Documentation/technical/shallow.txt these commits have no parents.
Quoted 12 lines
The basic idea is to write the SHA-1s of shallow commits into -$GIT_DIR/shallow, and handle its contents like the contents
Documentation/technical/shallow.txt +3
-7-of $GIT_DIR/info/grafts (with the difference that shallow -cannot contain parent information). - -This information is stored in a new file instead of grafts, or -even the config, since the user should not touch that file -at all (even throughout development of the shallow clone, it -was never manually edited!). +$GIT_DIR/shallow, and handle its contents similar to replace +refs (with the difference that shallow does not actually +create those replace refs) andIf grafts are deprecated, why not alse get rid of this mention and simply leave the 'what it does' part.
Quoted 1 line
Internally, shallow commits are implemented using the graft code path, and
however the change here is just to the documentation, independent of th code path's name.
Quoted 10 lines
they always will be: we will always need a list of the shallow commits, and we will always need to be able to lift the "shallow" attribute quickly, when deepening a shallow clone.
So it makes sense to mention that here, because we are deep in technical details in
Documentation/technical/.Quoted 3 lines
Quoted 3 lines
very much like the
deprecated
+graft file (with
I was looking to snip this 'graft' reference, as per the commit message..
Quoted 4 lines
Quoted 4 lines
Quoted 3 lines
the difference that shallow commits will
+always have their parents grafted away, not replaced bys/their parents grafted away/no parents/ (rather than being replaced..)
Then I botched this substitution
Quoted 3 lines
But the commits will typically have parents. So they really will have their parents grafted away as long as they are marked "shallow"...
OK, maybe I mis-used the figurative 'no parents', when it means the literal 'parents not present'.
Perhaps something like:
+$GIT_DIR/shallow, and handle its contents similar to replace +refs (with the difference that shallow does not actually +create those replace refs) with the difference that shallow commits will +always have their parents not present.
--
Philip
Re: [PATCH v3 09/11] technical/shallow: describe the relationship with replace refs
Review
Quoted 5 lines · Philip Oakley
Perhaps something like:
+$GIT_DIR/shallow, and handle its contents similar to replace(quoted diff) +3
-0+refs (with the difference that shallow does not actually +create those replace refs) with the difference that shallow commits will +always have their parents not present.
I am not sure if there is enough similarity to replace mechanism to mention that. It has lines of text, each of which records a commit object for which Git is told to pretend that it has no parent.
To those who are familiar with "graft" format, it is possible to explain the format as "it is similar to graft", as a line with a single commit object name tells Git to pretend that it has no parent in the "graft" format, but because we are getting rid of graft, it probably makes sense to just explain it without reference to replace (which may serve a similar purpose, but is certainly very far from "similar" as a mechanism when you explain how the contents of shallow is handled).
$GIT_DIR/shallow lists commit object names and tells Git to pretend as if they are root commits (e.g. "git log" traversal stops after showing them; "git fsck" does not complain saying the commits listed on their "parent" lines do not exist).
Re: [PATCH v3 09/11] technical/shallow: describe the relationship with replace refs
Hi Philip & Junio,
Quoted 21 lines · Junio C Hamano
Review
Quoted 5 lines · Philip Oakley
Perhaps something like:
+$GIT_DIR/shallow, and handle its contents similar to replace(quoted diff) +3
-0+refs (with the difference that shallow does not actually +create those replace refs) with the difference that shallow commits will +always have their parents not present.I am not sure if there is enough similarity to replace mechanism to mention that. It has lines of text, each of which records a commit object for which Git is told to pretend that it has no parent.
To those who are familiar with "graft" format, it is possible to explain the format as "it is similar to graft", as a line with a single commit object name tells Git to pretend that it has no parent in the "graft" format, but because we are getting rid of graft, it probably makes sense to just explain it without reference to replace (which may serve a similar purpose, but is certainly very far from "similar" as a mechanism when you explain how the contents of shallow is handled).
Quoted 4 lines
$GIT_DIR/shallow lists commit object names and tells Git to pretend as if they are root commits (e.g. "git log" traversal stops after showing them; "git fsck" does not complain saying the commits listed on their "parent" lines do not exist).
Good point. Thank you for that suggestion, I'll take it as-is.
Ciao,
Dscho
PATCH v3 10/11 technical/shallow: describe why shallow cannot use replace refs
In reply to Johannes SchindelinIt is tempting to do away with commit_graft altogether (in the long haul), now that grafts are deprecated.
However, the shallow feature needs a couple of things that the replace refs cannot fulfill. Let's point that out in the documentation.
--- Documentation/technical/shallow.txt | 7 +++++++ 1 file changed, 7 insertions(+)
Documentation/technical/shallow.txt +7 -0
@@ -25,6 +25,13 @@ Each line contains exactly one SHA-1. When read, a commit_graft will be constructed, which has nr_parent < 0 to make it easier to discern from user provided grafts. +Note that the shallow feature could not be changed easily to +use replace refs: a commit containing a `mergetag` is not allowed +to be replaced, not even by a root commit. Such a commit can be +made shallow, though. Also, having a `shallow` file explicitly +listing all the commits made shallow makes it a *lot* easier to +do shallow-specific things such as to deepen the history. + Since fsck-objects relies on the library to read the objects, it honours shallow commits automatically.
Signature · 1 line
2.17.0.windows.1.15.gaa56ade3205
PATCH v3 11/11 Remove obsolete script to convert grafts to replace refs
In reply to Johannes SchindelinThe functionality is now implemented as git replace --convert-graft-file.
--- contrib/convert-grafts-to-replace-refs.sh | 28 ----------------------- 1 file changed, 28 deletions(-) delete mode 100755 contrib/convert-grafts-to-replace-refs.sh
contrib/convert-grafts-to-replace-refs.sh deleted +0 -28
@@ -1,28 +0,0 @@ -#!/bin/sh - -# You should execute this script in the repository where you -# want to convert grafts to replace refs. - -GRAFTS_FILE="${GIT_DIR:-.git}/info/grafts" - -. $(git --exec-path)/git-sh-setup - -test -f "$GRAFTS_FILE" || die "Could not find graft file: '$GRAFTS_FILE'" - -grep '^[^# ]' "$GRAFTS_FILE" | -while read definition -do - if test -n "$definition" - then - echo "Converting: $definition" - git replace --graft $definition || - die "Conversion failed for: $definition" - fi -done - -mv "$GRAFTS_FILE" "$GRAFTS_FILE.bak" || - die "Could not rename '$GRAFTS_FILE' to '$GRAFTS_FILE.bak'" - -echo "Success!" -echo "All the grafts in '$GRAFTS_FILE' have been converted to replace refs!" -echo "The grafts file '$GRAFTS_FILE' has been renamed: '$GRAFTS_FILE.bak'"
Signature · 1 line
2.17.0.windows.1.15.gaa56ade3205
v4 cover Deprecate .git/info/grafts
In reply to Johannes SchindelinIt is fragile, as there is no way for the revision machinery to say "but now I want to traverse the graph ignoring the graft file" e.g. when pushing commits to a remote repository (which, as a consequence, can miss commits).
And we already have a better solution with git replace --graft <comit> [<parent>...].
Changes since v3:
- The argv_array_split() declaration now has a clear comment indicating that it does not perform any "un-quoting" but goes purely by whitespace.
- Fixed t6050 under GETTEXT_POISON.
Johannes Schindelin (11):
argv_array: offer to split a string by whitespace
commit: Let the callback of for_each_mergetag return on error
replace: avoid using die() to indicate a bug
replace: "libify" create_graft() and callees
replace: introduce --convert-graft-file
Add a test for git replace --convert-graft-file Deprecate support for .git/info/grafts
filter-branch: stop suggesting to use grafts technical/shallow: describe the relationship with replace refs technical/shallow: describe why shallow cannot use replace refs Remove obsolete script to convert grafts to replace refs
Documentation/git-filter-branch.txt | 2 +- Documentation/git-replace.txt | 11 +- Documentation/technical/shallow.txt | 24 ++- advice.c | 2 + advice.h | 1 + argv-array.c | 20 +++ argv-array.h | 2 + builtin/replace.c | 189 +++++++++++++++------- commit.c | 18 ++- commit.h | 4 +- contrib/convert-grafts-to-replace-refs.sh | 28 ---- log-tree.c | 13 +- t/t6001-rev-list-graft.sh | 9 ++ t/t6050-replace.sh | 20 +++ 14 files changed, 236 insertions(+), 107 deletions(-) delete mode 100755 contrib/convert-grafts-to-replace-refs.sh
base-commit: fe0a9eaf31dd0c349ae4308498c33a5c3794b293
Published-As: https://github.com/dscho/git/releases/tag/deprecate-grafts-v4
Fetch-It-Via: git fetch https://github.com/dscho/git deprecate-grafts-v4
Interdiff vs v3:
diff --git a/argv-array.h b/argv-array.h
index c7c397695df..750c30d2f2c 100644
--- a/argv-array.h
+++ b/argv-array.h
@@ -19,6 +19,7 @@ LAST_ARG_MUST_BE_NULL
void argv_array_pushl(struct argv_array *, ...);
void argv_array_pushv(struct argv_array *, const char **); void argv_array_pop(struct argv_array *);
+/* Splits by whitespace; does not handle quoted arguments! */
void argv_array_split(struct argv_array *, const char *); void argv_array_clear(struct argv_array *);
const char **argv_array_detach(struct argv_array *);
diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh index 8a3ee7c3db9..bed86a0af3d 100755
--- a/t/t6050-replace.sh
+++ b/t/t6050-replace.sh
@@ -460,8 +460,8 @@ test_expect_success '--convert-graft-file' '
test_when_finished "rm -f .git/info/grafts" && echo $EMPTY_BLOB $EMPTY_TREE >.git/info/grafts && test_must_fail git replace --convert-graft-file 2>err &&
- grep "$EMPTY_BLOB $EMPTY_TREE" err &&
- grep "$EMPTY_BLOB $EMPTY_TREE" .git/info/grafts
+ test_i18ngrep "$EMPTY_BLOB $EMPTY_TREE" err &&
+ test_i18ngrep "$EMPTY_BLOB $EMPTY_TREE" .git/info/grafts
'
test_done
Signature · 1 line
2.17.0.windows.1.15.gaa56ade3205
PATCH v4 1/11 argv_array: offer to split a string by whitespace
This is a simple function that will interpret a string as a whitespace delimited list of values, and add those values into the array.
Note: this function does not (yet) offer to split by arbitrary delimiters, or keep empty values in case of runs of whitespace, or de-quote Unix shell style. All fo this functionality can be added later, when and if needed.
--- argv-array.c | 20 ++++++++++++++++++++ argv-array.h | 2 ++ 2 files changed, 22 insertions(+)
argv-array.c +20 -0
@@ -64,6 +64,26 @@ void argv_array_pop(struct argv_array *array) array->argc--; } +void argv_array_split (struct argv_array *array,const char *to_split) +{ +while (isspace (*to_split)) + to_split++; +for (;;) { +const char *p = to_split; + +if (!*p) +break ; + +while (*p && !isspace (*p)) + p++; +argv_array_push_nodup (array,xstrndup (to_split, p - to_split)); + +while (isspace (*p)) + p++; + to_split = p; + } +} +void argv_array_clear (struct argv_array *array) {if (array->argv != empty_argv) {
argv-array.h +2 -0
@@ -19,6 +19,8 @@ LAST_ARG_MUST_BE_NULLvoid argv_array_pushl (struct argv_array *, ...);void argv_array_pushv (struct argv_array *,const char **);void argv_array_pop (struct argv_array *); +/* Splits by whitespace; does not handle quoted arguments! */ +void argv_array_split (struct argv_array *,const char *);void argv_array_clear (struct argv_array *);const char **argv_array_detach (struct argv_array *);
Signature · 1 line
2.17.0.windows.1.15.gaa56ade3205
Re: [PATCH v4 01/11] argv_array: offer to split a string by whitespace
argv-array.c void argv_array_pop(struct argv_array *array) · line 79
Quoted 13 lines · Johannes Schindelin
argv-array.c +13
-0+void argv_array_split (struct argv_array *array,const char *to_split) +{ +while (isspace (*to_split)) + to_split++; +for (;;) { +const char *p = to_split; + +if (!*p) +break ; + +while (*p && !isspace (*p)) + p++; +argv_array_push_nodup (array,xstrndup (to_split, p - to_split));
Can *p be '\0' at this point? If isspace('\0') behaves sensibly, then we wouldn't have had to check "*p &&" in the previous while() loop. If not, then while() on the next line needs the same "*p &&" check. I think ou r isspace('\0') is reliably false, so we could drop "*p &&" but I do not mind spelling it out that we care about end of string explicitly. I however think we would want to be consistent inside a single loop which stance we take.
argv-array.c void argv_array_pop(struct argv_array *array) · line 85
Quoted 5 lines
argv-array.c +5
-0+while (isspace (*p)) + p++; + to_split = p; + } +}
I wonder if the initial "skip spaces" can come inside the main loop, perhaps like so:
for (;;) {
const char *p = to_split;
while (*p && isspace(*p++))
;
if (!*p)
break;
for (to_split = p; *p && !isspace(*p); p++)
;
argv_array_push_nodup(array, xstrdup(to_split, p - to_split));
}or something.
Duplicated "skip over spaces" loop is not a big deal, though.
Re: [PATCH v4 01/11] argv_array: offer to split a string by whitespace
Quoted 17 lines · Junio C Hamano
argv-array.c void argv_array_pop(struct argv_array *array) · line 79
Quoted 13 lines · Johannes Schindelin
argv-array.c +13
-0+void argv_array_split (struct argv_array *array,const char *to_split) +{ +while (isspace (*to_split)) + to_split++; +for (;;) { +const char *p = to_split; + +if (!*p) +break ; + +while (*p && !isspace (*p)) + p++; +argv_array_push_nodup (array,xstrndup (to_split, p - to_split));Can *p be '\0' at this point?
My thinko. We do want to stop on NUL even though our isspace() says NUL is not a space. And using while (isspace(*p)) below without checking for NUL also makes sense.
Sorry for the noise.
PATCH v4 2/11 commit: Let the callback of for_each_mergetag return on error
In reply to Johannes SchindelinThis is yet another patch to be filed under the keyword "libification".
There is one subtle change in behavior here, where a git log that has been asked to show the mergetags would now stop reporting the mergetags upon the first failure, whereas previously, it would have continued to the next mergetag, if any.
In practice, that change should not matter, as it is 1) uncommon to perform octopus merges using multiple tags as merge heads, and 2) when the user asks to be shown those tags, they really should be there.
--- builtin/replace.c | 8 ++++---- commit.c | 8 +++++--- commit.h | 4 ++-- log-tree.c | 13 +++++++------ 4 files changed, 18 insertions(+), 15 deletions(-)
builtin/replace.c +4 -4
@@ -345,7 +345,7 @@ struct check_mergetag_data {const char **argv; }; -static void check_one_mergetag (struct commit *commit, +static int check_one_mergetag (struct commit *commit,struct commit_extra_header *extra,void *data) {
@@ -368,20 +368,20 @@ static void check_one_mergetag(struct commit *commit,if (get_oid (mergetag_data->argv[i], &oid) <0 )die (_ ("Not a valid object name: '%s'" ), mergetag_data->argv[i]);if (!oidcmp (&tag->tagged->oid, &oid)) -return ;/* found */ +return 0 ;/* found */ }die (_ ("original commit '%s' contains mergetag '%s' that is discarded; " "use --edit instead of --graft" ), ref,oid_to_hex (&tag_oid)); } -static void check_mergetags (struct commit *commit,int argc,const char **argv) +static int check_mergetags (struct commit *commit,int argc,const char **argv) {struct check_mergetag_data mergetag_data; mergetag_data.argc = argc; mergetag_data.argv = argv; -for_each_mergetag (check_one_mergetag, commit, &mergetag_data); +return for_each_mergetag (check_one_mergetag, commit, &mergetag_data); }static int create_graft (int argc,const char **argv,int force)
commit.c +5 -3
@@ -1288,17 +1288,19 @@ struct commit_extra_header *read_commit_extra_headers(struct commit *commit,return extra; } -void for_each_mergetag (each_mergetag_fn fn,struct commit *commit,void *data) +int for_each_mergetag (each_mergetag_fn fn,struct commit *commit,void *data) {struct commit_extra_header *extra, *to_free; +int res =0 ; to_free =read_commit_extra_headers (commit,NULL ); -for (extra = to_free; extra; extra = extra->next) { +for (extra = to_free; !res && extra; extra = extra->next) {if (strcmp (extra->key,"mergetag" ))continue ;/* not a merge tag */ -fn (commit, extra, data); + res =fn (commit, extra, data); }free_commit_extra_headers (to_free); +return res; }static inline int standard_header_field (const char *field,size_t len)
commit.h +2 -2
@@ -291,10 +291,10 @@ extern const char *find_commit_header(const char *msg, const char *key,/* Find the end of the log message, the right place for a new trailer. */ extern int ignore_non_trailer (const char *buf,size_t len); -typedef void (*each_mergetag_fn )(struct commit *commit,struct commit_extra_header *extra, +typedef int (*each_mergetag_fn )(struct commit *commit,struct commit_extra_header *extra,void *cb_data); -extern void for_each_mergetag (each_mergetag_fn fn,struct commit *commit,void *data); +extern int for_each_mergetag (each_mergetag_fn fn,struct commit *commit,void *data);struct merge_remote_desc {struct object *obj;/* the named object, could be a tag */
log-tree.c +7 -6
@@ -488,9 +488,9 @@ static int is_common_merge(const struct commit *commit) && !commit->parents->next->next); } -static void show_one_mergetag (struct commit *commit, -struct commit_extra_header *extra, -void *data) +static int show_one_mergetag (struct commit *commit, +struct commit_extra_header *extra, +void *data) {struct rev_info *opt = (struct rev_info *)data;struct object_id oid;
@@ -502,7 +502,7 @@ static void show_one_mergetag(struct commit *commit,hash_object_file (extra->value, extra->len,type_name (OBJ_TAG ), &oid); tag =lookup_tag (&oid);if (!tag) -return ;/* error message already given */ +return -1 ;/* error message already given */ strbuf_init (&verify_message,256 );if (parse_tag_buffer (tag, extra->value, extra->len))
@@ -536,11 +536,12 @@ static void show_one_mergetag(struct commit *commit,show_sig_lines (opt, status, verify_message.buf);strbuf_release (&verify_message); +return 0 ; } -static void show_mergetag (struct rev_info *opt,struct commit *commit) +static int show_mergetag (struct rev_info *opt,struct commit *commit) { -for_each_mergetag (show_one_mergetag, commit, opt); +return for_each_mergetag (show_one_mergetag, commit, opt); }void show_log (struct rev_info *opt)
Signature · 1 line
2.17.0.windows.1.15.gaa56ade3205
PATCH v4 3/11 replace: avoid using die() to indicate a bug
In reply to Johannes SchindelinWe have the BUG() macro for that purpose.
--- builtin/replace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
builtin/replace.c +1 -1
@@ -501,6 +501,6 @@ int cmd_replace(int argc, const char **argv, const char *prefix)return list_replace_refs (argv[0 ], format);default : -die ("BUG: invalid cmdmode %d" , (int )cmdmode); +BUG ("invalid cmdmode %d" , (int )cmdmode); } }
Signature · 1 line
2.17.0.windows.1.15.gaa56ade3205
PATCH v4 4/11 replace: "libify" create_graft() and callees
In reply to Johannes SchindelinFile this away as yet another patch in the "libification" category.
As with all useful functions, in the next commit we want to use create_graft() from a higher-level function where it would be inconvenient if the called function simply die()s: if there is a problem, we want to let the user know how to proceed, and the callee simply has no way of knowing what to say.
--- builtin/replace.c | 135 ++++++++++++++++++++++++++++------------------ 1 file changed, 84 insertions(+), 51 deletions(-)
builtin/replace.c +84 -51
@@ -79,9 +79,9 @@ static int list_replace_refs(const char *pattern, const char *format)else if (!strcmp (format,"long" )) data.format =REPLACE_FORMAT_LONG ;else -die ("invalid replace format '%s'\n" -"valid formats are 'short', 'medium' and 'long'\n" , - format); +return error ("invalid replace format '%s'\n" +"valid formats are 'short', 'medium' and 'long'\n" , + format);for_each_replace_ref (show_reference, (void *)&data);
@@ -134,7 +134,7 @@ static int delete_replace_ref(const char *name, const char *ref,return 0 ; } -static void check_ref_valid (struct object_id *object, +static int check_ref_valid (struct object_id *object,struct object_id *prev,struct strbuf *ref,int force)
@@ -142,12 +142,13 @@ static void check_ref_valid(struct object_id *object,strbuf_reset (ref);strbuf_addf (ref,"%s%s" , git_replace_ref_base,oid_to_hex (object));if (check_refname_format (ref->buf,0 )) -die ("'%s' is not a valid ref name." , ref->buf); +return error ("'%s' is not a valid ref name." , ref->buf);if (read_ref (ref->buf, prev))oidclr (prev);else if (!force) -die ("replace ref '%s' already exists" , ref->buf); +return error ("replace ref '%s' already exists" , ref->buf); +return 0 ; } static int replace_object_oid(const char *object_ref,
@@ -161,28 +162,31 @@ static int replace_object_oid(const char *object_ref,struct strbuf ref =STRBUF_INIT ;struct ref_transaction *transaction;struct strbuf err =STRBUF_INIT ; +int res =0 ; obj_type =oid_object_info (object,NULL ); repl_type =oid_object_info (repl,NULL );if (!force && obj_type != repl_type) -die ("Objects must be of the same type.\n" -"'%s' points to a replaced object of type '%s'\n" -"while '%s' points to a replacement object of type '%s'." , - object_ref,type_name (obj_type), - replace_ref,type_name (repl_type)); +return error ("Objects must be of the same type.\n" +"'%s' points to a replaced object of type '%s'\n" +"while '%s' points to a replacement object of " +"type '%s'." , + object_ref,type_name (obj_type), + replace_ref,type_name (repl_type)); -check_ref_valid (object, &prev, &ref, force); +if (check_ref_valid (object, &prev, &ref, force)) +return -1 ; transaction =ref_transaction_begin (&err);if (!transaction ||ref_transaction_update (transaction, ref.buf, repl, &prev,0 ,NULL , &err) ||ref_transaction_commit (transaction, &err)) -die ("%s" , err.buf); + res =error ("%s" , err.buf);ref_transaction_free (transaction);strbuf_release (&ref); -return 0 ; +return res; }static int replace_object (const char *object_ref,const char *replace_ref,int force)
@@ -190,9 +194,11 @@ static int replace_object(const char *object_ref, const char *replace_ref, int fstruct object_id object, repl;if (get_oid (object_ref, &object)) -die ("Failed to resolve '%s' as a valid ref." , object_ref); +return error ("Failed to resolve '%s' as a valid ref." , + object_ref);if (get_oid (replace_ref, &repl)) -die ("Failed to resolve '%s' as a valid ref." , replace_ref); +return error ("Failed to resolve '%s' as a valid ref." , + replace_ref);return replace_object_oid (object_ref, &object, replace_ref, &repl, force); }
@@ -202,7 +208,7 @@ static int replace_object(const char *object_ref, const char *replace_ref, int f* If "raw" is true, then the object's raw contents are printed according to * "type". Otherwise, we pretty-print the contents for human editing. */ -static void export_object (const struct object_id *oid,enum object_type type, +static int export_object (const struct object_id *oid,enum object_type type,int raw,const char *filename) {struct child_process cmd =CHILD_PROCESS_INIT ;
@@ -210,7 +216,7 @@ static void export_object(const struct object_id *oid, enum object_type type, fd =open (filename,O_WRONLY |O_CREAT |O_TRUNC ,0666 );if (fd <0 ) -die_errno ("unable to open %s for writing" , filename); +return error_errno ("unable to open %s for writing" , filename);argv_array_push (&cmd.args,"--no-replace-objects" );argv_array_push (&cmd.args,"cat-file" );
@@ -223,7 +229,8 @@ static void export_object(const struct object_id *oid, enum object_type type, cmd.out = fd;if (run_command (&cmd)) -die ("cat-file reported failure" ); +return error ("cat-file reported failure" ); +return 0 ; }/*
@@ -231,14 +238,14 @@ static void export_object(const struct object_id *oid, enum object_type type,* interpreting it as "type", and writing the result to the object database. * The sha1 of the written object is returned via sha1. */ -static void import_object (struct object_id *oid,enum object_type type, +static int import_object (struct object_id *oid,enum object_type type,int raw,const char *filename) {int fd; fd =open (filename,O_RDONLY );if (fd <0 ) -die_errno ("unable to open %s for reading" , filename); +return error_errno ("unable to open %s for reading" , filename);if (!raw && type ==OBJ_TREE ) {const char *argv[] = {"mktree" ,NULL };
@@ -250,27 +257,38 @@ static void import_object(struct object_id *oid, enum object_type type, cmd.in = fd; cmd.out =-1 ; -if (start_command (&cmd)) -die ("unable to spawn mktree" ); +if (start_command (&cmd)) { +close (fd); +return error ("unable to spawn mktree" ); + } -if (strbuf_read (&result, cmd.out,41 ) <0 ) -die_errno ("unable to read from mktree" ); +if (strbuf_read (&result, cmd.out,41 ) <0 ) { +close (fd); +close (cmd.out); +return error_errno ("unable to read from mktree" ); + }close (cmd.out); -if (finish_command (&cmd)) -die ("mktree reported failure" ); -if (get_oid_hex (result.buf, oid) <0 ) -die ("mktree did not return an object name" ); +if (finish_command (&cmd)) { +strbuf_release (&result); +return error ("mktree reported failure" ); + } +if (get_oid_hex (result.buf, oid) <0 ) { +strbuf_release (&result); +return error ("mktree did not return an object name" ); + }strbuf_release (&result); }else {struct stat st;int flags =HASH_FORMAT_CHECK |HASH_WRITE_OBJECT ; -if (fstat (fd, &st) <0 ) -die_errno ("unable to fstat %s" , filename); +if (fstat (fd, &st) <0 ) { +close (fd); +return error_errno ("unable to fstat %s" , filename); + }if (index_fd (oid, fd, &st, type,NULL , flags) <0 ) -die ("unable to write object to database" ); +return error ("unable to write object to database" );/* index_fd close()s fd for us */ }
@@ -278,6 +296,7 @@ static void import_object(struct object_id *oid, enum object_type type,* No need to close(fd) here; both run-command and index-fd * will have done it for us. */ +return 0 ; }static int edit_and_replace (const char *object_ref,int force,int raw)
@@ -288,19 +307,23 @@ static int edit_and_replace(const char *object_ref, int force, int raw)struct strbuf ref =STRBUF_INIT ;if (get_oid (object_ref, &old_oid) <0 ) -die ("Not a valid object name: '%s'" , object_ref); +return error ("Not a valid object name: '%s'" , object_ref); type =oid_object_info (&old_oid,NULL );if (type <0 ) -die ("unable to get object type for %s" ,oid_to_hex (&old_oid)); +return error ("unable to get object type for %s" , +oid_to_hex (&old_oid)); -check_ref_valid (&old_oid, &prev, &ref, force); +if (check_ref_valid (&old_oid, &prev, &ref, force)) +return -1 ;strbuf_release (&ref); -export_object (&old_oid, type, raw, tmpfile); +if (export_object (&old_oid, type, raw, tmpfile)) +return -1 ;if (launch_editor (tmpfile,NULL ,NULL ) <0 ) -die ("editing object file failed" ); -import_object (&new_oid, type, raw, tmpfile); +return error ("editing object file failed" ); +if (import_object (&new_oid, type, raw, tmpfile)) +return -1 ;free (tmpfile);
@@ -310,7 +333,7 @@ static int edit_and_replace(const char *object_ref, int force, int raw)return replace_object_oid (object_ref, &old_oid,"replacement" , &new_oid, force); } -static void replace_parents (struct strbuf *buf,int argc,const char **argv) +static int replace_parents (struct strbuf *buf,int argc,const char **argv) {struct strbuf new_parents =STRBUF_INIT ;const char *parent_start, *parent_end;
@@ -328,8 +351,10 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv)for (i =0 ; i < argc; i++) {struct object_id oid;if (get_oid (argv[i], &oid) <0 ) -die (_ ("Not a valid object name: '%s'" ), argv[i]); -lookup_commit_or_die (&oid, argv[i]); +return error (_ ("Not a valid object name: '%s'" ), + argv[i]); +if (!lookup_commit_reference (&oid)) +return error (_ ("could not parse %s" ), argv[i]);strbuf_addf (&new_parents,"parent %s\n" ,oid_to_hex (&oid)); }
@@ -338,6 +363,7 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv) new_parents.buf, new_parents.len);strbuf_release (&new_parents); +return 0 ; }struct check_mergetag_data {
@@ -358,21 +384,23 @@ static int check_one_mergetag(struct commit *commit,hash_object_file (extra->value, extra->len,type_name (OBJ_TAG ), &tag_oid); tag =lookup_tag (&tag_oid);if (!tag) -die (_ ("bad mergetag in commit '%s'" ), ref); +return error (_ ("bad mergetag in commit '%s'" ), ref);if (parse_tag_buffer (tag, extra->value, extra->len)) -die (_ ("malformed mergetag in commit '%s'" ), ref); +return error (_ ("malformed mergetag in commit '%s'" ), ref);/* iterate over new parents */ for (i =1 ; i < mergetag_data->argc; i++) {struct object_id oid;if (get_oid (mergetag_data->argv[i], &oid) <0 ) -die (_ ("Not a valid object name: '%s'" ), mergetag_data->argv[i]); +return error (_ ("Not a valid object name: '%s'" ), + mergetag_data->argv[i]);if (!oidcmp (&tag->tagged->oid, &oid))return 0 ;/* found */ } -die (_ ("original commit '%s' contains mergetag '%s' that is discarded; " -"use --edit instead of --graft" ), ref,oid_to_hex (&tag_oid)); +return error (_ ("original commit '%s' contains mergetag '%s' that is " +"discarded; use --edit instead of --graft" ), ref, +oid_to_hex (&tag_oid)); }static int check_mergetags (struct commit *commit,int argc,const char **argv)
@@ -394,24 +422,29 @@ static int create_graft(int argc, const char **argv, int force)unsigned long size;if (get_oid (old_ref, &old_oid) <0 ) -die (_ ("Not a valid object name: '%s'" ), old_ref); - commit =lookup_commit_or_die (&old_oid, old_ref); +return error (_ ("Not a valid object name: '%s'" ), old_ref); + commit =lookup_commit_reference (&old_oid); +if (!commit) +return error (_ ("could not parse %s" ), old_ref); buffer =get_commit_buffer (commit, &size);strbuf_add (&buf, buffer, size);unuse_commit_buffer (commit, buffer); -replace_parents (&buf, argc -1 , &argv[1 ]); +if (replace_parents (&buf, argc -1 , &argv[1 ]) <0 ) +return -1 ;if (remove_signature (&buf)) {warning (_ ("the original commit '%s' has a gpg signature." ), old_ref);warning (_ ("the signature will be removed in the replacement commit!" )); } -check_mergetags (commit, argc, argv); +if (check_mergetags (commit, argc, argv)) +return -1 ;if (write_object_file (buf.buf, buf.len, commit_type, &new_oid)) -die (_ ("could not write replacement commit for: '%s'" ), old_ref); +return error (_ ("could not write replacement commit for: '%s'" ), + old_ref);strbuf_release (&buf);
Signature · 1 line
2.17.0.windows.1.15.gaa56ade3205
Re: [PATCH v4 04/11] replace: "libify" create_graft() and callees
Hi Johannes,
builtin/replace.c static void import_object(struct object_id *oid, enum object_type type, · line 260
Quoted 7 lines · Johannes Schindelin
builtin/replace.c +4
-2@@ -250,27 +257,38 @@ static void import_object(struct object_id *oid, enum object_type type, -if (strbuf_read (&result, cmd.out,41 ) <0 ) -die_errno ("unable to read from mktree" ); +if (strbuf_read (&result, cmd.out,41 ) <0 ) { +close (fd); +close (cmd.out); +return error_errno ("unable to read from mktree" );
So before the errno is coming directly from strbuf_read, which will set errno on error to the desired errno. (It will come from an underlying read())
However close() may fail and clobber errno,
so I would think we'd need to
if (strbuf_read(&result, cmd.out, 41) < 0) {
int err = errno; /* close shall not clobber errno */
close(fd);
close(cmd.out);
errno = err;
return error_errno(...);
}builtin/replace.c static void import_object(struct object_id *oid, enum object_type type, · line 289
Quoted 6 lines
builtin/replace.c +4
-2-if (fstat (fd, &st) <0 ) -die_errno ("unable to fstat %s" , filename); +if (fstat (fd, &st) <0 ) { +close (fd); +return error_errno ("unable to fstat %s" , filename); + }
Same here?
An alternative would be to do
ret = error_errno(...)
close (..)
return ret;Quoted 19 lines
builtin/replace.c +5
-4@@ -288,19 +307,23 @@ static int edit_and_replace(const char *object_ref, int force, int raw)struct strbuf ref =STRBUF_INIT ;if (get_oid (object_ref, &old_oid) <0 ) -die ("Not a valid object name: '%s'" , object_ref); +return error ("Not a valid object name: '%s'" , object_ref); type =oid_object_info (&old_oid,NULL );if (type <0 ) -die ("unable to get object type for %s" ,oid_to_hex (&old_oid)); +return error ("unable to get object type for %s" , +oid_to_hex (&old_oid)); -check_ref_valid (&old_oid, &prev, &ref, force); +if (check_ref_valid (&old_oid, &prev, &ref, force)) +return -1 ;strbuf_release (&ref); -export_object (&old_oid, type, raw, tmpfile);
builtin/replace.c static int edit_and_replace(const char *object_ref, int force, int raw) · line 328
Quoted 10 lines
builtin/replace.c +5
-2+if (export_object (&old_oid, type, raw, tmpfile)) +return -1 ;if (launch_editor (tmpfile,NULL ,NULL ) <0 ) -die ("editing object file failed" ); -import_object (&new_oid, type, raw, tmpfile); +return error ("editing object file failed" ); +if (import_object (&new_oid, type, raw, tmpfile)) +return -1 ;free (tmpfile);
Do we need to free tmpfile in previous returns?
Quoted 24 lines
builtin/replace.c +6
-3@@ -394,24 +422,29 @@ static int create_graft(int argc, const char **argv, int force)unsigned long size;if (get_oid (old_ref, &old_oid) <0 ) -die (_ ("Not a valid object name: '%s'" ), old_ref); - commit =lookup_commit_or_die (&old_oid, old_ref); +return error (_ ("Not a valid object name: '%s'" ), old_ref); + commit =lookup_commit_reference (&old_oid); +if (!commit) +return error (_ ("could not parse %s" ), old_ref); buffer =get_commit_buffer (commit, &size);strbuf_add (&buf, buffer, size);unuse_commit_buffer (commit, buffer); -replace_parents (&buf, argc -1 , &argv[1 ]); +if (replace_parents (&buf, argc -1 , &argv[1 ]) <0 ) +return -1 ;if (remove_signature (&buf)) {warning (_ ("the original commit '%s' has a gpg signature." ), old_ref);warning (_ ("the signature will be removed in the replacement commit!" )); }
builtin/replace.c static int create_graft(int argc, const char **argv, int force) · line 449
Quoted 10 lines
builtin/replace.c +4
-2-check_mergetags (commit, argc, argv); +if (check_mergetags (commit, argc, argv)) +return -1 ;if (write_object_file (buf.buf, buf.len, commit_type, &new_oid)) -die (_ ("could not write replacement commit for: '%s'" ), old_ref); +return error (_ ("could not write replacement commit for: '%s'" ), + old_ref);strbuf_release (&buf);
Release &buf in the other return cases, too?
Thanks,
Stefan
Re: [PATCH v4 04/11] replace: "libify" create_graft() and callees
Hi Stefan,
Quoted 13 lines · Stefan Beller
builtin/replace.c static void import_object(struct object_id *oid, enum object_type type, · line 260
Quoted 7 lines · Johannes Schindelin
builtin/replace.c +4
-2@@ -250,27 +257,38 @@ static void import_object(struct object_id *oid, enum object_type type, -if (strbuf_read (&result, cmd.out,41 ) <0 ) -die_errno ("unable to read from mktree" ); +if (strbuf_read (&result, cmd.out,41 ) <0 ) { +close (fd); +close (cmd.out); +return error_errno ("unable to read from mktree" );So before the errno is coming directly from
strbuf_read, which will set errno on error to the desired errno. (It will come from an underlyingread())
Yes, you are right!
Quoted 10 lines
However
close()may fail and clobber errno,
so I would think we'd need toif (strbuf_read(&result, cmd.out, 41) < 0) { int err = errno; /* close shall not clobber errno */ close(fd); close(cmd.out); errno = err; return error_errno(...); }
I went for the easier route: call error_errno() before close(fd), and then return -1 after close(cmd.out). Since error_errno() always returns -1, the result is pretty much the same (I do not think that we want the caller of import_object() to rely on the errno).
Quoted 8 lines
builtin/replace.c static void import_object(struct object_id *oid, enum object_type type, · line 289
Quoted 6 lines
builtin/replace.c +4
-2-if (fstat (fd, &st) <0 ) -die_errno ("unable to fstat %s" , filename); +if (fstat (fd, &st) <0 ) { +close (fd); +return error_errno ("unable to fstat %s" , filename); + }Same here?
Yep.
Quoted 4 lines
An alternative would be to do
ret = error_errno(...) close (..) return ret;
I even saved one variable ;-)
Quoted 28 lines
Quoted 28 lines
builtin/replace.c +10
-6@@ -288,19 +307,23 @@ static int edit_and_replace(const char *object_ref, int force, int raw)struct strbuf ref =STRBUF_INIT ;if (get_oid (object_ref, &old_oid) <0 ) -die ("Not a valid object name: '%s'" , object_ref); +return error ("Not a valid object name: '%s'" , object_ref); type =oid_object_info (&old_oid,NULL );if (type <0 ) -die ("unable to get object type for %s" ,oid_to_hex (&old_oid)); +return error ("unable to get object type for %s" , +oid_to_hex (&old_oid)); -check_ref_valid (&old_oid, &prev, &ref, force); +if (check_ref_valid (&old_oid, &prev, &ref, force)) +return -1 ;strbuf_release (&ref); -export_object (&old_oid, type, raw, tmpfile); +if (export_object (&old_oid, type, raw, tmpfile)) +return -1 ;if (launch_editor (tmpfile,NULL ,NULL ) <0 ) -die ("editing object file failed" ); -import_object (&new_oid, type, raw, tmpfile); +return error ("editing object file failed" ); +if (import_object (&new_oid, type, raw, tmpfile)) +return -1 ;
Quoted 3 lines
builtin/replace.c static int edit_and_replace(const char *object_ref, int force, int raw) · line 328
Quoted 1 line
builtin/replace.c +0
-0free (tmpfile);Do we need to free tmpfile in previous returns?
Oy vey. How many more mistakes can I introduce in one commit...
Quoted 28 lines
Quoted 28 lines
builtin/replace.c +8
-4@@ -394,24 +422,29 @@ static int create_graft(int argc, const char **argv, int force)unsigned long size;if (get_oid (old_ref, &old_oid) <0 ) -die (_ ("Not a valid object name: '%s'" ), old_ref); - commit =lookup_commit_or_die (&old_oid, old_ref); +return error (_ ("Not a valid object name: '%s'" ), old_ref); + commit =lookup_commit_reference (&old_oid); +if (!commit) +return error (_ ("could not parse %s" ), old_ref); buffer =get_commit_buffer (commit, &size);strbuf_add (&buf, buffer, size);unuse_commit_buffer (commit, buffer); -replace_parents (&buf, argc -1 , &argv[1 ]); +if (replace_parents (&buf, argc -1 , &argv[1 ]) <0 ) +return -1 ;if (remove_signature (&buf)) {warning (_ ("the original commit '%s' has a gpg signature." ), old_ref);warning (_ ("the signature will be removed in the replacement commit!" )); } -check_mergetags (commit, argc, argv); +if (check_mergetags (commit, argc, argv)) +return -1 ;
Quoted 8 lines
builtin/replace.c static int create_graft(int argc, const char **argv, int force) · line 449
Quoted 6 lines
builtin/replace.c +2
-1if (write_object_file (buf.buf, buf.len, commit_type, &new_oid)) -die (_ ("could not write replacement commit for: '%s'" ), old_ref); +return error (_ ("could not write replacement commit for: '%s'" ), + old_ref);strbuf_release (&buf);Release &buf in the other return cases, too?
Absolutely.
Thank you for helping me improve these patches,
Dscho
Re: [PATCH v4 04/11] replace: "libify" create_graft() and callees
Hi Johannes,
Quoted 3 lines · Johannes Schindelin
Oy vey. How many more mistakes can I introduce in one commit...
I ask this myself all the time, but Software is hard when not having computer assisted checks. The test suite doesn't quite count here, as it doesn't yell loudly enough for leaks in corner cases.
Thanks for taking these seriously, I was unsure if the first issues (close() clobbering the errno) were sever enough to bother. It complicates the code, but the effect is theoretical) (for EBADF) or a real niche corner case (EINTR).
Speaking of that, I wonder if we eventually want to have a wrapper
int xclose(int fd)
{
int err = errno;
int ret = close(fd)
if (errno == EINTR)
/* on linux we don't care about this, other OSes? */
;
errno = err;
return ret;
}Though not in this series.
Thanks,
Stefan
Re: [PATCH v4 04/11] replace: "libify" create_graft() and callees
Hi Stefan,
Quoted 9 lines · Stefan Beller
Quoted 3 lines · Johannes Schindelin
Oy vey. How many more mistakes can I introduce in one commit...
I ask this myself all the time, but Software is hard when not having computer assisted checks.
Right. I still hope to find some time to play with Infer (Open Source alternative to Coverity, started at Facebook). Windows support is not there yet, which is the main hold-up for me (if I had the time, I would spend it on adding Windows support to Infer). I could imagine that Infer might be flexible enough to ask these questions programmatically:
- is there a code path that forgets to close() file handles?
- is there a code path that forgets to release strbufs?
- are there redundant strbuf_release() calls?
Quoted 2 lines
The test suite doesn't quite count here, as it doesn't yell loudly enough for leaks in corner cases.
Right, and running everything through valgrind is not fast enough. Besides, this misses non-Linux code paths.
Quoted 4 lines
Thanks for taking these seriously, I was unsure if the first issues (
close()clobbering the errno) were sever enough to bother. It complicates the code, but the effect is theoretical) (for EBADF) or a real niche corner case (EINTR).
Maybe it might not be *that* serious currently. It is incorrect, though, and makes the code less reusable. I like to copy-edit my code a lot when refactoring is not an option.
Quoted 14 lines
Speaking of that, I wonder if we eventually want to have a wrapper
int xclose(int fd)
{ int err = errno; int ret = close(fd) if (errno == EINTR) /* on linux we don't care about this, other OSes? */ ; errno = err; return ret; }
Quoted 1 line
Though not in this series.
Or maybe a Coccinelle rule? (Still not in this series, though.)
Ciao,
Dscho
PATCH v4 5/11 replace: introduce --convert-graft-file
In reply to Johannes SchindelinThis option is intended to help with the transition away from the now-deprecated graft file.
--- Documentation/git-replace.txt | 11 ++++++--- builtin/replace.c | 44 ++++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 4 deletions(-)
Documentation/git-replace.txt +8 -3
@@ -11,6 +11,7 @@ SYNOPSIS 'git replace' [-f] <object> <replacement> 'git replace' [-f] --edit <object> 'git replace' [-f] --graft <commit> [<parent>...] +'git replace' [-f] --convert-graft-file 'git replace' -d <object>... 'git replace' [--format=<format>] [-l [<pattern>]]
@@ -87,9 +88,13 @@ OPTIONS content as <commit> except that its parents will be [<parent>...] instead of <commit>'s parents. A replacement ref is then created to replace <commit> with the newly created - commit. See contrib/convert-grafts-to-replace-refs.sh for an - example script based on this option that can convert grafts to - replace refs. + commit. Use `--convert-graft-file` to convert a + `$GIT_DIR/info/grafts` file and use replace refs instead. + +--convert-graft-file:: + Creates graft commits for all entries in `$GIT_DIR/info/grafts` + and deletes that file upon success. The purpose is to help users + with transitioning off of the now-deprecated graft file. -l <pattern>:: --list <pattern>::
builtin/replace.c +43 -1
80 lines not on this page · raw message
Signature · 1 line
2.17.0.windows.1.15.gaa56ade3205
Re: [PATCH v4 05/11] replace: introduce --convert-graft-file
Quoted 19 lines · Johannes Schindelin
This option is intended to help with the transition away from the now-deprecated graft file.
Signed-off-by Johannes Schindelin <[email protected]>---
builtin/replace.c +12
-0@@ -454,6 +455,38 @@ static int create_graft(int argc, const char **argv, int force) +static int convert_graft_file (int force) +{ +const char *graft_file =get_graft_file (); +FILE *fp =fopen_or_warn (graft_file,"r" ); +struct strbuf buf =STRBUF_INIT , err =STRBUF_INIT ; +struct argv_array args =ARGV_ARRAY_INIT ; + +if (!fp) +return -1 ; + +while (strbuf_getline (&buf, fp) !=EOF ) { +if (*buf.buf =='#' )
builtin/replace.c static int create_graft(int argc, const char **argv, int force) · line 476
Quoted 10 lines
builtin/replace.c +10
-0+continue ; + +argv_array_split (&args, buf.buf); +if (args.argc &&create_graft (args.argc, args.argv, force)) +strbuf_addf (&err,"\n\t%s" , buf.buf); +argv_array_clear (&args); + } + +strbuf_release (&buf); +argv_array_clear (&args);
This argv_array_clear() is redundant, isn't it?
Quoted 8 lines
builtin/replace.c +8
-0+if (!err.len) +return unlink_or_warn (graft_file); + +warning (_ ("could not convert the following graft(s):\n%s" ), err.buf); +strbuf_release (&err); + +return -1 ; +}
Re: [PATCH v4 05/11] replace: introduce --convert-graft-file
Hi Eric,
Quoted 25 lines · Eric Sunshine
Quoted 23 lines · Johannes Schindelin
This option is intended to help with the transition away from the now-deprecated graft file.
Signed-off-by Johannes Schindelin <[email protected]>---
builtin/replace.c +16
-0@@ -454,6 +455,38 @@ static int create_graft(int argc, const char **argv, int force) +static int convert_graft_file (int force) +{ +const char *graft_file =get_graft_file (); +FILE *fp =fopen_or_warn (graft_file,"r" ); +struct strbuf buf =STRBUF_INIT , err =STRBUF_INIT ; +struct argv_array args =ARGV_ARRAY_INIT ; + +if (!fp) +return -1 ; + +while (strbuf_getline (&buf, fp) !=EOF ) { +if (*buf.buf =='#' ) +continue ; + +argv_array_split (&args, buf.buf); +if (args.argc &&create_graft (args.argc, args.argv, force))
Quoted 8 lines
builtin/replace.c static int create_graft(int argc, const char **argv, int force) · line 479
Quoted 6 lines
builtin/replace.c +6
-0+strbuf_addf (&err,"\n\t%s" , buf.buf); +argv_array_clear (&args); + } + +strbuf_release (&buf); +argv_array_clear (&args);This
argv_array_clear()is redundant, isn't it?
It sure is!
Thank you for the review,
Dscho
PATCH v4 6/11 Add a test for `git replace --convert-graft-file`
In reply to Johannes SchindelinThe proof, as the saying goes, lies in the pudding. So here is a regression test that not only demonstrates what the option is supposed to accomplish, but also demonstrates that it does accomplish it.
--- t/t6050-replace.sh | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+)
t/t6050-replace.sh +20 -0
@@ -444,4 +444,24 @@ test_expect_success GPG '--graft on a commit with a mergetag' ' git replace -d $HASH10 ' +test_expect_success '--convert-graft-file' ' + : add and convert graft file && + printf "%s\n%s %s\n\n# comment\n%s\n" \ + $(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \ + >.git/info/grafts && + git replace --convert-graft-file && + test_path_is_missing .git/info/grafts && + + : verify that the history is now "grafted" && + git rev-list HEAD >out && + test_line_count = 4 out && + + : create invalid graft file and verify that it is not deleted && + test_when_finished "rm -f .git/info/grafts" && + echo $EMPTY_BLOB $EMPTY_TREE >.git/info/grafts && + test_must_fail git replace --convert-graft-file 2>err && + test_i18ngrep "$EMPTY_BLOB $EMPTY_TREE" err && + test_i18ngrep "$EMPTY_BLOB $EMPTY_TREE" .git/info/grafts +' + test_done
Signature · 1 line
2.17.0.windows.1.15.gaa56ade3205
PATCH v4 7/11 Deprecate support for .git/info/grafts
In reply to Johannes SchindelinThe grafts feature was a convenient way to "stitch together" ancient history to the fresh start of linux.git.
Its implementation is, however, not up to Git's standards, as there are too many ways where it can lead to surprising and unwelcome behavior.
For example, when pushing from a repository with active grafts, it is possible to miss commits that have been "grafted out", resulting in a broken state on the other side.
Also, the grafts feature is limited to "rewriting" commits' list of parents, it cannot replace anything else.
The much younger feature implemented as git replace set out to remedy those limitations and dangerous bugs.
Seeing as git replace is pretty mature by now (since 4228e8bc98 (replace: add --graft option, 2014-07-19) it can perform the graft file's duties), it is time to deprecate support for the graft file, and to retire it eventually.
--- advice.c | 2 ++ advice.h | 1 + commit.c | 10 ++++++++++ t/t6001-rev-list-graft.sh | 9 +++++++++ 4 files changed, 22 insertions(+)
advice.c +2 -0
@@ -19,6 +19,7 @@ int advice_rm_hints = 1;int advice_add_embedded_repo =1 ;int advice_ignored_hook =1 ;int advice_waiting_for_editor =1 ; +int advice_graft_file_deprecated =1 ; static struct { const char *name;
@@ -42,6 +43,7 @@ static struct { { "addembeddedrepo", &advice_add_embedded_repo }, { "ignoredhook", &advice_ignored_hook }, { "waitingforeditor", &advice_waiting_for_editor }, + { "graftfiledeprecated", &advice_graft_file_deprecated },/* make this an alias for backward compatibility */ { "pushnonfastforward", &advice_push_update_rejected }
advice.h +1 -0
@@ -21,6 +21,7 @@ extern int advice_rm_hints;extern int advice_add_embedded_repo;extern int advice_ignored_hook;extern int advice_waiting_for_editor; +extern int advice_graft_file_deprecated;int git_default_advice_config (const char *var,const char *value); __attribute__((format (printf, 1, 2)))
commit.c +10 -0
@@ -12,6 +12,7 @@#include "prio-queue.h" #include "sha1-lookup.h" #include "wt-status.h" +#include "advice.h" static struct commit_extra_header *read_commit_extra_header_lines (const char *buf,size_t len,const char **);
@@ -176,6 +177,15 @@ static int read_graft_file(const char *graft_file)struct strbuf buf =STRBUF_INIT ;if (!fp)return -1 ; +if (advice_graft_file_deprecated) +advise (_ ("Support for <GIT_DIR>/info/grafts is deprecated\n" +"and will be removed in a future Git version.\n" +"\n" +"Please use \"git replace --convert-graft-file\"\n" +"to convert the grafts into replace refs.\n" +"\n" +"Turn this message off by running\n" +"\"git config advice.graftFileDeprecated false\"" ));while (!strbuf_getwholeline (&buf, fp,'\n' )) {/* The format is just "Commit Parent1 Parent2 ...\n" */ struct commit_graft *graft =read_graft_line (&buf);
t/t6001-rev-list-graft.sh +9 -0
@@ -110,4 +110,13 @@ do " done + +test_expect_success 'show advice that grafts are deprecated' ' + git show HEAD 2>err && + test_i18ngrep "git replace" err && + test_config advice.graftFileDeprecated false && + git show HEAD 2>err && + test_i18ngrep ! "git replace" err +' + test_done
Signature · 1 line
2.17.0.windows.1.15.gaa56ade3205
PATCH v4 8/11 filter-branch: stop suggesting to use grafts
In reply to Johannes SchindelinThe graft file is deprecated now, so let's use replace refs in the example in filter-branch's man page instead.
--- Documentation/git-filter-branch.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
Documentation/git-filter-branch.txt +1 -1
@@ -288,7 +288,7 @@ git filter-branch --parent-filter \ or even simpler: ----------------------------------------------- -echo "$commit-id $graft-id" >> .git/info/grafts +git replace --graft $commit-id $graft-id git filter-branch $graft-id..HEAD -----------------------------------------------
Signature · 1 line
2.17.0.windows.1.15.gaa56ade3205
PATCH v4 9/11 technical/shallow: describe the relationship with replace refs
In reply to Johannes SchindelinNow that grafts are deprecated, we should start to assume that readers have no idea what grafts are. So it makes more sense to describe the "shallow" feature in terms of replace refs.
--- Documentation/technical/shallow.txt | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-)
Documentation/technical/shallow.txt +11 -8
@@ -9,14 +9,17 @@ these commits have no parents. ********************************************************* The basic idea is to write the SHA-1s of shallow commits into -$GIT_DIR/shallow, and handle its contents like the contents -of $GIT_DIR/info/grafts (with the difference that shallow -cannot contain parent information). - -This information is stored in a new file instead of grafts, or -even the config, since the user should not touch that file -at all (even throughout development of the shallow clone, it -was never manually edited!). +$GIT_DIR/shallow, and handle its contents similar to replace +refs (with the difference that shallow does not actually +create those replace refs) and very much like the deprecated +graft file (with the difference that shallow commits will +always have their parents grafted away, not replaced by +different parents). + +This information is stored in a special-purpose file because the +user should not touch that file at all (even throughout +development of the shallow clone, it was never manually +edited!). Each line contains exactly one SHA-1. When read, a commit_graft will be constructed, which has nr_parent < 0 to make it easier
Signature · 1 line
2.17.0.windows.1.15.gaa56ade3205
PATCH v4 10/11 technical/shallow: describe why shallow cannot use replace refs
In reply to Johannes SchindelinIt is tempting to do away with commit_graft altogether (in the long haul), now that grafts are deprecated.
However, the shallow feature needs a couple of things that the replace refs cannot fulfill. Let's point that out in the documentation.
--- Documentation/technical/shallow.txt | 7 +++++++ 1 file changed, 7 insertions(+)
Documentation/technical/shallow.txt +7 -0
@@ -25,6 +25,13 @@ Each line contains exactly one SHA-1. When read, a commit_graft will be constructed, which has nr_parent < 0 to make it easier to discern from user provided grafts. +Note that the shallow feature could not be changed easily to +use replace refs: a commit containing a `mergetag` is not allowed +to be replaced, not even by a root commit. Such a commit can be +made shallow, though. Also, having a `shallow` file explicitly +listing all the commits made shallow makes it a *lot* easier to +do shallow-specific things such as to deepen the history. + Since fsck-objects relies on the library to read the objects, it honours shallow commits automatically.
Signature · 1 line
2.17.0.windows.1.15.gaa56ade3205
PATCH v4 11/11 Remove obsolete script to convert grafts to replace refs
In reply to Johannes SchindelinThe functionality is now implemented as git replace --convert-graft-file.
--- contrib/convert-grafts-to-replace-refs.sh | 28 ----------------------- 1 file changed, 28 deletions(-) delete mode 100755 contrib/convert-grafts-to-replace-refs.sh
contrib/convert-grafts-to-replace-refs.sh deleted +0 -28
@@ -1,28 +0,0 @@ -#!/bin/sh - -# You should execute this script in the repository where you -# want to convert grafts to replace refs. - -GRAFTS_FILE="${GIT_DIR:-.git}/info/grafts" - -. $(git --exec-path)/git-sh-setup - -test -f "$GRAFTS_FILE" || die "Could not find graft file: '$GRAFTS_FILE'" - -grep '^[^# ]' "$GRAFTS_FILE" | -while read definition -do - if test -n "$definition" - then - echo "Converting: $definition" - git replace --graft $definition || - die "Conversion failed for: $definition" - fi -done - -mv "$GRAFTS_FILE" "$GRAFTS_FILE.bak" || - die "Could not rename '$GRAFTS_FILE' to '$GRAFTS_FILE.bak'" - -echo "Success!" -echo "All the grafts in '$GRAFTS_FILE' have been converted to replace refs!" -echo "The grafts file '$GRAFTS_FILE' has been renamed: '$GRAFTS_FILE.bak'"
Signature · 1 line
2.17.0.windows.1.15.gaa56ade3205
Re: [PATCH v4 00/11] Deprecate .git/info/grafts
In reply to Johannes SchindelinQuoted 8 lines · Johannes Schindelin
It is fragile, as there is no way for the revision machinery to say "but now I want to traverse the graph ignoring the graft file" e.g. when pushing commits to a remote repository (which, as a consequence, can miss commits).
And we already have a better solution with
git replace --graft <comit> [<parent>...].
Apart from some technical questions on patch 4 [1] this series looks good to me,.
Thanks,
Stefan
[1] https://public-inbox.org/git/CAGZ79ka=BLGCCTOw848m0SE9O+ZKhQfiW9RUz99W4=Gdg+7ofQ@mail.gmail.com/
v5 cover Deprecate .git/info/grafts
In reply to Johannes SchindelinIt is fragile, as there is no way for the revision machinery to say "but now I want to traverse the graph ignoring the graft file" e.g. when pushing commits to a remote repository (which, as a consequence, can miss commits).
And we already have a better solution with git replace --graft <comit> [<parent>...].
Changes since v4:
- Fixed resource leaks (missing free() and strbuf_release() calls when returning an error).
- Avoided error_errno() mistakenly using close()'s errno.
- Actually close the handle to the graft file after converting it.
- Changed the description of Documentation/technical/shallow to not even bother to describe how it might be construed to be similar to replace refs.
Johannes Schindelin (11):
argv_array: offer to split a string by whitespace
commit: Let the callback of for_each_mergetag return on error
replace: avoid using die() to indicate a bug
replace: "libify" create_graft() and callees
replace: introduce --convert-graft-file
Add a test for git replace --convert-graft-file Deprecate support for .git/info/grafts
filter-branch: stop suggesting to use grafts technical/shallow: stop referring to grafts technical/shallow: describe why shallow cannot use replace refs Remove obsolete script to convert grafts to replace refs
Documentation/git-filter-branch.txt | 2 +- Documentation/git-replace.txt | 11 +- Documentation/technical/shallow.txt | 20 +- advice.c | 2 + advice.h | 1 + argv-array.c | 20 ++ argv-array.h | 2 + builtin/replace.c | 218 +++++++++++++++------- commit.c | 18 +- commit.h | 4 +- contrib/convert-grafts-to-replace-refs.sh | 28 --- log-tree.c | 13 +- t/t6001-rev-list-graft.sh | 9 + t/t6050-replace.sh | 20 ++ 14 files changed, 253 insertions(+), 115 deletions(-) delete mode 100755 contrib/convert-grafts-to-replace-refs.sh
base-commit: 1f1cddd558b54bb0ce19c8ace353fd07b758510d
Published-As: https://github.com/dscho/git/releases/tag/deprecate-grafts-v5
Fetch-It-Via: git fetch https://github.com/dscho/git deprecate-grafts-v5
Interdiff vs v4:
diff --git a/Documentation/technical/shallow.txt b/Documentation/technical/shallow.txt index cb79181c2bb..01dedfe9ffe 100644
--- a/Documentation/technical/shallow.txt
+++ b/Documentation/technical/shallow.txt
@@ -8,18 +8,10 @@ repo, and therefore grafts are introduced pretending that
these commits have no parents.
*********************************************************
-The basic idea is to write the SHA-1s of shallow commits into -$GIT_DIR/shallow, and handle its contents similar to replace -refs (with the difference that shallow does not actually -create those replace refs) and very much like the deprecated -graft file (with the difference that shallow commits will -always have their parents grafted away, not replaced by -different parents).
-
-This information is stored in a special-purpose file because the -user should not touch that file at all (even throughout -development of the shallow clone, it was never manually -edited!).
+$GIT_DIR/shallow lists commit object names and tells Git to +pretend as if they are root commits (e.g. "git log" traversal +stops after showing them; "git fsck" does not complain saying +the commits listed on their "parent" lines do not exist).
Each line contains exactly one SHA-1. When read, a commit_graft will be constructed, which has nr_parent < 0 to make it easier
diff --git a/builtin/replace.c b/builtin/replace.c
index 6b3e0301e90..acd30e3d824 100644
--- a/builtin/replace.c
+++ b/builtin/replace.c
@@ -175,8 +175,10 @@ static int replace_object_oid(const char *object_ref,
object_ref, type_name(obj_type), replace_ref, type_name(repl_type));
- if (check_ref_valid(object, &prev, &ref, force))
+ if (check_ref_valid(object, &prev, &ref, force)) {
+ strbuf_release(&ref);
return -1;
+ }
transaction = ref_transaction_begin(&err);
if (!transaction ||
@@ -264,9 +266,10 @@ static int import_object(struct object_id *oid, enum object_type type,
}
if (strbuf_read(&result, cmd.out, 41) < 0) {
+ error_errno("unable to read from mktree");
close(fd);
close(cmd.out);
- return error_errno("unable to read from mktree");
+ return -1;
}
close(cmd.out);
@@ -285,8 +288,9 @@ static int import_object(struct object_id *oid, enum object_type type,
int flags = HASH_FORMAT_CHECK | HASH_WRITE_OBJECT;
if (fstat(fd, &st) < 0) {
+ error_errno("unable to fstat %s", filename);
close(fd);
- return error_errno("unable to fstat %s", filename);
+ return -1;
}
if (index_fd(oid, fd, &st, type, NULL, flags) < 0)
return error("unable to write object to database");
@@ -302,7 +306,7 @@ static int import_object(struct object_id *oid, enum object_type type,
static int edit_and_replace(const char *object_ref, int force, int raw)
{
- char *tmpfile = git_pathdup("REPLACE_EDITOBJ");
+ char *tmpfile;
enum object_type type;
struct object_id old_oid, new_oid, prev;
struct strbuf ref = STRBUF_INIT;
@@ -315,17 +319,20 @@ static int edit_and_replace(const char *object_ref, int force, int raw)
return error("unable to get object type for %s",
oid_to_hex(&old_oid));
- if (check_ref_valid(&old_oid, &prev, &ref, force))
+ if (check_ref_valid(&old_oid, &prev, &ref, force)) {
+ strbuf_release(&ref);
return -1;
+ }
strbuf_release(&ref);
- if (export_object(&old_oid, type, raw, tmpfile))
- return -1;
- if (launch_editor(tmpfile, NULL, NULL) < 0)
- return error("editing object file failed");
- if (import_object(&new_oid, type, raw, tmpfile))
+ tmpfile = git_pathdup("REPLACE_EDITOBJ");
+ if (export_object(&old_oid, type, raw, tmpfile) ||
+ (launch_editor(tmpfile, NULL, NULL) < 0 &&
+ error("editing object file failed")) ||
+ import_object(&new_oid, type, raw, tmpfile)) {
+ free(tmpfile);
return -1;
-
+ }
free(tmpfile);
if (!oidcmp(&old_oid, &new_oid))
@@ -351,11 +358,15 @@ static int replace_parents(struct strbuf *buf, int argc, const char **argv)
/* prepare new parents */
for (i = 0; i < argc; i++) {
struct object_id oid;
- if (get_oid(argv[i], &oid) < 0)
+ if (get_oid(argv[i], &oid) < 0) {
+ strbuf_release(&new_parents);
return error(_("Not a valid object name: '%s'"),
argv[i]);
- if (!lookup_commit_reference(&oid))
+ }
+ if (!lookup_commit_reference(&oid)) {
+ strbuf_release(&new_parents);
return error(_("could not parse %s"), argv[i]);
+ }
strbuf_addf(&new_parents, "parent %s\n", oid_to_hex(&oid));
}
@@ -432,20 +443,26 @@ static int create_graft(int argc, const char **argv, int force)
strbuf_add(&buf, buffer, size);
unuse_commit_buffer(commit, buffer);
- if (replace_parents(&buf, argc - 1, &argv[1]) < 0)
+ if (replace_parents(&buf, argc - 1, &argv[1]) < 0) {
+ strbuf_release(&buf);
return -1;
+ }
if (remove_signature(&buf)) {
warning(_("the original commit '%s' has a gpg signature."), old_ref); warning(_("the signature will be removed in the replacement commit!"));
}
- if (check_mergetags(commit, argc, argv))
+ if (check_mergetags(commit, argc, argv)) {
+ strbuf_release(&buf);
return -1;
+ }
- if (write_object_file(buf.buf, buf.len, commit_type, &new_oid))
+ if (write_object_file(buf.buf, buf.len, commit_type, &new_oid)) {
+ strbuf_release(&buf);
return error(_("could not write replacement commit for: '%s'"),
old_ref);
+ }
strbuf_release(&buf);
@@ -474,9 +491,9 @@ static int convert_graft_file(int force)
strbuf_addf(&err, "\n\t%s", buf.buf);
argv_array_clear(&args);
}
+ fclose(fp);
strbuf_release(&buf);
- argv_array_clear(&args);
if (!err.len)
return unlink_or_warn(graft_file);
Signature · 1 line
2.17.0.windows.1.33.gfcbb1fa0445
PATCH v5 1/11 argv_array: offer to split a string by whitespace
This is a simple function that will interpret a string as a whitespace delimited list of values, and add those values into the array.
Note: this function does not (yet) offer to split by arbitrary delimiters, or keep empty values in case of runs of whitespace, or de-quote Unix shell style. All fo this functionality can be added later, when and if needed.
--- argv-array.c | 20 ++++++++++++++++++++ argv-array.h | 2 ++ 2 files changed, 22 insertions(+)
argv-array.c +20 -0
@@ -64,6 +64,26 @@ void argv_array_pop(struct argv_array *array) array->argc--; } +void argv_array_split (struct argv_array *array,const char *to_split) +{ +while (isspace (*to_split)) + to_split++; +for (;;) { +const char *p = to_split; + +if (!*p) +break ; + +while (*p && !isspace (*p)) + p++; +argv_array_push_nodup (array,xstrndup (to_split, p - to_split)); + +while (isspace (*p)) + p++; + to_split = p; + } +} +void argv_array_clear (struct argv_array *array) {if (array->argv != empty_argv) {
argv-array.h +2 -0
@@ -19,6 +19,8 @@ LAST_ARG_MUST_BE_NULLvoid argv_array_pushl (struct argv_array *, ...);void argv_array_pushv (struct argv_array *,const char **);void argv_array_pop (struct argv_array *); +/* Splits by whitespace; does not handle quoted arguments! */ +void argv_array_split (struct argv_array *,const char *);void argv_array_clear (struct argv_array *);const char **argv_array_detach (struct argv_array *);
Signature · 1 line
2.17.0.windows.1.33.gfcbb1fa0445
PATCH v5 2/11 commit: Let the callback of for_each_mergetag return on error
In reply to Johannes SchindelinThis is yet another patch to be filed under the keyword "libification".
There is one subtle change in behavior here, where a git log that has been asked to show the mergetags would now stop reporting the mergetags upon the first failure, whereas previously, it would have continued to the next mergetag, if any.
In practice, that change should not matter, as it is 1) uncommon to perform octopus merges using multiple tags as merge heads, and 2) when the user asks to be shown those tags, they really should be there.
--- builtin/replace.c | 8 ++++---- commit.c | 8 +++++--- commit.h | 4 ++-- log-tree.c | 13 +++++++------ 4 files changed, 18 insertions(+), 15 deletions(-)
builtin/replace.c +4 -4
@@ -345,7 +345,7 @@ struct check_mergetag_data {const char **argv; }; -static void check_one_mergetag (struct commit *commit, +static int check_one_mergetag (struct commit *commit,struct commit_extra_header *extra,void *data) {
@@ -368,20 +368,20 @@ static void check_one_mergetag(struct commit *commit,if (get_oid (mergetag_data->argv[i], &oid) <0 )die (_ ("Not a valid object name: '%s'" ), mergetag_data->argv[i]);if (!oidcmp (&tag->tagged->oid, &oid)) -return ;/* found */ +return 0 ;/* found */ }die (_ ("original commit '%s' contains mergetag '%s' that is discarded; " "use --edit instead of --graft" ), ref,oid_to_hex (&tag_oid)); } -static void check_mergetags (struct commit *commit,int argc,const char **argv) +static int check_mergetags (struct commit *commit,int argc,const char **argv) {struct check_mergetag_data mergetag_data; mergetag_data.argc = argc; mergetag_data.argv = argv; -for_each_mergetag (check_one_mergetag, commit, &mergetag_data); +return for_each_mergetag (check_one_mergetag, commit, &mergetag_data); }static int create_graft (int argc,const char **argv,int force)
commit.c +5 -3
@@ -1288,17 +1288,19 @@ struct commit_extra_header *read_commit_extra_headers(struct commit *commit,return extra; } -void for_each_mergetag (each_mergetag_fn fn,struct commit *commit,void *data) +int for_each_mergetag (each_mergetag_fn fn,struct commit *commit,void *data) {struct commit_extra_header *extra, *to_free; +int res =0 ; to_free =read_commit_extra_headers (commit,NULL ); -for (extra = to_free; extra; extra = extra->next) { +for (extra = to_free; !res && extra; extra = extra->next) {if (strcmp (extra->key,"mergetag" ))continue ;/* not a merge tag */ -fn (commit, extra, data); + res =fn (commit, extra, data); }free_commit_extra_headers (to_free); +return res; }static inline int standard_header_field (const char *field,size_t len)
commit.h +2 -2
@@ -291,10 +291,10 @@ extern const char *find_commit_header(const char *msg, const char *key,/* Find the end of the log message, the right place for a new trailer. */ extern int ignore_non_trailer (const char *buf,size_t len); -typedef void (*each_mergetag_fn )(struct commit *commit,struct commit_extra_header *extra, +typedef int (*each_mergetag_fn )(struct commit *commit,struct commit_extra_header *extra,void *cb_data); -extern void for_each_mergetag (each_mergetag_fn fn,struct commit *commit,void *data); +extern int for_each_mergetag (each_mergetag_fn fn,struct commit *commit,void *data);struct merge_remote_desc {struct object *obj;/* the named object, could be a tag */
log-tree.c +7 -6
@@ -488,9 +488,9 @@ static int is_common_merge(const struct commit *commit) && !commit->parents->next->next); } -static void show_one_mergetag (struct commit *commit, -struct commit_extra_header *extra, -void *data) +static int show_one_mergetag (struct commit *commit, +struct commit_extra_header *extra, +void *data) {struct rev_info *opt = (struct rev_info *)data;struct object_id oid;
@@ -502,7 +502,7 @@ static void show_one_mergetag(struct commit *commit,hash_object_file (extra->value, extra->len,type_name (OBJ_TAG ), &oid); tag =lookup_tag (&oid);if (!tag) -return ;/* error message already given */ +return -1 ;/* error message already given */ strbuf_init (&verify_message,256 );if (parse_tag_buffer (tag, extra->value, extra->len))
@@ -536,11 +536,12 @@ static void show_one_mergetag(struct commit *commit,show_sig_lines (opt, status, verify_message.buf);strbuf_release (&verify_message); +return 0 ; } -static void show_mergetag (struct rev_info *opt,struct commit *commit) +static int show_mergetag (struct rev_info *opt,struct commit *commit) { -for_each_mergetag (show_one_mergetag, commit, opt); +return for_each_mergetag (show_one_mergetag, commit, opt); }void show_log (struct rev_info *opt)
Signature · 1 line
2.17.0.windows.1.33.gfcbb1fa0445
PATCH v5 3/11 replace: avoid using die() to indicate a bug
In reply to Johannes SchindelinWe have the BUG() macro for that purpose.
--- builtin/replace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
builtin/replace.c +1 -1
@@ -501,6 +501,6 @@ int cmd_replace(int argc, const char **argv, const char *prefix)return list_replace_refs (argv[0 ], format);default : -die ("BUG: invalid cmdmode %d" , (int )cmdmode); +BUG ("invalid cmdmode %d" , (int )cmdmode); } }
Signature · 1 line
2.17.0.windows.1.33.gfcbb1fa0445
PATCH v5 4/11 replace: "libify" create_graft() and callees
In reply to Johannes SchindelinFile this away as yet another patch in the "libification" category.
As with all useful functions, in the next commit we want to use create_graft() from a higher-level function where it would be inconvenient if the called function simply die()s: if there is a problem, we want to let the user know how to proceed, and the callee simply has no way of knowing what to say.
--- builtin/replace.c | 164 ++++++++++++++++++++++++++++++---------------- 1 file changed, 107 insertions(+), 57 deletions(-)
builtin/replace.c +107 -57
@@ -79,9 +79,9 @@ static int list_replace_refs(const char *pattern, const char *format)else if (!strcmp (format,"long" )) data.format =REPLACE_FORMAT_LONG ;else -die ("invalid replace format '%s'\n" -"valid formats are 'short', 'medium' and 'long'\n" , - format); +return error ("invalid replace format '%s'\n" +"valid formats are 'short', 'medium' and 'long'\n" , + format);for_each_replace_ref (show_reference, (void *)&data);
@@ -134,7 +134,7 @@ static int delete_replace_ref(const char *name, const char *ref,return 0 ; } -static void check_ref_valid (struct object_id *object, +static int check_ref_valid (struct object_id *object,struct object_id *prev,struct strbuf *ref,int force)
@@ -142,12 +142,13 @@ static void check_ref_valid(struct object_id *object,strbuf_reset (ref);strbuf_addf (ref,"%s%s" , git_replace_ref_base,oid_to_hex (object));if (check_refname_format (ref->buf,0 )) -die ("'%s' is not a valid ref name." , ref->buf); +return error ("'%s' is not a valid ref name." , ref->buf);if (read_ref (ref->buf, prev))oidclr (prev);else if (!force) -die ("replace ref '%s' already exists" , ref->buf); +return error ("replace ref '%s' already exists" , ref->buf); +return 0 ; } static int replace_object_oid(const char *object_ref,
@@ -161,28 +162,33 @@ static int replace_object_oid(const char *object_ref,struct strbuf ref =STRBUF_INIT ;struct ref_transaction *transaction;struct strbuf err =STRBUF_INIT ; +int res =0 ; obj_type =oid_object_info (object,NULL ); repl_type =oid_object_info (repl,NULL );if (!force && obj_type != repl_type) -die ("Objects must be of the same type.\n" -"'%s' points to a replaced object of type '%s'\n" -"while '%s' points to a replacement object of type '%s'." , - object_ref,type_name (obj_type), - replace_ref,type_name (repl_type)); - -check_ref_valid (object, &prev, &ref, force); +return error ("Objects must be of the same type.\n" +"'%s' points to a replaced object of type '%s'\n" +"while '%s' points to a replacement object of " +"type '%s'." , + object_ref,type_name (obj_type), + replace_ref,type_name (repl_type)); + +if (check_ref_valid (object, &prev, &ref, force)) { +strbuf_release (&ref); +return -1 ; + } transaction =ref_transaction_begin (&err);if (!transaction ||ref_transaction_update (transaction, ref.buf, repl, &prev,0 ,NULL , &err) ||ref_transaction_commit (transaction, &err)) -die ("%s" , err.buf); + res =error ("%s" , err.buf);ref_transaction_free (transaction);strbuf_release (&ref); -return 0 ; +return res; }static int replace_object (const char *object_ref,const char *replace_ref,int force)
@@ -190,9 +196,11 @@ static int replace_object(const char *object_ref, const char *replace_ref, int fstruct object_id object, repl;if (get_oid (object_ref, &object)) -die ("Failed to resolve '%s' as a valid ref." , object_ref); +return error ("Failed to resolve '%s' as a valid ref." , + object_ref);if (get_oid (replace_ref, &repl)) -die ("Failed to resolve '%s' as a valid ref." , replace_ref); +return error ("Failed to resolve '%s' as a valid ref." , + replace_ref);return replace_object_oid (object_ref, &object, replace_ref, &repl, force); }
@@ -202,7 +210,7 @@ static int replace_object(const char *object_ref, const char *replace_ref, int f* If "raw" is true, then the object's raw contents are printed according to * "type". Otherwise, we pretty-print the contents for human editing. */ -static void export_object (const struct object_id *oid,enum object_type type, +static int export_object (const struct object_id *oid,enum object_type type,int raw,const char *filename) {struct child_process cmd =CHILD_PROCESS_INIT ;
@@ -210,7 +218,7 @@ static void export_object(const struct object_id *oid, enum object_type type, fd =open (filename,O_WRONLY |O_CREAT |O_TRUNC ,0666 );if (fd <0 ) -die_errno ("unable to open %s for writing" , filename); +return error_errno ("unable to open %s for writing" , filename);argv_array_push (&cmd.args,"--no-replace-objects" );argv_array_push (&cmd.args,"cat-file" );
@@ -223,7 +231,8 @@ static void export_object(const struct object_id *oid, enum object_type type, cmd.out = fd;if (run_command (&cmd)) -die ("cat-file reported failure" ); +return error ("cat-file reported failure" ); +return 0 ; }/*
@@ -231,14 +240,14 @@ static void export_object(const struct object_id *oid, enum object_type type,* interpreting it as "type", and writing the result to the object database. * The sha1 of the written object is returned via sha1. */ -static void import_object (struct object_id *oid,enum object_type type, +static int import_object (struct object_id *oid,enum object_type type,int raw,const char *filename) {int fd; fd =open (filename,O_RDONLY );if (fd <0 ) -die_errno ("unable to open %s for reading" , filename); +return error_errno ("unable to open %s for reading" , filename);if (!raw && type ==OBJ_TREE ) {const char *argv[] = {"mktree" ,NULL };
@@ -250,27 +259,40 @@ static void import_object(struct object_id *oid, enum object_type type, cmd.in = fd; cmd.out =-1 ; -if (start_command (&cmd)) -die ("unable to spawn mktree" ); +if (start_command (&cmd)) { +close (fd); +return error ("unable to spawn mktree" ); + } -if (strbuf_read (&result, cmd.out,41 ) <0 ) -die_errno ("unable to read from mktree" ); +if (strbuf_read (&result, cmd.out,41 ) <0 ) { +error_errno ("unable to read from mktree" ); +close (fd); +close (cmd.out); +return -1 ; + }close (cmd.out); -if (finish_command (&cmd)) -die ("mktree reported failure" ); -if (get_oid_hex (result.buf, oid) <0 ) -die ("mktree did not return an object name" ); +if (finish_command (&cmd)) { +strbuf_release (&result); +return error ("mktree reported failure" ); + } +if (get_oid_hex (result.buf, oid) <0 ) { +strbuf_release (&result); +return error ("mktree did not return an object name" ); + }strbuf_release (&result); }else {struct stat st;int flags =HASH_FORMAT_CHECK |HASH_WRITE_OBJECT ; -if (fstat (fd, &st) <0 ) -die_errno ("unable to fstat %s" , filename); +if (fstat (fd, &st) <0 ) { +error_errno ("unable to fstat %s" , filename); +close (fd); +return -1 ; + }if (index_fd (oid, fd, &st, type,NULL , flags) <0 ) -die ("unable to write object to database" ); +return error ("unable to write object to database" );/* index_fd close()s fd for us */ }
@@ -278,30 +300,38 @@ static void import_object(struct object_id *oid, enum object_type type,* No need to close(fd) here; both run-command and index-fd * will have done it for us. */ +return 0 ; }static int edit_and_replace (const char *object_ref,int force,int raw) { -char *tmpfile =git_pathdup ("REPLACE_EDITOBJ" ); +char *tmpfile;enum object_type type;struct object_id old_oid, new_oid, prev;struct strbuf ref =STRBUF_INIT ;if (get_oid (object_ref, &old_oid) <0 ) -die ("Not a valid object name: '%s'" , object_ref); +return error ("Not a valid object name: '%s'" , object_ref); type =oid_object_info (&old_oid,NULL );if (type <0 ) -die ("unable to get object type for %s" ,oid_to_hex (&old_oid)); +return error ("unable to get object type for %s" , +oid_to_hex (&old_oid)); -check_ref_valid (&old_oid, &prev, &ref, force); +if (check_ref_valid (&old_oid, &prev, &ref, force)) { +strbuf_release (&ref); +return -1 ; + }strbuf_release (&ref); -export_object (&old_oid, type, raw, tmpfile); -if (launch_editor (tmpfile,NULL ,NULL ) <0 ) -die ("editing object file failed" ); -import_object (&new_oid, type, raw, tmpfile); - + tmpfile =git_pathdup ("REPLACE_EDITOBJ" ); +if (export_object (&old_oid, type, raw, tmpfile) || + (launch_editor (tmpfile,NULL ,NULL ) <0 && +error ("editing object file failed" )) || +import_object (&new_oid, type, raw, tmpfile)) { +free (tmpfile); +return -1 ; + }free (tmpfile);if (!oidcmp (&old_oid, &new_oid))
@@ -310,7 +340,7 @@ static int edit_and_replace(const char *object_ref, int force, int raw)return replace_object_oid (object_ref, &old_oid,"replacement" , &new_oid, force); } -static void replace_parents (struct strbuf *buf,int argc,const char **argv) +static int replace_parents (struct strbuf *buf,int argc,const char **argv) {struct strbuf new_parents =STRBUF_INIT ;const char *parent_start, *parent_end;
@@ -327,9 +357,15 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv)/* prepare new parents */ for (i =0 ; i < argc; i++) {struct object_id oid; -if (get_oid (argv[i], &oid) <0 ) -die (_ ("Not a valid object name: '%s'" ), argv[i]); -lookup_commit_or_die (&oid, argv[i]); +if (get_oid (argv[i], &oid) <0 ) { +strbuf_release (&new_parents); +return error (_ ("Not a valid object name: '%s'" ), + argv[i]); + } +if (!lookup_commit_reference (&oid)) { +strbuf_release (&new_parents); +return error (_ ("could not parse %s" ), argv[i]); + }strbuf_addf (&new_parents,"parent %s\n" ,oid_to_hex (&oid)); }
@@ -338,6 +374,7 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv) new_parents.buf, new_parents.len);strbuf_release (&new_parents); +return 0 ; }struct check_mergetag_data {
@@ -358,21 +395,23 @@ static int check_one_mergetag(struct commit *commit,hash_object_file (extra->value, extra->len,type_name (OBJ_TAG ), &tag_oid); tag =lookup_tag (&tag_oid);if (!tag) -die (_ ("bad mergetag in commit '%s'" ), ref); +return error (_ ("bad mergetag in commit '%s'" ), ref);if (parse_tag_buffer (tag, extra->value, extra->len)) -die (_ ("malformed mergetag in commit '%s'" ), ref); +return error (_ ("malformed mergetag in commit '%s'" ), ref);/* iterate over new parents */ for (i =1 ; i < mergetag_data->argc; i++) {struct object_id oid;if (get_oid (mergetag_data->argv[i], &oid) <0 ) -die (_ ("Not a valid object name: '%s'" ), mergetag_data->argv[i]); +return error (_ ("Not a valid object name: '%s'" ), + mergetag_data->argv[i]);if (!oidcmp (&tag->tagged->oid, &oid))return 0 ;/* found */ } -die (_ ("original commit '%s' contains mergetag '%s' that is discarded; " -"use --edit instead of --graft" ), ref,oid_to_hex (&tag_oid)); +return error (_ ("original commit '%s' contains mergetag '%s' that is " +"discarded; use --edit instead of --graft" ), ref, +oid_to_hex (&tag_oid)); }static int check_mergetags (struct commit *commit,int argc,const char **argv)
@@ -394,24 +433,35 @@ static int create_graft(int argc, const char **argv, int force)unsigned long size;if (get_oid (old_ref, &old_oid) <0 ) -die (_ ("Not a valid object name: '%s'" ), old_ref); - commit =lookup_commit_or_die (&old_oid, old_ref); +return error (_ ("Not a valid object name: '%s'" ), old_ref); + commit =lookup_commit_reference (&old_oid); +if (!commit) +return error (_ ("could not parse %s" ), old_ref); buffer =get_commit_buffer (commit, &size);strbuf_add (&buf, buffer, size);unuse_commit_buffer (commit, buffer); -replace_parents (&buf, argc -1 , &argv[1 ]); +if (replace_parents (&buf, argc -1 , &argv[1 ]) <0 ) { +strbuf_release (&buf); +return -1 ; + }if (remove_signature (&buf)) {warning (_ ("the original commit '%s' has a gpg signature." ), old_ref);warning (_ ("the signature will be removed in the replacement commit!" )); } -check_mergetags (commit, argc, argv); +if (check_mergetags (commit, argc, argv)) { +strbuf_release (&buf); +return -1 ; + } -if (write_object_file (buf.buf, buf.len, commit_type, &new_oid)) -die (_ ("could not write replacement commit for: '%s'" ), old_ref); +if (write_object_file (buf.buf, buf.len, commit_type, &new_oid)) { +strbuf_release (&buf); +return error (_ ("could not write replacement commit for: '%s'" ), + old_ref); + }strbuf_release (&buf);
Signature · 1 line
2.17.0.windows.1.33.gfcbb1fa0445
PATCH v5 5/11 replace: introduce --convert-graft-file
In reply to Johannes SchindelinThis option is intended to help with the transition away from the now-deprecated graft file.
--- Documentation/git-replace.txt | 11 ++++++--- builtin/replace.c | 45 ++++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 4 deletions(-)
Documentation/git-replace.txt +8 -3
@@ -11,6 +11,7 @@ SYNOPSIS 'git replace' [-f] <object> <replacement> 'git replace' [-f] --edit <object> 'git replace' [-f] --graft <commit> [<parent>...] +'git replace' [-f] --convert-graft-file 'git replace' -d <object>... 'git replace' [--format=<format>] [-l [<pattern>]]
@@ -87,9 +88,13 @@ OPTIONS content as <commit> except that its parents will be [<parent>...] instead of <commit>'s parents. A replacement ref is then created to replace <commit> with the newly created - commit. See contrib/convert-grafts-to-replace-refs.sh for an - example script based on this option that can convert grafts to - replace refs. + commit. Use `--convert-graft-file` to convert a + `$GIT_DIR/info/grafts` file and use replace refs instead. + +--convert-graft-file:: + Creates graft commits for all entries in `$GIT_DIR/info/grafts` + and deletes that file upon success. The purpose is to help users + with transitioning off of the now-deprecated graft file. -l <pattern>:: --list <pattern>::
builtin/replace.c +44 -1
81 lines not on this page · raw message
Signature · 1 line
2.17.0.windows.1.33.gfcbb1fa0445
PATCH v5 6/11 Add a test for `git replace --convert-graft-file`
In reply to Johannes SchindelinThe proof, as the saying goes, lies in the pudding. So here is a regression test that not only demonstrates what the option is supposed to accomplish, but also demonstrates that it does accomplish it.
--- builtin/replace.c | 1 - t/t6050-replace.sh | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-)
builtin/replace.c +0 -1
@@ -494,7 +494,6 @@ static int convert_graft_file(int force)fclose (fp);strbuf_release (&buf); -argv_array_clear (&args);if (!err.len)return unlink_or_warn (graft_file);
t/t6050-replace.sh +20 -0
@@ -444,4 +444,24 @@ test_expect_success GPG '--graft on a commit with a mergetag' ' git replace -d $HASH10 ' +test_expect_success '--convert-graft-file' ' + : add and convert graft file && + printf "%s\n%s %s\n\n# comment\n%s\n" \ + $(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \ + >.git/info/grafts && + git replace --convert-graft-file && + test_path_is_missing .git/info/grafts && + + : verify that the history is now "grafted" && + git rev-list HEAD >out && + test_line_count = 4 out && + + : create invalid graft file and verify that it is not deleted && + test_when_finished "rm -f .git/info/grafts" && + echo $EMPTY_BLOB $EMPTY_TREE >.git/info/grafts && + test_must_fail git replace --convert-graft-file 2>err && + test_i18ngrep "$EMPTY_BLOB $EMPTY_TREE" err && + test_i18ngrep "$EMPTY_BLOB $EMPTY_TREE" .git/info/grafts +' + test_done
Signature · 1 line
2.17.0.windows.1.33.gfcbb1fa0445
PATCH v5 7/11 Deprecate support for .git/info/grafts
In reply to Johannes SchindelinThe grafts feature was a convenient way to "stitch together" ancient history to the fresh start of linux.git.
Its implementation is, however, not up to Git's standards, as there are too many ways where it can lead to surprising and unwelcome behavior.
For example, when pushing from a repository with active grafts, it is possible to miss commits that have been "grafted out", resulting in a broken state on the other side.
Also, the grafts feature is limited to "rewriting" commits' list of parents, it cannot replace anything else.
The much younger feature implemented as git replace set out to remedy those limitations and dangerous bugs.
Seeing as git replace is pretty mature by now (since 4228e8bc98 (replace: add --graft option, 2014-07-19) it can perform the graft file's duties), it is time to deprecate support for the graft file, and to retire it eventually.
--- advice.c | 2 ++ advice.h | 1 + commit.c | 10 ++++++++++ t/t6001-rev-list-graft.sh | 9 +++++++++ 4 files changed, 22 insertions(+)
advice.c +2 -0
@@ -19,6 +19,7 @@ int advice_rm_hints = 1;int advice_add_embedded_repo =1 ;int advice_ignored_hook =1 ;int advice_waiting_for_editor =1 ; +int advice_graft_file_deprecated =1 ; static struct { const char *name;
@@ -42,6 +43,7 @@ static struct { { "addembeddedrepo", &advice_add_embedded_repo }, { "ignoredhook", &advice_ignored_hook }, { "waitingforeditor", &advice_waiting_for_editor }, + { "graftfiledeprecated", &advice_graft_file_deprecated },/* make this an alias for backward compatibility */ { "pushnonfastforward", &advice_push_update_rejected }
advice.h +1 -0
@@ -21,6 +21,7 @@ extern int advice_rm_hints;extern int advice_add_embedded_repo;extern int advice_ignored_hook;extern int advice_waiting_for_editor; +extern int advice_graft_file_deprecated;int git_default_advice_config (const char *var,const char *value); __attribute__((format (printf, 1, 2)))
commit.c +10 -0
@@ -12,6 +12,7 @@#include "prio-queue.h" #include "sha1-lookup.h" #include "wt-status.h" +#include "advice.h" static struct commit_extra_header *read_commit_extra_header_lines (const char *buf,size_t len,const char **);
@@ -176,6 +177,15 @@ static int read_graft_file(const char *graft_file)struct strbuf buf =STRBUF_INIT ;if (!fp)return -1 ; +if (advice_graft_file_deprecated) +advise (_ ("Support for <GIT_DIR>/info/grafts is deprecated\n" +"and will be removed in a future Git version.\n" +"\n" +"Please use \"git replace --convert-graft-file\"\n" +"to convert the grafts into replace refs.\n" +"\n" +"Turn this message off by running\n" +"\"git config advice.graftFileDeprecated false\"" ));while (!strbuf_getwholeline (&buf, fp,'\n' )) {/* The format is just "Commit Parent1 Parent2 ...\n" */ struct commit_graft *graft =read_graft_line (&buf);
t/t6001-rev-list-graft.sh +9 -0
@@ -110,4 +110,13 @@ do " done + +test_expect_success 'show advice that grafts are deprecated' ' + git show HEAD 2>err && + test_i18ngrep "git replace" err && + test_config advice.graftFileDeprecated false && + git show HEAD 2>err && + test_i18ngrep ! "git replace" err +' + test_done
Signature · 1 line
2.17.0.windows.1.33.gfcbb1fa0445
PATCH v5 8/11 filter-branch: stop suggesting to use grafts
In reply to Johannes SchindelinThe graft file is deprecated now, so let's use replace refs in the example in filter-branch's man page instead.
--- Documentation/git-filter-branch.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
Documentation/git-filter-branch.txt +1 -1
@@ -288,7 +288,7 @@ git filter-branch --parent-filter \ or even simpler: ----------------------------------------------- -echo "$commit-id $graft-id" >> .git/info/grafts +git replace --graft $commit-id $graft-id git filter-branch $graft-id..HEAD -----------------------------------------------
Signature · 1 line
2.17.0.windows.1.33.gfcbb1fa0445
PATCH v5 9/11 technical/shallow: stop referring to grafts
In reply to Johannes SchindelinNow that grafts are deprecated, we should start to assume that readers have no idea what grafts are. So it makes more sense to make the description of the "shallow" feature stand on its own.
--- Documentation/technical/shallow.txt | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-)
Documentation/technical/shallow.txt +4 -9
@@ -8,15 +8,10 @@ repo, and therefore grafts are introduced pretending that these commits have no parents. ********************************************************* -The basic idea is to write the SHA-1s of shallow commits into -$GIT_DIR/shallow, and handle its contents like the contents -of $GIT_DIR/info/grafts (with the difference that shallow -cannot contain parent information). - -This information is stored in a new file instead of grafts, or -even the config, since the user should not touch that file -at all (even throughout development of the shallow clone, it -was never manually edited!). +$GIT_DIR/shallow lists commit object names and tells Git to +pretend as if they are root commits (e.g. "git log" traversal +stops after showing them; "git fsck" does not complain saying +the commits listed on their "parent" lines do not exist). Each line contains exactly one SHA-1. When read, a commit_graft will be constructed, which has nr_parent < 0 to make it easier
Signature · 1 line
2.17.0.windows.1.33.gfcbb1fa0445
Re: [PATCH v5 09/11] technical/shallow: stop referring to grafts
Quoted 25 lines · Johannes Schindelin
Now that grafts are deprecated, we should start to assume that readers have no idea what grafts are. So it makes more sense to make the description of the "shallow" feature stand on its own.
Suggested-by Eric Sunshine <[email protected]>Helped-by Junio Hamano <[email protected]>Signed-off-by Johannes Schindelin <[email protected]>--- Documentation/technical/shallow.txt | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-)Documentation/technical/shallow.txt +0
-6@@ -8,15 +8,10 @@ repo, and therefore grafts are introduced pretending that these commits have no parents. ********************************************************* -The basic idea is to write the SHA-1s of shallow commits into -$GIT_DIR/shallow, and handle its contents like the contents -of $GIT_DIR/info/grafts (with the difference that shallow -cannot contain parent information). - -This information is stored in a new file instead of grafts, or
Documentation/technical/shallow.txt repo, and therefore grafts are introduced pretending that · line 17
Quoted 10 lines
Documentation/technical/shallow.txt +4
-3-even the config, since the user should not touch that file -at all (even throughout development of the shallow clone, it -was never manually edited!). +$GIT_DIR/shallow lists commit object names and tells Git to +pretend as if they are root commits (e.g. "git log" traversal +stops after showing them; "git fsck" does not complain saying +the commits listed on their "parent" lines do not exist). Each line contains exactly one SHA-1. When read, a commit_graft will be constructed, which has nr_parent < 0 to make it easier
Is the removed information (repeated below) important or not?
the user should not touch that file
at all (even throughout development of the shallow clone, it was never manually edited!).
Signature · 1 line
Jakub Narębski
Re: [PATCH v5 09/11] technical/shallow: stop referring to grafts
Hi Kuba,
Quoted 24 lines · Jakub Narębski
Quoted 22 lines · Johannes Schindelin
Documentation/technical/shallow.txt +4
-9@@ -8,15 +8,10 @@ repo, and therefore grafts are introduced pretending that these commits have no parents. ********************************************************* -The basic idea is to write the SHA-1s of shallow commits into -$GIT_DIR/shallow, and handle its contents like the contents -of $GIT_DIR/info/grafts (with the difference that shallow -cannot contain parent information). - -This information is stored in a new file instead of grafts, or -even the config, since the user should not touch that file -at all (even throughout development of the shallow clone, it -was never manually edited!). +$GIT_DIR/shallow lists commit object names and tells Git to +pretend as if they are root commits (e.g. "git log" traversal +stops after showing them; "git fsck" does not complain saying +the commits listed on their "parent" lines do not exist).
Quoted 8 lines
Documentation/technical/shallow.txt repo, and therefore grafts are introduced pretending that · line 17
Quoted 2 lines
Documentation/technical/shallow.txt +0
-0Each line contains exactly one SHA-1. When read, a commit_graft will be constructed, which has nr_parent < 0 to make it easierIs the removed information (repeated below) important or not?
the user should not touch that file
at all (even throughout development of the shallow clone, it was never manually edited!).
Back in the days, it might have been necessary to tell people not to meddle with Git's internals. Nowadays I don't think that'd be necessary anymore, hence I removed it.
Ciao,
Dscho
PATCH v5 10/11 technical/shallow: describe why shallow cannot use replace refs
In reply to Johannes SchindelinIt is tempting to do away with commit_graft altogether (in the long haul), now that grafts are deprecated.
However, the shallow feature needs a couple of things that the replace refs cannot fulfill. Let's point that out in the documentation.
--- Documentation/technical/shallow.txt | 7 +++++++ 1 file changed, 7 insertions(+)
Documentation/technical/shallow.txt +7 -0
@@ -17,6 +17,13 @@ Each line contains exactly one SHA-1. When read, a commit_graft will be constructed, which has nr_parent < 0 to make it easier to discern from user provided grafts. +Note that the shallow feature could not be changed easily to +use replace refs: a commit containing a `mergetag` is not allowed +to be replaced, not even by a root commit. Such a commit can be +made shallow, though. Also, having a `shallow` file explicitly +listing all the commits made shallow makes it a *lot* easier to +do shallow-specific things such as to deepen the history. + Since fsck-objects relies on the library to read the objects, it honours shallow commits automatically.
Signature · 1 line
2.17.0.windows.1.33.gfcbb1fa0445
PATCH v5 11/11 Remove obsolete script to convert grafts to replace refs
In reply to Johannes SchindelinThe functionality is now implemented as git replace --convert-graft-file.
--- contrib/convert-grafts-to-replace-refs.sh | 28 ----------------------- 1 file changed, 28 deletions(-) delete mode 100755 contrib/convert-grafts-to-replace-refs.sh
contrib/convert-grafts-to-replace-refs.sh deleted +0 -28
@@ -1,28 +0,0 @@ -#!/bin/sh - -# You should execute this script in the repository where you -# want to convert grafts to replace refs. - -GRAFTS_FILE="${GIT_DIR:-.git}/info/grafts" - -. $(git --exec-path)/git-sh-setup - -test -f "$GRAFTS_FILE" || die "Could not find graft file: '$GRAFTS_FILE'" - -grep '^[^# ]' "$GRAFTS_FILE" | -while read definition -do - if test -n "$definition" - then - echo "Converting: $definition" - git replace --graft $definition || - die "Conversion failed for: $definition" - fi -done - -mv "$GRAFTS_FILE" "$GRAFTS_FILE.bak" || - die "Could not rename '$GRAFTS_FILE' to '$GRAFTS_FILE.bak'" - -echo "Success!" -echo "All the grafts in '$GRAFTS_FILE' have been converted to replace refs!" -echo "The grafts file '$GRAFTS_FILE' has been renamed: '$GRAFTS_FILE.bak'"
Signature · 1 line
2.17.0.windows.1.33.gfcbb1fa0445
Re: [PATCH v5 00/11] Deprecate .git/info/grafts
In reply to Johannes SchindelinQuoted 6 lines · Johannes Schindelin
- if (
export_object(&old_oid, type, raw, tmpfile))
- return -1;
- if (launch_editor(tmpfile, NULL, NULL)< 0)
- returnerror("editing object file failed");
- if (import_object(&new_oid, type, raw, tmpfile))
+ tmpfile =git_pathdup("REPLACE_EDITOBJ");
Quoted 8 lines
+ if (
export_object(&old_oid, type, raw, tmpfile)||
+ (launch_editor(tmpfile, NULL, NULL)< 0 &&
+error("editing object file failed")) ||
+import_object(&new_oid, type, raw, tmpfile)) {
+ free(tmpfile);
return -1;
-
+ }
I know the above is to avoid leaking tmpfile, but a single if () condition that makes multiple calls to functions primarily for their side effects is too ugly to live. Perhaps something like
if (export_object(...))
goto clean_fail;
if (launch_editor(...)) {
error("editing object file failed");
goto clean_fail;
}
if (import_object(...)) {
clean_fail:
free(tmpfile);
return -1;
}would keep the ease-of-reading of the original while plugging the leak. It would even be cleaner to move the body of clean_fail:
completely out of line, instead of having it in the last of three steps like the above.
Other than that, looked cleanly done. Thanks for a pleasant read.
Will queue.
Re: [PATCH v5 00/11] Deprecate .git/info/grafts
Hi Junio,
Quoted 17 lines · Junio C Hamano
Quoted 6 lines · Johannes Schindelin
- if (
export_object(&old_oid, type, raw, tmpfile))
- return -1;
- if (launch_editor(tmpfile, NULL, NULL)< 0)
- returnerror("editing object file failed");
- if (import_object(&new_oid, type, raw, tmpfile))
+ tmpfile =git_pathdup("REPLACE_EDITOBJ");Quoted 8 lines
+ if (
export_object(&old_oid, type, raw, tmpfile)||
+ (launch_editor(tmpfile, NULL, NULL)< 0 &&
+error("editing object file failed")) ||
+import_object(&new_oid, type, raw, tmpfile)) {
+ free(tmpfile);
return -1;
-
+ }
Quoted 3 lines
I know the above is to avoid leaking tmpfile, but a single if () condition that makes multiple calls to functions primarily for their side effects is too ugly to live.
I changed it back to individual conditional blocks, with every single one of them having their own free(tmpfile). That is at least clearer.
Ciao,
Dscho
v6 cover Deprecate .git/info/grafts
In reply to Johannes SchindelinIt is fragile, as there is no way for the revision machinery to say "but now I want to traverse the graph ignoring the graft file" e.g. when pushing commits to a remote repository (which, as a consequence, can miss commits).
And we already have a better solution with git replace --graft <comit> [<parent>...].
Changes since v5:
- Disentangled the lumped-together conditional blocks in edit_and_replace() again.
- Moved fixup (a superfluous argv_array_clear()) from the patch that adds a test for --convert-graft-file back to the patch that actually introduces that option.
Johannes Schindelin (11):
argv_array: offer to split a string by whitespace
commit: Let the callback of for_each_mergetag return on error
replace: avoid using die() to indicate a bug
replace: "libify" create_graft() and callees
replace: introduce --convert-graft-file
Add a test for git replace --convert-graft-file Deprecate support for .git/info/grafts
filter-branch: stop suggesting to use grafts technical/shallow: stop referring to grafts technical/shallow: describe why shallow cannot use replace refs Remove obsolete script to convert grafts to replace refs
Documentation/git-filter-branch.txt | 2 +- Documentation/git-replace.txt | 11 +- Documentation/technical/shallow.txt | 20 +- advice.c | 2 + advice.h | 1 + argv-array.c | 20 ++ argv-array.h | 2 + builtin/replace.c | 223 ++++++++++++++++------ commit.c | 18 +- commit.h | 4 +- contrib/convert-grafts-to-replace-refs.sh | 28 --- log-tree.c | 13 +- t/t6001-rev-list-graft.sh | 9 + t/t6050-replace.sh | 20 ++ 14 files changed, 258 insertions(+), 115 deletions(-) delete mode 100755 contrib/convert-grafts-to-replace-refs.sh
base-commit: 1f1cddd558b54bb0ce19c8ace353fd07b758510d
Published-As: https://github.com/dscho/git/releases/tag/deprecate-grafts-v6
Fetch-It-Via: git fetch https://github.com/dscho/git deprecate-grafts-v6
Interdiff vs v5:
diff --git a/builtin/replace.c b/builtin/replace.c
index acd30e3d824..35394ec1874 100644
--- a/builtin/replace.c
+++ b/builtin/replace.c
@@ -326,10 +326,15 @@ static int edit_and_replace(const char *object_ref, int force, int raw)
strbuf_release(&ref);
tmpfile = git_pathdup("REPLACE_EDITOBJ");
- if (export_object(&old_oid, type, raw, tmpfile) ||
- (launch_editor(tmpfile, NULL, NULL) < 0 &&
- error("editing object file failed")) ||
- import_object(&new_oid, type, raw, tmpfile)) {
+ if (export_object(&old_oid, type, raw, tmpfile)) {
+ free(tmpfile);
+ return -1;
+ }
+ if (launch_editor(tmpfile, NULL, NULL) < 0) {
+ free(tmpfile);
+ return error("editing object file failed");
+ }
+ if (import_object(&new_oid, type, raw, tmpfile)) {
free(tmpfile);
return -1;
}
Signature · 1 line
2.17.0.windows.1.33.gfcbb1fa0445
PATCH v6 1/11 argv_array: offer to split a string by whitespace
This is a simple function that will interpret a string as a whitespace delimited list of values, and add those values into the array.
Note: this function does not (yet) offer to split by arbitrary delimiters, or keep empty values in case of runs of whitespace, or de-quote Unix shell style. All fo this functionality can be added later, when and if needed.
--- argv-array.c | 20 ++++++++++++++++++++ argv-array.h | 2 ++ 2 files changed, 22 insertions(+)
argv-array.c +20 -0
@@ -64,6 +64,26 @@ void argv_array_pop(struct argv_array *array) array->argc--; } +void argv_array_split (struct argv_array *array,const char *to_split) +{ +while (isspace (*to_split)) + to_split++; +for (;;) { +const char *p = to_split; + +if (!*p) +break ; + +while (*p && !isspace (*p)) + p++; +argv_array_push_nodup (array,xstrndup (to_split, p - to_split)); + +while (isspace (*p)) + p++; + to_split = p; + } +} +void argv_array_clear (struct argv_array *array) {if (array->argv != empty_argv) {
argv-array.h +2 -0
@@ -19,6 +19,8 @@ LAST_ARG_MUST_BE_NULLvoid argv_array_pushl (struct argv_array *, ...);void argv_array_pushv (struct argv_array *,const char **);void argv_array_pop (struct argv_array *); +/* Splits by whitespace; does not handle quoted arguments! */ +void argv_array_split (struct argv_array *,const char *);void argv_array_clear (struct argv_array *);const char **argv_array_detach (struct argv_array *);
Signature · 1 line
2.17.0.windows.1.33.gfcbb1fa0445
PATCH v6 2/11 commit: Let the callback of for_each_mergetag return on error
In reply to Johannes SchindelinThis is yet another patch to be filed under the keyword "libification".
There is one subtle change in behavior here, where a git log that has been asked to show the mergetags would now stop reporting the mergetags upon the first failure, whereas previously, it would have continued to the next mergetag, if any.
In practice, that change should not matter, as it is 1) uncommon to perform octopus merges using multiple tags as merge heads, and 2) when the user asks to be shown those tags, they really should be there.
--- builtin/replace.c | 8 ++++---- commit.c | 8 +++++--- commit.h | 4 ++-- log-tree.c | 13 +++++++------ 4 files changed, 18 insertions(+), 15 deletions(-)
builtin/replace.c +4 -4
@@ -345,7 +345,7 @@ struct check_mergetag_data {const char **argv; }; -static void check_one_mergetag (struct commit *commit, +static int check_one_mergetag (struct commit *commit,struct commit_extra_header *extra,void *data) {
@@ -368,20 +368,20 @@ static void check_one_mergetag(struct commit *commit,if (get_oid (mergetag_data->argv[i], &oid) <0 )die (_ ("Not a valid object name: '%s'" ), mergetag_data->argv[i]);if (!oidcmp (&tag->tagged->oid, &oid)) -return ;/* found */ +return 0 ;/* found */ }die (_ ("original commit '%s' contains mergetag '%s' that is discarded; " "use --edit instead of --graft" ), ref,oid_to_hex (&tag_oid)); } -static void check_mergetags (struct commit *commit,int argc,const char **argv) +static int check_mergetags (struct commit *commit,int argc,const char **argv) {struct check_mergetag_data mergetag_data; mergetag_data.argc = argc; mergetag_data.argv = argv; -for_each_mergetag (check_one_mergetag, commit, &mergetag_data); +return for_each_mergetag (check_one_mergetag, commit, &mergetag_data); }static int create_graft (int argc,const char **argv,int force)
commit.c +5 -3
@@ -1288,17 +1288,19 @@ struct commit_extra_header *read_commit_extra_headers(struct commit *commit,return extra; } -void for_each_mergetag (each_mergetag_fn fn,struct commit *commit,void *data) +int for_each_mergetag (each_mergetag_fn fn,struct commit *commit,void *data) {struct commit_extra_header *extra, *to_free; +int res =0 ; to_free =read_commit_extra_headers (commit,NULL ); -for (extra = to_free; extra; extra = extra->next) { +for (extra = to_free; !res && extra; extra = extra->next) {if (strcmp (extra->key,"mergetag" ))continue ;/* not a merge tag */ -fn (commit, extra, data); + res =fn (commit, extra, data); }free_commit_extra_headers (to_free); +return res; }static inline int standard_header_field (const char *field,size_t len)
commit.h +2 -2
@@ -291,10 +291,10 @@ extern const char *find_commit_header(const char *msg, const char *key,/* Find the end of the log message, the right place for a new trailer. */ extern int ignore_non_trailer (const char *buf,size_t len); -typedef void (*each_mergetag_fn )(struct commit *commit,struct commit_extra_header *extra, +typedef int (*each_mergetag_fn )(struct commit *commit,struct commit_extra_header *extra,void *cb_data); -extern void for_each_mergetag (each_mergetag_fn fn,struct commit *commit,void *data); +extern int for_each_mergetag (each_mergetag_fn fn,struct commit *commit,void *data);struct merge_remote_desc {struct object *obj;/* the named object, could be a tag */
log-tree.c +7 -6
@@ -488,9 +488,9 @@ static int is_common_merge(const struct commit *commit) && !commit->parents->next->next); } -static void show_one_mergetag (struct commit *commit, -struct commit_extra_header *extra, -void *data) +static int show_one_mergetag (struct commit *commit, +struct commit_extra_header *extra, +void *data) {struct rev_info *opt = (struct rev_info *)data;struct object_id oid;
@@ -502,7 +502,7 @@ static void show_one_mergetag(struct commit *commit,hash_object_file (extra->value, extra->len,type_name (OBJ_TAG ), &oid); tag =lookup_tag (&oid);if (!tag) -return ;/* error message already given */ +return -1 ;/* error message already given */ strbuf_init (&verify_message,256 );if (parse_tag_buffer (tag, extra->value, extra->len))
@@ -536,11 +536,12 @@ static void show_one_mergetag(struct commit *commit,show_sig_lines (opt, status, verify_message.buf);strbuf_release (&verify_message); +return 0 ; } -static void show_mergetag (struct rev_info *opt,struct commit *commit) +static int show_mergetag (struct rev_info *opt,struct commit *commit) { -for_each_mergetag (show_one_mergetag, commit, opt); +return for_each_mergetag (show_one_mergetag, commit, opt); }void show_log (struct rev_info *opt)
Signature · 1 line
2.17.0.windows.1.33.gfcbb1fa0445
PATCH v6 3/11 replace: avoid using die() to indicate a bug
In reply to Johannes SchindelinWe have the BUG() macro for that purpose.
--- builtin/replace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
builtin/replace.c +1 -1
@@ -501,6 +501,6 @@ int cmd_replace(int argc, const char **argv, const char *prefix)return list_replace_refs (argv[0 ], format);default : -die ("BUG: invalid cmdmode %d" , (int )cmdmode); +BUG ("invalid cmdmode %d" , (int )cmdmode); } }
Signature · 1 line
2.17.0.windows.1.33.gfcbb1fa0445
PATCH v6 4/11 replace: "libify" create_graft() and callees
In reply to Johannes SchindelinFile this away as yet another patch in the "libification" category.
As with all useful functions, in the next commit we want to use create_graft() from a higher-level function where it would be inconvenient if the called function simply die()s: if there is a problem, we want to let the user know how to proceed, and the callee simply has no way of knowing what to say.
--- builtin/replace.c | 169 ++++++++++++++++++++++++++++++---------------- 1 file changed, 112 insertions(+), 57 deletions(-)
builtin/replace.c +112 -57
@@ -79,9 +79,9 @@ static int list_replace_refs(const char *pattern, const char *format)else if (!strcmp (format,"long" )) data.format =REPLACE_FORMAT_LONG ;else -die ("invalid replace format '%s'\n" -"valid formats are 'short', 'medium' and 'long'\n" , - format); +return error ("invalid replace format '%s'\n" +"valid formats are 'short', 'medium' and 'long'\n" , + format);for_each_replace_ref (show_reference, (void *)&data);
@@ -134,7 +134,7 @@ static int delete_replace_ref(const char *name, const char *ref,return 0 ; } -static void check_ref_valid (struct object_id *object, +static int check_ref_valid (struct object_id *object,struct object_id *prev,struct strbuf *ref,int force)
@@ -142,12 +142,13 @@ static void check_ref_valid(struct object_id *object,strbuf_reset (ref);strbuf_addf (ref,"%s%s" , git_replace_ref_base,oid_to_hex (object));if (check_refname_format (ref->buf,0 )) -die ("'%s' is not a valid ref name." , ref->buf); +return error ("'%s' is not a valid ref name." , ref->buf);if (read_ref (ref->buf, prev))oidclr (prev);else if (!force) -die ("replace ref '%s' already exists" , ref->buf); +return error ("replace ref '%s' already exists" , ref->buf); +return 0 ; } static int replace_object_oid(const char *object_ref,
@@ -161,28 +162,33 @@ static int replace_object_oid(const char *object_ref,struct strbuf ref =STRBUF_INIT ;struct ref_transaction *transaction;struct strbuf err =STRBUF_INIT ; +int res =0 ; obj_type =oid_object_info (object,NULL ); repl_type =oid_object_info (repl,NULL );if (!force && obj_type != repl_type) -die ("Objects must be of the same type.\n" -"'%s' points to a replaced object of type '%s'\n" -"while '%s' points to a replacement object of type '%s'." , - object_ref,type_name (obj_type), - replace_ref,type_name (repl_type)); - -check_ref_valid (object, &prev, &ref, force); +return error ("Objects must be of the same type.\n" +"'%s' points to a replaced object of type '%s'\n" +"while '%s' points to a replacement object of " +"type '%s'." , + object_ref,type_name (obj_type), + replace_ref,type_name (repl_type)); + +if (check_ref_valid (object, &prev, &ref, force)) { +strbuf_release (&ref); +return -1 ; + } transaction =ref_transaction_begin (&err);if (!transaction ||ref_transaction_update (transaction, ref.buf, repl, &prev,0 ,NULL , &err) ||ref_transaction_commit (transaction, &err)) -die ("%s" , err.buf); + res =error ("%s" , err.buf);ref_transaction_free (transaction);strbuf_release (&ref); -return 0 ; +return res; }static int replace_object (const char *object_ref,const char *replace_ref,int force)
@@ -190,9 +196,11 @@ static int replace_object(const char *object_ref, const char *replace_ref, int fstruct object_id object, repl;if (get_oid (object_ref, &object)) -die ("Failed to resolve '%s' as a valid ref." , object_ref); +return error ("Failed to resolve '%s' as a valid ref." , + object_ref);if (get_oid (replace_ref, &repl)) -die ("Failed to resolve '%s' as a valid ref." , replace_ref); +return error ("Failed to resolve '%s' as a valid ref." , + replace_ref);return replace_object_oid (object_ref, &object, replace_ref, &repl, force); }
@@ -202,7 +210,7 @@ static int replace_object(const char *object_ref, const char *replace_ref, int f* If "raw" is true, then the object's raw contents are printed according to * "type". Otherwise, we pretty-print the contents for human editing. */ -static void export_object (const struct object_id *oid,enum object_type type, +static int export_object (const struct object_id *oid,enum object_type type,int raw,const char *filename) {struct child_process cmd =CHILD_PROCESS_INIT ;
@@ -210,7 +218,7 @@ static void export_object(const struct object_id *oid, enum object_type type, fd =open (filename,O_WRONLY |O_CREAT |O_TRUNC ,0666 );if (fd <0 ) -die_errno ("unable to open %s for writing" , filename); +return error_errno ("unable to open %s for writing" , filename);argv_array_push (&cmd.args,"--no-replace-objects" );argv_array_push (&cmd.args,"cat-file" );
@@ -223,7 +231,8 @@ static void export_object(const struct object_id *oid, enum object_type type, cmd.out = fd;if (run_command (&cmd)) -die ("cat-file reported failure" ); +return error ("cat-file reported failure" ); +return 0 ; }/*
@@ -231,14 +240,14 @@ static void export_object(const struct object_id *oid, enum object_type type,* interpreting it as "type", and writing the result to the object database. * The sha1 of the written object is returned via sha1. */ -static void import_object (struct object_id *oid,enum object_type type, +static int import_object (struct object_id *oid,enum object_type type,int raw,const char *filename) {int fd; fd =open (filename,O_RDONLY );if (fd <0 ) -die_errno ("unable to open %s for reading" , filename); +return error_errno ("unable to open %s for reading" , filename);if (!raw && type ==OBJ_TREE ) {const char *argv[] = {"mktree" ,NULL };
@@ -250,27 +259,40 @@ static void import_object(struct object_id *oid, enum object_type type, cmd.in = fd; cmd.out =-1 ; -if (start_command (&cmd)) -die ("unable to spawn mktree" ); +if (start_command (&cmd)) { +close (fd); +return error ("unable to spawn mktree" ); + } -if (strbuf_read (&result, cmd.out,41 ) <0 ) -die_errno ("unable to read from mktree" ); +if (strbuf_read (&result, cmd.out,41 ) <0 ) { +error_errno ("unable to read from mktree" ); +close (fd); +close (cmd.out); +return -1 ; + }close (cmd.out); -if (finish_command (&cmd)) -die ("mktree reported failure" ); -if (get_oid_hex (result.buf, oid) <0 ) -die ("mktree did not return an object name" ); +if (finish_command (&cmd)) { +strbuf_release (&result); +return error ("mktree reported failure" ); + } +if (get_oid_hex (result.buf, oid) <0 ) { +strbuf_release (&result); +return error ("mktree did not return an object name" ); + }strbuf_release (&result); }else {struct stat st;int flags =HASH_FORMAT_CHECK |HASH_WRITE_OBJECT ; -if (fstat (fd, &st) <0 ) -die_errno ("unable to fstat %s" , filename); +if (fstat (fd, &st) <0 ) { +error_errno ("unable to fstat %s" , filename); +close (fd); +return -1 ; + }if (index_fd (oid, fd, &st, type,NULL , flags) <0 ) -die ("unable to write object to database" ); +return error ("unable to write object to database" );/* index_fd close()s fd for us */ }
@@ -278,30 +300,43 @@ static void import_object(struct object_id *oid, enum object_type type,* No need to close(fd) here; both run-command and index-fd * will have done it for us. */ +return 0 ; }static int edit_and_replace (const char *object_ref,int force,int raw) { -char *tmpfile =git_pathdup ("REPLACE_EDITOBJ" ); +char *tmpfile;enum object_type type;struct object_id old_oid, new_oid, prev;struct strbuf ref =STRBUF_INIT ;if (get_oid (object_ref, &old_oid) <0 ) -die ("Not a valid object name: '%s'" , object_ref); +return error ("Not a valid object name: '%s'" , object_ref); type =oid_object_info (&old_oid,NULL );if (type <0 ) -die ("unable to get object type for %s" ,oid_to_hex (&old_oid)); +return error ("unable to get object type for %s" , +oid_to_hex (&old_oid)); -check_ref_valid (&old_oid, &prev, &ref, force); +if (check_ref_valid (&old_oid, &prev, &ref, force)) { +strbuf_release (&ref); +return -1 ; + }strbuf_release (&ref); -export_object (&old_oid, type, raw, tmpfile); -if (launch_editor (tmpfile,NULL ,NULL ) <0 ) -die ("editing object file failed" ); -import_object (&new_oid, type, raw, tmpfile); - + tmpfile =git_pathdup ("REPLACE_EDITOBJ" ); +if (export_object (&old_oid, type, raw, tmpfile)) { +free (tmpfile); +return -1 ; + } +if (launch_editor (tmpfile,NULL ,NULL ) <0 ) { +free (tmpfile); +return error ("editing object file failed" ); + } +if (import_object (&new_oid, type, raw, tmpfile)) { +free (tmpfile); +return -1 ; + }free (tmpfile);if (!oidcmp (&old_oid, &new_oid))
@@ -310,7 +345,7 @@ static int edit_and_replace(const char *object_ref, int force, int raw)return replace_object_oid (object_ref, &old_oid,"replacement" , &new_oid, force); } -static void replace_parents (struct strbuf *buf,int argc,const char **argv) +static int replace_parents (struct strbuf *buf,int argc,const char **argv) {struct strbuf new_parents =STRBUF_INIT ;const char *parent_start, *parent_end;
@@ -327,9 +362,15 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv)/* prepare new parents */ for (i =0 ; i < argc; i++) {struct object_id oid; -if (get_oid (argv[i], &oid) <0 ) -die (_ ("Not a valid object name: '%s'" ), argv[i]); -lookup_commit_or_die (&oid, argv[i]); +if (get_oid (argv[i], &oid) <0 ) { +strbuf_release (&new_parents); +return error (_ ("Not a valid object name: '%s'" ), + argv[i]); + } +if (!lookup_commit_reference (&oid)) { +strbuf_release (&new_parents); +return error (_ ("could not parse %s" ), argv[i]); + }strbuf_addf (&new_parents,"parent %s\n" ,oid_to_hex (&oid)); }
@@ -338,6 +379,7 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv) new_parents.buf, new_parents.len);strbuf_release (&new_parents); +return 0 ; }struct check_mergetag_data {
@@ -358,21 +400,23 @@ static int check_one_mergetag(struct commit *commit,hash_object_file (extra->value, extra->len,type_name (OBJ_TAG ), &tag_oid); tag =lookup_tag (&tag_oid);if (!tag) -die (_ ("bad mergetag in commit '%s'" ), ref); +return error (_ ("bad mergetag in commit '%s'" ), ref);if (parse_tag_buffer (tag, extra->value, extra->len)) -die (_ ("malformed mergetag in commit '%s'" ), ref); +return error (_ ("malformed mergetag in commit '%s'" ), ref);/* iterate over new parents */ for (i =1 ; i < mergetag_data->argc; i++) {struct object_id oid;if (get_oid (mergetag_data->argv[i], &oid) <0 ) -die (_ ("Not a valid object name: '%s'" ), mergetag_data->argv[i]); +return error (_ ("Not a valid object name: '%s'" ), + mergetag_data->argv[i]);if (!oidcmp (&tag->tagged->oid, &oid))return 0 ;/* found */ } -die (_ ("original commit '%s' contains mergetag '%s' that is discarded; " -"use --edit instead of --graft" ), ref,oid_to_hex (&tag_oid)); +return error (_ ("original commit '%s' contains mergetag '%s' that is " +"discarded; use --edit instead of --graft" ), ref, +oid_to_hex (&tag_oid)); }static int check_mergetags (struct commit *commit,int argc,const char **argv)
@@ -394,24 +438,35 @@ static int create_graft(int argc, const char **argv, int force)unsigned long size;if (get_oid (old_ref, &old_oid) <0 ) -die (_ ("Not a valid object name: '%s'" ), old_ref); - commit =lookup_commit_or_die (&old_oid, old_ref); +return error (_ ("Not a valid object name: '%s'" ), old_ref); + commit =lookup_commit_reference (&old_oid); +if (!commit) +return error (_ ("could not parse %s" ), old_ref); buffer =get_commit_buffer (commit, &size);strbuf_add (&buf, buffer, size);unuse_commit_buffer (commit, buffer); -replace_parents (&buf, argc -1 , &argv[1 ]); +if (replace_parents (&buf, argc -1 , &argv[1 ]) <0 ) { +strbuf_release (&buf); +return -1 ; + }if (remove_signature (&buf)) {warning (_ ("the original commit '%s' has a gpg signature." ), old_ref);warning (_ ("the signature will be removed in the replacement commit!" )); } -check_mergetags (commit, argc, argv); +if (check_mergetags (commit, argc, argv)) { +strbuf_release (&buf); +return -1 ; + } -if (write_object_file (buf.buf, buf.len, commit_type, &new_oid)) -die (_ ("could not write replacement commit for: '%s'" ), old_ref); +if (write_object_file (buf.buf, buf.len, commit_type, &new_oid)) { +strbuf_release (&buf); +return error (_ ("could not write replacement commit for: '%s'" ), + old_ref); + }strbuf_release (&buf);
Signature · 1 line
2.17.0.windows.1.33.gfcbb1fa0445
PATCH v6 5/11 replace: introduce --convert-graft-file
In reply to Johannes SchindelinThis option is intended to help with the transition away from the now-deprecated graft file.
--- Documentation/git-replace.txt | 11 ++++++--- builtin/replace.c | 44 ++++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 4 deletions(-)
Documentation/git-replace.txt +8 -3
@@ -11,6 +11,7 @@ SYNOPSIS 'git replace' [-f] <object> <replacement> 'git replace' [-f] --edit <object> 'git replace' [-f] --graft <commit> [<parent>...] +'git replace' [-f] --convert-graft-file 'git replace' -d <object>... 'git replace' [--format=<format>] [-l [<pattern>]]
@@ -87,9 +88,13 @@ OPTIONS content as <commit> except that its parents will be [<parent>...] instead of <commit>'s parents. A replacement ref is then created to replace <commit> with the newly created - commit. See contrib/convert-grafts-to-replace-refs.sh for an - example script based on this option that can convert grafts to - replace refs. + commit. Use `--convert-graft-file` to convert a + `$GIT_DIR/info/grafts` file and use replace refs instead. + +--convert-graft-file:: + Creates graft commits for all entries in `$GIT_DIR/info/grafts` + and deletes that file upon success. The purpose is to help users + with transitioning off of the now-deprecated graft file. -l <pattern>:: --list <pattern>::
builtin/replace.c +43 -1
80 lines not on this page · raw message
Signature · 1 line
2.17.0.windows.1.33.gfcbb1fa0445
PATCH v6 6/11 Add a test for `git replace --convert-graft-file`
In reply to Johannes SchindelinThe proof, as the saying goes, lies in the pudding. So here is a regression test that not only demonstrates what the option is supposed to accomplish, but also demonstrates that it does accomplish it.
--- t/t6050-replace.sh | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+)
t/t6050-replace.sh +20 -0
@@ -444,4 +444,24 @@ test_expect_success GPG '--graft on a commit with a mergetag' ' git replace -d $HASH10 ' +test_expect_success '--convert-graft-file' ' + : add and convert graft file && + printf "%s\n%s %s\n\n# comment\n%s\n" \ + $(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \ + >.git/info/grafts && + git replace --convert-graft-file && + test_path_is_missing .git/info/grafts && + + : verify that the history is now "grafted" && + git rev-list HEAD >out && + test_line_count = 4 out && + + : create invalid graft file and verify that it is not deleted && + test_when_finished "rm -f .git/info/grafts" && + echo $EMPTY_BLOB $EMPTY_TREE >.git/info/grafts && + test_must_fail git replace --convert-graft-file 2>err && + test_i18ngrep "$EMPTY_BLOB $EMPTY_TREE" err && + test_i18ngrep "$EMPTY_BLOB $EMPTY_TREE" .git/info/grafts +' + test_done
Signature · 1 line
2.17.0.windows.1.33.gfcbb1fa0445
Re: [PATCH v6 06/11] Add a test for `git replace --convert-graft-file`
Hallo Johannes,
t/t6050-replace.sh test_expect_success GPG '--graft on a commit with a mergetag' '
Quoted 5 lines
t/t6050-replace.sh +0
-0@@ -444,4 +444,24 @@ test_expect_success GPG '--graft on a commit with a mergetag' '
Note the GPG prereq of the previous test.
t/t6050-replace.sh test_expect_success GPG '--graft on a commit with a mergetag' ' · line 450
Quoted 7 lines
t/t6050-replace.sh +4
-0git replace -d $HASH10 ' +test_expect_success '--convert-graft-file' ' + : add and convert graft file && + printf "%s\n%s %s\n\n# comment\n%s\n" \ + $(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \
This test relies on the piece of history that was created two tests earlier in 'set up a merge commit with a mergetag', and that test, too, has the GPG prereq. So if there is no GPG installed, then that test won't be executed, the history needed by this test won't be created, HEAD won't have a second parent, and I'm sure you know where this is going:
ok 32 # skip set up a merge commit with a mergetag (missing GPG)
<... snip ...>
ok 33 # skip --graft on a commit with a mergetag (missing GPG)
<... snip ...>
++git log --graph --oneline
* ffccc9d hello: again 3 more lines
* 14ac020 hello: 2 more lines
* 093e41a hello: BUG fixed
* 40237c8 hello: 1 more line
* 8fc2a8e hello: 2 more lines
* 4217adb hello: 4 more lines with a BUG
* 00ad688 hello: 4 lines
++: add and convert graft file
+++git rev-parse 'HEAD^^' 'HEAD^' 'HEAD^^' 'HEAD^2'
fatal: ambiguous argument 'HEAD^2': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'
++printf '%s\n%s %s\n\n# comment\n%s\n' 093e41a79d4a8bfe3c758a96c95e5bf9e35ed89c 14ac020163ea60a9d683ce68e36c946f31ecc856 093e41a79d4a8bfe3c758a96c95e5bf9e35ed89c 'HEAD^2' ++cat .git/info/grafts
093e41a79d4a8bfe3c758a96c95e5bf9e35ed89c
14ac020163ea60a9d683ce68e36c946f31ecc856 093e41a79d4a8bfe3c758a96c95e5bf9e35ed89c
# comment
HEAD^2
++git replace --convert-graft-file
hint: Support for <GIT_DIR>/info/grafts is deprecated
hint: and will be removed in a future Git version.
hint:
hint: Please use "git replace --convert-graft-file"
hint: to convert the grafts into replace refs.
hint:
hint: Turn this message off by running
hint: "git config advice.graftFileDeprecated false"
error: bad graft data: HEAD^2
error: new commit is the same as the old one: '14ac020163ea60a9d683ce68e36c946f31ecc856'
error: Not a valid object name: 'HEAD^2'
warning: could not convert the following graft(s):
14ac020163ea60a9d683ce68e36c946f31ecc856 093e41a79d4a8bfe3c758a96c95e5bf9e35ed89c
HEAD^2
error: last command exited with $?=1
not ok 34 - --convert-graft-fileQuoted 19 lines
t/t6050-replace.sh +16
-0+ >.git/info/grafts && + git replace --convert-graft-file && + test_path_is_missing .git/info/grafts && + + : verify that the history is now "grafted" && + git rev-list HEAD >out && + test_line_count = 4 out && + + : create invalid graft file and verify that it is not deleted && + test_when_finished "rm -f .git/info/grafts" && + echo $EMPTY_BLOB $EMPTY_TREE >.git/info/grafts && + test_must_fail git replace --convert-graft-file 2>err && + test_i18ngrep "$EMPTY_BLOB $EMPTY_TREE" err && + test_i18ngrep "$EMPTY_BLOB $EMPTY_TREE" .git/info/grafts +' + test_doneSignature · 1 line
2.17.0.windows.1.33.gfcbb1fa0445
Re: [PATCH v6 06/11] Add a test for `git replace --convert-graft-file`
Hi Gábor,
Quoted 9 lines · SZEDER Gábor
t/t6050-replace.sh test_expect_success GPG '--graft on a commit with a mergetag' '
Quoted 5 lines
t/t6050-replace.sh +0
-0@@ -444,4 +444,24 @@ test_expect_success GPG '--graft on a commit with a mergetag' 'Note the GPG prereq of the previous test.
[... demonstrates how the new test fails without the GPG prereq ...]
Ouch. You're right. Will fix,
Dscho
PATCH v6 7/11 Deprecate support for .git/info/grafts
In reply to Johannes SchindelinThe grafts feature was a convenient way to "stitch together" ancient history to the fresh start of linux.git.
Its implementation is, however, not up to Git's standards, as there are too many ways where it can lead to surprising and unwelcome behavior.
For example, when pushing from a repository with active grafts, it is possible to miss commits that have been "grafted out", resulting in a broken state on the other side.
Also, the grafts feature is limited to "rewriting" commits' list of parents, it cannot replace anything else.
The much younger feature implemented as git replace set out to remedy those limitations and dangerous bugs.
Seeing as git replace is pretty mature by now (since 4228e8bc98 (replace: add --graft option, 2014-07-19) it can perform the graft file's duties), it is time to deprecate support for the graft file, and to retire it eventually.
--- advice.c | 2 ++ advice.h | 1 + commit.c | 10 ++++++++++ t/t6001-rev-list-graft.sh | 9 +++++++++ 4 files changed, 22 insertions(+)
advice.c +2 -0
@@ -19,6 +19,7 @@ int advice_rm_hints = 1;int advice_add_embedded_repo =1 ;int advice_ignored_hook =1 ;int advice_waiting_for_editor =1 ; +int advice_graft_file_deprecated =1 ; static struct { const char *name;
@@ -42,6 +43,7 @@ static struct { { "addembeddedrepo", &advice_add_embedded_repo }, { "ignoredhook", &advice_ignored_hook }, { "waitingforeditor", &advice_waiting_for_editor }, + { "graftfiledeprecated", &advice_graft_file_deprecated },/* make this an alias for backward compatibility */ { "pushnonfastforward", &advice_push_update_rejected }
advice.h +1 -0
@@ -21,6 +21,7 @@ extern int advice_rm_hints;extern int advice_add_embedded_repo;extern int advice_ignored_hook;extern int advice_waiting_for_editor; +extern int advice_graft_file_deprecated;int git_default_advice_config (const char *var,const char *value); __attribute__((format (printf, 1, 2)))
commit.c +10 -0
@@ -12,6 +12,7 @@#include "prio-queue.h" #include "sha1-lookup.h" #include "wt-status.h" +#include "advice.h" static struct commit_extra_header *read_commit_extra_header_lines (const char *buf,size_t len,const char **);
@@ -176,6 +177,15 @@ static int read_graft_file(const char *graft_file)struct strbuf buf =STRBUF_INIT ;if (!fp)return -1 ; +if (advice_graft_file_deprecated) +advise (_ ("Support for <GIT_DIR>/info/grafts is deprecated\n" +"and will be removed in a future Git version.\n" +"\n" +"Please use \"git replace --convert-graft-file\"\n" +"to convert the grafts into replace refs.\n" +"\n" +"Turn this message off by running\n" +"\"git config advice.graftFileDeprecated false\"" ));while (!strbuf_getwholeline (&buf, fp,'\n' )) {/* The format is just "Commit Parent1 Parent2 ...\n" */ struct commit_graft *graft =read_graft_line (&buf);
t/t6001-rev-list-graft.sh +9 -0
@@ -110,4 +110,13 @@ do " done + +test_expect_success 'show advice that grafts are deprecated' ' + git show HEAD 2>err && + test_i18ngrep "git replace" err && + test_config advice.graftFileDeprecated false && + git show HEAD 2>err && + test_i18ngrep ! "git replace" err +' + test_done
Signature · 1 line
2.17.0.windows.1.33.gfcbb1fa0445
PATCH v6 8/11 filter-branch: stop suggesting to use grafts
In reply to Johannes SchindelinThe graft file is deprecated now, so let's use replace refs in the example in filter-branch's man page instead.
--- Documentation/git-filter-branch.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
Documentation/git-filter-branch.txt +1 -1
@@ -288,7 +288,7 @@ git filter-branch --parent-filter \ or even simpler: ----------------------------------------------- -echo "$commit-id $graft-id" >> .git/info/grafts +git replace --graft $commit-id $graft-id git filter-branch $graft-id..HEAD -----------------------------------------------
Signature · 1 line
2.17.0.windows.1.33.gfcbb1fa0445
PATCH v6 9/11 technical/shallow: stop referring to grafts
In reply to Johannes SchindelinNow that grafts are deprecated, we should start to assume that readers have no idea what grafts are. So it makes more sense to make the description of the "shallow" feature stand on its own.
--- Documentation/technical/shallow.txt | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-)
Documentation/technical/shallow.txt +4 -9
@@ -8,15 +8,10 @@ repo, and therefore grafts are introduced pretending that these commits have no parents. ********************************************************* -The basic idea is to write the SHA-1s of shallow commits into -$GIT_DIR/shallow, and handle its contents like the contents -of $GIT_DIR/info/grafts (with the difference that shallow -cannot contain parent information). - -This information is stored in a new file instead of grafts, or -even the config, since the user should not touch that file -at all (even throughout development of the shallow clone, it -was never manually edited!). +$GIT_DIR/shallow lists commit object names and tells Git to +pretend as if they are root commits (e.g. "git log" traversal +stops after showing them; "git fsck" does not complain saying +the commits listed on their "parent" lines do not exist). Each line contains exactly one SHA-1. When read, a commit_graft will be constructed, which has nr_parent < 0 to make it easier
Signature · 1 line
2.17.0.windows.1.33.gfcbb1fa0445
PATCH v6 10/11 technical/shallow: describe why shallow cannot use replace refs
In reply to Johannes SchindelinIt is tempting to do away with commit_graft altogether (in the long haul), now that grafts are deprecated.
However, the shallow feature needs a couple of things that the replace refs cannot fulfill. Let's point that out in the documentation.
--- Documentation/technical/shallow.txt | 7 +++++++ 1 file changed, 7 insertions(+)
Documentation/technical/shallow.txt +7 -0
@@ -17,6 +17,13 @@ Each line contains exactly one SHA-1. When read, a commit_graft will be constructed, which has nr_parent < 0 to make it easier to discern from user provided grafts. +Note that the shallow feature could not be changed easily to +use replace refs: a commit containing a `mergetag` is not allowed +to be replaced, not even by a root commit. Such a commit can be +made shallow, though. Also, having a `shallow` file explicitly +listing all the commits made shallow makes it a *lot* easier to +do shallow-specific things such as to deepen the history. + Since fsck-objects relies on the library to read the objects, it honours shallow commits automatically.
Signature · 1 line
2.17.0.windows.1.33.gfcbb1fa0445
PATCH v6 11/11 Remove obsolete script to convert grafts to replace refs
In reply to Johannes SchindelinThe functionality is now implemented as git replace --convert-graft-file.
--- contrib/convert-grafts-to-replace-refs.sh | 28 ----------------------- 1 file changed, 28 deletions(-) delete mode 100755 contrib/convert-grafts-to-replace-refs.sh
contrib/convert-grafts-to-replace-refs.sh deleted +0 -28
@@ -1,28 +0,0 @@ -#!/bin/sh - -# You should execute this script in the repository where you -# want to convert grafts to replace refs. - -GRAFTS_FILE="${GIT_DIR:-.git}/info/grafts" - -. $(git --exec-path)/git-sh-setup - -test -f "$GRAFTS_FILE" || die "Could not find graft file: '$GRAFTS_FILE'" - -grep '^[^# ]' "$GRAFTS_FILE" | -while read definition -do - if test -n "$definition" - then - echo "Converting: $definition" - git replace --graft $definition || - die "Conversion failed for: $definition" - fi -done - -mv "$GRAFTS_FILE" "$GRAFTS_FILE.bak" || - die "Could not rename '$GRAFTS_FILE' to '$GRAFTS_FILE.bak'" - -echo "Success!" -echo "All the grafts in '$GRAFTS_FILE' have been converted to replace refs!" -echo "The grafts file '$GRAFTS_FILE' has been renamed: '$GRAFTS_FILE.bak'"
Signature · 1 line
2.17.0.windows.1.33.gfcbb1fa0445
Re: [PATCH v6 11/11] Remove obsolete script to convert grafts to replace refs
From: "Johannes Schindelin" <[email protected]>
Quoted 2 lines
The functionality is now implemented as
git replace --convert-graft-file.
A rather late in the day thought: Should this go through the same deprecation dance?
I.e. replace the body of the script with the new `git
replace --convert-graft-file` and echo (or die!) a warning message that this script is now deprecated and will be removed?
At least it will catch those who arrive via random web advice!
--
Philip
Quoted 47 lines
Signed-off-by Johannes Schindelin <[email protected]>---
contrib/convert-grafts-to-replace-refs.sh| 28 -----------------------1 file changed, 28 deletions(-)delete mode 100755
contrib/convert-grafts-to-replace-refs.shcontrib/convert-grafts-to-replace-refs.sh deleted +0
-27@@ -1,28 +0,0 @@ -#!/bin/sh - -# You should execute this script in the repository where you -# want to convert grafts to replace refs. - -GRAFTS_FILE="${GIT_DIR:-.git}/info/grafts" - -. $(git --exec-path)/git-sh-setup - -test -f "$GRAFTS_FILE" || die "Could not find graft file: '$GRAFTS_FILE'" - -grep '^[^# ]' "$GRAFTS_FILE" | -while read definition -do - if test -n "$definition" - then - echo "Converting: $definition" - git replace --graft $definition || - die "Conversion failed for: $definition" - fi -done - -mv "$GRAFTS_FILE" "$GRAFTS_FILE.bak" || - die "Could not rename '$GRAFTS_FILE' to '$GRAFTS_FILE.bak'" - -echo "Success!" -echo "All the grafts in '$GRAFTS_FILE' have been converted to replacerefs!"
-echo "The grafts file '$GRAFTS_FILE' has been renamed:
'$GRAFTS_FILE.bak'"Signature · 1 line
2.17.0.windows.1.33.gfcbb1fa0445
Re: [PATCH v6 11/11] Remove obsolete script to convert grafts to replace refs
Hi Philip,
Quoted 4 lines · Philip Oakley
From: "Johannes Schindelin" <[email protected]>
Quoted 2 lines
The functionality is now implemented as
git replace --convert-graft-file.
Quoted 8 lines
A rather late in the day thought: Should this go through the same deprecation dance?
I.e. replace the body of the script with the new `git
replace --convert-graft-file` and echo (or die!) a warning message that this script is now deprecated and will be removed?At least it will catch those who arrive via random web advice!
Originally, I did not even want to remove it... I just did as I was told by Junio...
Ciao,
Dscho
Re: [PATCH v6 11/11] Remove obsolete script to convert grafts to replace refs
In reply to Philip OakleyHi Philip,
Quoted 8 lines · Philip Oakley
From: "Johannes Schindelin" <[email protected]>
Quoted 3 lines
The functionality is now implemented as
git replace --convert-graft-file.A rather late in the day thought: Should this go through the same deprecation dance?
I think contrib/ has other rules than the core
(c.f. git log --oneline --no-merges --grep "contrib: remove") which is why I'd consider the deprecation dance unneeded.
Quoted 1 line
At least it will catch those who arrive via random web advice!
Good point, unlike the other removals, this is not a dormant project.
I would still think it is ok to remove it. Those who are looking for this script, surely will look at the man page of git-replace or are looking for the grafts. man gitglossary is already sufficient:
grafts
Grafts enables [...]
Note that the grafts mechanism is outdated and can lead to problems transferring objects between repositories; see git-replace(1) for a more flexible and robust system to do the same thing.
v7 cover Deprecate .git/info/grafts
In reply to Johannes Schindelin[Resent the cover letter, as I did not see it on public-inbox...]
It is fragile, as there is no way for the revision machinery to say "but now I want to traverse the graph ignoring the graft file" e.g. when pushing commits to a remote repository (which, as a consequence, can miss commits).
And we already have a better solution with git replace --graft <comit> [<parent>...].
Changes since v6:
- Made --convert-graft-file issue a mere warning (instead of an error) when a graft leaves the parents unchanged, and is hence unnecessary.
- Modified the regression test for --convert-graft-file to succeed even when the GPG prerequisite is unmet (thanks, Gábor!).
- Reassured by Stefan's feedback, I refrained from reinstating the contrib/ script.
Johannes Schindelin (12):
argv_array: offer to split a string by whitespace
commit: Let the callback of for_each_mergetag return on error
replace: avoid using die() to indicate a bug
replace: "libify" create_graft() and callees
replace: prepare create_graft() for converting graft files wholesale
replace: introduce --convert-graft-file
Add a test for git replace --convert-graft-file Deprecate support for .git/info/grafts
filter-branch: stop suggesting to use grafts technical/shallow: stop referring to grafts technical/shallow: describe why shallow cannot use replace refs Remove obsolete script to convert grafts to replace refs
Documentation/git-filter-branch.txt | 2 +- Documentation/git-replace.txt | 11 +- Documentation/technical/shallow.txt | 20 +- advice.c | 2 + advice.h | 1 + argv-array.c | 20 ++ argv-array.h | 2 + builtin/replace.c | 234 ++++++++++++++++------ commit.c | 18 +- commit.h | 4 +- contrib/convert-grafts-to-replace-refs.sh | 28 --- log-tree.c | 13 +- t/t6001-rev-list-graft.sh | 9 + t/t6050-replace.sh | 28 +++ 14 files changed, 274 insertions(+), 118 deletions(-) delete mode 100755 contrib/convert-grafts-to-replace-refs.sh
base-commit: 1f1cddd558b54bb0ce19c8ace353fd07b758510d
Published-As: https://github.com/dscho/git/releases/tag/deprecate-grafts-v7
Fetch-It-Via: git fetch https://github.com/dscho/git deprecate-grafts-v7
Interdiff vs v6:
diff --git a/builtin/replace.c b/builtin/replace.c
index 35394ec1874..a87fca045be 100644
--- a/builtin/replace.c
+++ b/builtin/replace.c
@@ -429,7 +429,7 @@ static int check_mergetags(struct commit *commit, int argc, const char **argv)
return for_each_mergetag(check_one_mergetag, commit, &mergetag_data);
}
-static int create_graft(int argc, const char **argv, int force) +static int create_graft(int argc, const char **argv, int force, int gentle)
{
struct object_id old_oid, new_oid;
const char *old_ref = argv[0];
@@ -471,8 +471,13 @@ static int create_graft(int argc, const char **argv, int force)
strbuf_release(&buf);
- if (!oidcmp(&old_oid, &new_oid))
+ if (!oidcmp(&old_oid, &new_oid)) {
+ if (gentle) {
+ warning("graft for '%s' unnecessary", oid_to_hex(&old_oid));
+ return 0;
+ }
return error("new commit is the same as the old one: '%s'", oid_to_hex(&old_oid));
+ }
return replace_object_oid(old_ref, &old_oid, "replacement", &new_oid, force);
}
@@ -492,7 +497,7 @@ static int convert_graft_file(int force)
continue;
argv_array_split(&args, buf.buf);
- if (args.argc && create_graft(args.argc, args.argv, force))
+ if (args.argc && create_graft(args.argc, args.argv, force, 1))
strbuf_addf(&err, "\n\t%s", buf.buf);
argv_array_clear(&args);
}
@@ -583,7 +588,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix)
if (argc < 1)
usage_msg_opt("-g needs at least one argument",
git_replace_usage, options);
- return create_graft(argc, argv, force);
+ return create_graft(argc, argv, force, 0);
case MODE_CONVERT_GRAFT_FILE:
if (argc != 0)
diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh
index bed86a0af3d..d174bfed309 100755
--- a/t/t6050-replace.sh
+++ b/t/t6050-replace.sh
@@ -445,6 +445,14 @@ test_expect_success GPG '--graft on a commit with a mergetag' '
'
test_expect_success '--convert-graft-file' '
+ git checkout -b with-graft-file &&
+ test_commit root2 &&
+ git reset --hard root2^ &&
+ test_commit root1 &&
+ test_commit after-root1 &&
+ test_tick &&
+ git merge -m merge-root2 root2 &&
+
: add and convert graft file &&
printf "%s\n%s %s\n\n# comment\n%s\n" \
$(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \
Signature · 1 line
2.17.0.windows.1.36.gdf4ca5fb72a
PATCH v7 1/12 argv_array: offer to split a string by whitespace
This is a simple function that will interpret a string as a whitespace delimited list of values, and add those values into the array.
Note: this function does not (yet) offer to split by arbitrary delimiters, or keep empty values in case of runs of whitespace, or de-quote Unix shell style. All fo this functionality can be added later, when and if needed.
--- argv-array.c | 20 ++++++++++++++++++++ argv-array.h | 2 ++ 2 files changed, 22 insertions(+)
argv-array.c +20 -0
@@ -64,6 +64,26 @@ void argv_array_pop(struct argv_array *array) array->argc--; } +void argv_array_split (struct argv_array *array,const char *to_split) +{ +while (isspace (*to_split)) + to_split++; +for (;;) { +const char *p = to_split; + +if (!*p) +break ; + +while (*p && !isspace (*p)) + p++; +argv_array_push_nodup (array,xstrndup (to_split, p - to_split)); + +while (isspace (*p)) + p++; + to_split = p; + } +} +void argv_array_clear (struct argv_array *array) {if (array->argv != empty_argv) {
argv-array.h +2 -0
@@ -19,6 +19,8 @@ LAST_ARG_MUST_BE_NULLvoid argv_array_pushl (struct argv_array *, ...);void argv_array_pushv (struct argv_array *,const char **);void argv_array_pop (struct argv_array *); +/* Splits by whitespace; does not handle quoted arguments! */ +void argv_array_split (struct argv_array *,const char *);void argv_array_clear (struct argv_array *);const char **argv_array_detach (struct argv_array *);
Signature · 1 line
2.17.0.windows.1.36.gdf4ca5fb72a
PATCH v7 2/12 commit: Let the callback of for_each_mergetag return on error
In reply to Johannes SchindelinThis is yet another patch to be filed under the keyword "libification".
There is one subtle change in behavior here, where a git log that has been asked to show the mergetags would now stop reporting the mergetags upon the first failure, whereas previously, it would have continued to the next mergetag, if any.
In practice, that change should not matter, as it is 1) uncommon to perform octopus merges using multiple tags as merge heads, and 2) when the user asks to be shown those tags, they really should be there.
--- builtin/replace.c | 8 ++++---- commit.c | 8 +++++--- commit.h | 4 ++-- log-tree.c | 13 +++++++------ 4 files changed, 18 insertions(+), 15 deletions(-)
builtin/replace.c +4 -4
@@ -345,7 +345,7 @@ struct check_mergetag_data {const char **argv; }; -static void check_one_mergetag (struct commit *commit, +static int check_one_mergetag (struct commit *commit,struct commit_extra_header *extra,void *data) {
@@ -368,20 +368,20 @@ static void check_one_mergetag(struct commit *commit,if (get_oid (mergetag_data->argv[i], &oid) <0 )die (_ ("Not a valid object name: '%s'" ), mergetag_data->argv[i]);if (!oidcmp (&tag->tagged->oid, &oid)) -return ;/* found */ +return 0 ;/* found */ }die (_ ("original commit '%s' contains mergetag '%s' that is discarded; " "use --edit instead of --graft" ), ref,oid_to_hex (&tag_oid)); } -static void check_mergetags (struct commit *commit,int argc,const char **argv) +static int check_mergetags (struct commit *commit,int argc,const char **argv) {struct check_mergetag_data mergetag_data; mergetag_data.argc = argc; mergetag_data.argv = argv; -for_each_mergetag (check_one_mergetag, commit, &mergetag_data); +return for_each_mergetag (check_one_mergetag, commit, &mergetag_data); }static int create_graft (int argc,const char **argv,int force)
commit.c +5 -3
@@ -1288,17 +1288,19 @@ struct commit_extra_header *read_commit_extra_headers(struct commit *commit,return extra; } -void for_each_mergetag (each_mergetag_fn fn,struct commit *commit,void *data) +int for_each_mergetag (each_mergetag_fn fn,struct commit *commit,void *data) {struct commit_extra_header *extra, *to_free; +int res =0 ; to_free =read_commit_extra_headers (commit,NULL ); -for (extra = to_free; extra; extra = extra->next) { +for (extra = to_free; !res && extra; extra = extra->next) {if (strcmp (extra->key,"mergetag" ))continue ;/* not a merge tag */ -fn (commit, extra, data); + res =fn (commit, extra, data); }free_commit_extra_headers (to_free); +return res; }static inline int standard_header_field (const char *field,size_t len)
commit.h +2 -2
@@ -291,10 +291,10 @@ extern const char *find_commit_header(const char *msg, const char *key,/* Find the end of the log message, the right place for a new trailer. */ extern int ignore_non_trailer (const char *buf,size_t len); -typedef void (*each_mergetag_fn )(struct commit *commit,struct commit_extra_header *extra, +typedef int (*each_mergetag_fn )(struct commit *commit,struct commit_extra_header *extra,void *cb_data); -extern void for_each_mergetag (each_mergetag_fn fn,struct commit *commit,void *data); +extern int for_each_mergetag (each_mergetag_fn fn,struct commit *commit,void *data);struct merge_remote_desc {struct object *obj;/* the named object, could be a tag */
log-tree.c +7 -6
@@ -488,9 +488,9 @@ static int is_common_merge(const struct commit *commit) && !commit->parents->next->next); } -static void show_one_mergetag (struct commit *commit, -struct commit_extra_header *extra, -void *data) +static int show_one_mergetag (struct commit *commit, +struct commit_extra_header *extra, +void *data) {struct rev_info *opt = (struct rev_info *)data;struct object_id oid;
@@ -502,7 +502,7 @@ static void show_one_mergetag(struct commit *commit,hash_object_file (extra->value, extra->len,type_name (OBJ_TAG ), &oid); tag =lookup_tag (&oid);if (!tag) -return ;/* error message already given */ +return -1 ;/* error message already given */ strbuf_init (&verify_message,256 );if (parse_tag_buffer (tag, extra->value, extra->len))
@@ -536,11 +536,12 @@ static void show_one_mergetag(struct commit *commit,show_sig_lines (opt, status, verify_message.buf);strbuf_release (&verify_message); +return 0 ; } -static void show_mergetag (struct rev_info *opt,struct commit *commit) +static int show_mergetag (struct rev_info *opt,struct commit *commit) { -for_each_mergetag (show_one_mergetag, commit, opt); +return for_each_mergetag (show_one_mergetag, commit, opt); }void show_log (struct rev_info *opt)
Signature · 1 line
2.17.0.windows.1.36.gdf4ca5fb72a
PATCH v7 3/12 replace: avoid using die() to indicate a bug
In reply to Johannes SchindelinWe have the BUG() macro for that purpose.
--- builtin/replace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
builtin/replace.c +1 -1
@@ -501,6 +501,6 @@ int cmd_replace(int argc, const char **argv, const char *prefix)return list_replace_refs (argv[0 ], format);default : -die ("BUG: invalid cmdmode %d" , (int )cmdmode); +BUG ("invalid cmdmode %d" , (int )cmdmode); } }
Signature · 1 line
2.17.0.windows.1.36.gdf4ca5fb72a
PATCH v7 4/12 replace: "libify" create_graft() and callees
In reply to Johannes SchindelinFile this away as yet another patch in the "libification" category.
As with all useful functions, in the next commit we want to use create_graft() from a higher-level function where it would be inconvenient if the called function simply die()s: if there is a problem, we want to let the user know how to proceed, and the callee simply has no way of knowing what to say.
--- builtin/replace.c | 169 ++++++++++++++++++++++++++++++---------------- 1 file changed, 112 insertions(+), 57 deletions(-)
builtin/replace.c +112 -57
@@ -79,9 +79,9 @@ static int list_replace_refs(const char *pattern, const char *format)else if (!strcmp (format,"long" )) data.format =REPLACE_FORMAT_LONG ;else -die ("invalid replace format '%s'\n" -"valid formats are 'short', 'medium' and 'long'\n" , - format); +return error ("invalid replace format '%s'\n" +"valid formats are 'short', 'medium' and 'long'\n" , + format);for_each_replace_ref (show_reference, (void *)&data);
@@ -134,7 +134,7 @@ static int delete_replace_ref(const char *name, const char *ref,return 0 ; } -static void check_ref_valid (struct object_id *object, +static int check_ref_valid (struct object_id *object,struct object_id *prev,struct strbuf *ref,int force)
@@ -142,12 +142,13 @@ static void check_ref_valid(struct object_id *object,strbuf_reset (ref);strbuf_addf (ref,"%s%s" , git_replace_ref_base,oid_to_hex (object));if (check_refname_format (ref->buf,0 )) -die ("'%s' is not a valid ref name." , ref->buf); +return error ("'%s' is not a valid ref name." , ref->buf);if (read_ref (ref->buf, prev))oidclr (prev);else if (!force) -die ("replace ref '%s' already exists" , ref->buf); +return error ("replace ref '%s' already exists" , ref->buf); +return 0 ; } static int replace_object_oid(const char *object_ref,
@@ -161,28 +162,33 @@ static int replace_object_oid(const char *object_ref,struct strbuf ref =STRBUF_INIT ;struct ref_transaction *transaction;struct strbuf err =STRBUF_INIT ; +int res =0 ; obj_type =oid_object_info (object,NULL ); repl_type =oid_object_info (repl,NULL );if (!force && obj_type != repl_type) -die ("Objects must be of the same type.\n" -"'%s' points to a replaced object of type '%s'\n" -"while '%s' points to a replacement object of type '%s'." , - object_ref,type_name (obj_type), - replace_ref,type_name (repl_type)); - -check_ref_valid (object, &prev, &ref, force); +return error ("Objects must be of the same type.\n" +"'%s' points to a replaced object of type '%s'\n" +"while '%s' points to a replacement object of " +"type '%s'." , + object_ref,type_name (obj_type), + replace_ref,type_name (repl_type)); + +if (check_ref_valid (object, &prev, &ref, force)) { +strbuf_release (&ref); +return -1 ; + } transaction =ref_transaction_begin (&err);if (!transaction ||ref_transaction_update (transaction, ref.buf, repl, &prev,0 ,NULL , &err) ||ref_transaction_commit (transaction, &err)) -die ("%s" , err.buf); + res =error ("%s" , err.buf);ref_transaction_free (transaction);strbuf_release (&ref); -return 0 ; +return res; }static int replace_object (const char *object_ref,const char *replace_ref,int force)
@@ -190,9 +196,11 @@ static int replace_object(const char *object_ref, const char *replace_ref, int fstruct object_id object, repl;if (get_oid (object_ref, &object)) -die ("Failed to resolve '%s' as a valid ref." , object_ref); +return error ("Failed to resolve '%s' as a valid ref." , + object_ref);if (get_oid (replace_ref, &repl)) -die ("Failed to resolve '%s' as a valid ref." , replace_ref); +return error ("Failed to resolve '%s' as a valid ref." , + replace_ref);return replace_object_oid (object_ref, &object, replace_ref, &repl, force); }
@@ -202,7 +210,7 @@ static int replace_object(const char *object_ref, const char *replace_ref, int f* If "raw" is true, then the object's raw contents are printed according to * "type". Otherwise, we pretty-print the contents for human editing. */ -static void export_object (const struct object_id *oid,enum object_type type, +static int export_object (const struct object_id *oid,enum object_type type,int raw,const char *filename) {struct child_process cmd =CHILD_PROCESS_INIT ;
@@ -210,7 +218,7 @@ static void export_object(const struct object_id *oid, enum object_type type, fd =open (filename,O_WRONLY |O_CREAT |O_TRUNC ,0666 );if (fd <0 ) -die_errno ("unable to open %s for writing" , filename); +return error_errno ("unable to open %s for writing" , filename);argv_array_push (&cmd.args,"--no-replace-objects" );argv_array_push (&cmd.args,"cat-file" );
@@ -223,7 +231,8 @@ static void export_object(const struct object_id *oid, enum object_type type, cmd.out = fd;if (run_command (&cmd)) -die ("cat-file reported failure" ); +return error ("cat-file reported failure" ); +return 0 ; }/*
@@ -231,14 +240,14 @@ static void export_object(const struct object_id *oid, enum object_type type,* interpreting it as "type", and writing the result to the object database. * The sha1 of the written object is returned via sha1. */ -static void import_object (struct object_id *oid,enum object_type type, +static int import_object (struct object_id *oid,enum object_type type,int raw,const char *filename) {int fd; fd =open (filename,O_RDONLY );if (fd <0 ) -die_errno ("unable to open %s for reading" , filename); +return error_errno ("unable to open %s for reading" , filename);if (!raw && type ==OBJ_TREE ) {const char *argv[] = {"mktree" ,NULL };
@@ -250,27 +259,40 @@ static void import_object(struct object_id *oid, enum object_type type, cmd.in = fd; cmd.out =-1 ; -if (start_command (&cmd)) -die ("unable to spawn mktree" ); +if (start_command (&cmd)) { +close (fd); +return error ("unable to spawn mktree" ); + } -if (strbuf_read (&result, cmd.out,41 ) <0 ) -die_errno ("unable to read from mktree" ); +if (strbuf_read (&result, cmd.out,41 ) <0 ) { +error_errno ("unable to read from mktree" ); +close (fd); +close (cmd.out); +return -1 ; + }close (cmd.out); -if (finish_command (&cmd)) -die ("mktree reported failure" ); -if (get_oid_hex (result.buf, oid) <0 ) -die ("mktree did not return an object name" ); +if (finish_command (&cmd)) { +strbuf_release (&result); +return error ("mktree reported failure" ); + } +if (get_oid_hex (result.buf, oid) <0 ) { +strbuf_release (&result); +return error ("mktree did not return an object name" ); + }strbuf_release (&result); }else {struct stat st;int flags =HASH_FORMAT_CHECK |HASH_WRITE_OBJECT ; -if (fstat (fd, &st) <0 ) -die_errno ("unable to fstat %s" , filename); +if (fstat (fd, &st) <0 ) { +error_errno ("unable to fstat %s" , filename); +close (fd); +return -1 ; + }if (index_fd (oid, fd, &st, type,NULL , flags) <0 ) -die ("unable to write object to database" ); +return error ("unable to write object to database" );/* index_fd close()s fd for us */ }
@@ -278,30 +300,43 @@ static void import_object(struct object_id *oid, enum object_type type,* No need to close(fd) here; both run-command and index-fd * will have done it for us. */ +return 0 ; }static int edit_and_replace (const char *object_ref,int force,int raw) { -char *tmpfile =git_pathdup ("REPLACE_EDITOBJ" ); +char *tmpfile;enum object_type type;struct object_id old_oid, new_oid, prev;struct strbuf ref =STRBUF_INIT ;if (get_oid (object_ref, &old_oid) <0 ) -die ("Not a valid object name: '%s'" , object_ref); +return error ("Not a valid object name: '%s'" , object_ref); type =oid_object_info (&old_oid,NULL );if (type <0 ) -die ("unable to get object type for %s" ,oid_to_hex (&old_oid)); +return error ("unable to get object type for %s" , +oid_to_hex (&old_oid)); -check_ref_valid (&old_oid, &prev, &ref, force); +if (check_ref_valid (&old_oid, &prev, &ref, force)) { +strbuf_release (&ref); +return -1 ; + }strbuf_release (&ref); -export_object (&old_oid, type, raw, tmpfile); -if (launch_editor (tmpfile,NULL ,NULL ) <0 ) -die ("editing object file failed" ); -import_object (&new_oid, type, raw, tmpfile); - + tmpfile =git_pathdup ("REPLACE_EDITOBJ" ); +if (export_object (&old_oid, type, raw, tmpfile)) { +free (tmpfile); +return -1 ; + } +if (launch_editor (tmpfile,NULL ,NULL ) <0 ) { +free (tmpfile); +return error ("editing object file failed" ); + } +if (import_object (&new_oid, type, raw, tmpfile)) { +free (tmpfile); +return -1 ; + }free (tmpfile);if (!oidcmp (&old_oid, &new_oid))
@@ -310,7 +345,7 @@ static int edit_and_replace(const char *object_ref, int force, int raw)return replace_object_oid (object_ref, &old_oid,"replacement" , &new_oid, force); } -static void replace_parents (struct strbuf *buf,int argc,const char **argv) +static int replace_parents (struct strbuf *buf,int argc,const char **argv) {struct strbuf new_parents =STRBUF_INIT ;const char *parent_start, *parent_end;
@@ -327,9 +362,15 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv)/* prepare new parents */ for (i =0 ; i < argc; i++) {struct object_id oid; -if (get_oid (argv[i], &oid) <0 ) -die (_ ("Not a valid object name: '%s'" ), argv[i]); -lookup_commit_or_die (&oid, argv[i]); +if (get_oid (argv[i], &oid) <0 ) { +strbuf_release (&new_parents); +return error (_ ("Not a valid object name: '%s'" ), + argv[i]); + } +if (!lookup_commit_reference (&oid)) { +strbuf_release (&new_parents); +return error (_ ("could not parse %s" ), argv[i]); + }strbuf_addf (&new_parents,"parent %s\n" ,oid_to_hex (&oid)); }
@@ -338,6 +379,7 @@ static void replace_parents(struct strbuf *buf, int argc, const char **argv) new_parents.buf, new_parents.len);strbuf_release (&new_parents); +return 0 ; }struct check_mergetag_data {
@@ -358,21 +400,23 @@ static int check_one_mergetag(struct commit *commit,hash_object_file (extra->value, extra->len,type_name (OBJ_TAG ), &tag_oid); tag =lookup_tag (&tag_oid);if (!tag) -die (_ ("bad mergetag in commit '%s'" ), ref); +return error (_ ("bad mergetag in commit '%s'" ), ref);if (parse_tag_buffer (tag, extra->value, extra->len)) -die (_ ("malformed mergetag in commit '%s'" ), ref); +return error (_ ("malformed mergetag in commit '%s'" ), ref);/* iterate over new parents */ for (i =1 ; i < mergetag_data->argc; i++) {struct object_id oid;if (get_oid (mergetag_data->argv[i], &oid) <0 ) -die (_ ("Not a valid object name: '%s'" ), mergetag_data->argv[i]); +return error (_ ("Not a valid object name: '%s'" ), + mergetag_data->argv[i]);if (!oidcmp (&tag->tagged->oid, &oid))return 0 ;/* found */ } -die (_ ("original commit '%s' contains mergetag '%s' that is discarded; " -"use --edit instead of --graft" ), ref,oid_to_hex (&tag_oid)); +return error (_ ("original commit '%s' contains mergetag '%s' that is " +"discarded; use --edit instead of --graft" ), ref, +oid_to_hex (&tag_oid)); }static int check_mergetags (struct commit *commit,int argc,const char **argv)
@@ -394,24 +438,35 @@ static int create_graft(int argc, const char **argv, int force)unsigned long size;if (get_oid (old_ref, &old_oid) <0 ) -die (_ ("Not a valid object name: '%s'" ), old_ref); - commit =lookup_commit_or_die (&old_oid, old_ref); +return error (_ ("Not a valid object name: '%s'" ), old_ref); + commit =lookup_commit_reference (&old_oid); +if (!commit) +return error (_ ("could not parse %s" ), old_ref); buffer =get_commit_buffer (commit, &size);strbuf_add (&buf, buffer, size);unuse_commit_buffer (commit, buffer); -replace_parents (&buf, argc -1 , &argv[1 ]); +if (replace_parents (&buf, argc -1 , &argv[1 ]) <0 ) { +strbuf_release (&buf); +return -1 ; + }if (remove_signature (&buf)) {warning (_ ("the original commit '%s' has a gpg signature." ), old_ref);warning (_ ("the signature will be removed in the replacement commit!" )); } -check_mergetags (commit, argc, argv); +if (check_mergetags (commit, argc, argv)) { +strbuf_release (&buf); +return -1 ; + } -if (write_object_file (buf.buf, buf.len, commit_type, &new_oid)) -die (_ ("could not write replacement commit for: '%s'" ), old_ref); +if (write_object_file (buf.buf, buf.len, commit_type, &new_oid)) { +strbuf_release (&buf); +return error (_ ("could not write replacement commit for: '%s'" ), + old_ref); + }strbuf_release (&buf);
Signature · 1 line
2.17.0.windows.1.36.gdf4ca5fb72a
PATCH v7 5/12 replace: prepare create_graft() for converting graft files wholesale
In reply to Johannes SchindelinWhen converting all grafts in a graft file to replace refs, and one of them happens to leave the original commit's parents unchanged, we do not want to error out. Instead, we would like to issue a warning.
Prepare the create_graft() function for such a use case by adding a gentle parameter. If set, we do not return an error when the replace ref is unchanged, but a mere warning.
--- builtin/replace.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-)
builtin/replace.c +8 -3
@@ -428,7 +428,7 @@ static int check_mergetags(struct commit *commit, int argc, const char **argv)return for_each_mergetag (check_one_mergetag, commit, &mergetag_data); } -static int create_graft (int argc,const char **argv,int force) +static int create_graft (int argc,const char **argv,int force,int gentle) {struct object_id old_oid, new_oid;const char *old_ref = argv[0 ];
@@ -470,8 +470,13 @@ static int create_graft(int argc, const char **argv, int force)strbuf_release (&buf); -if (!oidcmp (&old_oid, &new_oid)) +if (!oidcmp (&old_oid, &new_oid)) { +if (gentle) { +warning ("graft for '%s' unnecessary" ,oid_to_hex (&old_oid)); +return 0 ; + }return error ("new commit is the same as the old one: '%s'" ,oid_to_hex (&old_oid)); + }return replace_object_oid (old_ref, &old_oid,"replacement" , &new_oid, force); }
@@ -547,7 +552,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix)if (argc <1 )usage_msg_opt ("-g needs at least one argument" , git_replace_usage, options); -return create_graft (argc, argv, force); +return create_graft (argc, argv, force,0 );case MODE_LIST :if (argc >1 )
Signature · 1 line
2.17.0.windows.1.36.gdf4ca5fb72a
PATCH v7 6/12 replace: introduce --convert-graft-file
In reply to Johannes SchindelinThis option is intended to help with the transition away from the now-deprecated graft file.
--- Documentation/git-replace.txt | 11 ++++++--- builtin/replace.c | 44 ++++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 4 deletions(-)
Documentation/git-replace.txt +8 -3
@@ -11,6 +11,7 @@ SYNOPSIS 'git replace' [-f] <object> <replacement> 'git replace' [-f] --edit <object> 'git replace' [-f] --graft <commit> [<parent>...] +'git replace' [-f] --convert-graft-file 'git replace' -d <object>... 'git replace' [--format=<format>] [-l [<pattern>]]
@@ -87,9 +88,13 @@ OPTIONS content as <commit> except that its parents will be [<parent>...] instead of <commit>'s parents. A replacement ref is then created to replace <commit> with the newly created - commit. See contrib/convert-grafts-to-replace-refs.sh for an - example script based on this option that can convert grafts to - replace refs. + commit. Use `--convert-graft-file` to convert a + `$GIT_DIR/info/grafts` file and use replace refs instead. + +--convert-graft-file:: + Creates graft commits for all entries in `$GIT_DIR/info/grafts` + and deletes that file upon success. The purpose is to help users + with transitioning off of the now-deprecated graft file. -l <pattern>:: --list <pattern>::
builtin/replace.c +43 -1
80 lines not on this page · raw message
Signature · 1 line
2.17.0.windows.1.36.gdf4ca5fb72a
PATCH v7 7/12 Add a test for `git replace --convert-graft-file`
In reply to Johannes SchindelinThe proof, as the saying goes, lies in the pudding. So here is a regression test that not only demonstrates what the option is supposed to accomplish, but also demonstrates that it does accomplish it.
--- t/t6050-replace.sh | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+)
t/t6050-replace.sh +28 -0
@@ -444,4 +444,32 @@ test_expect_success GPG '--graft on a commit with a mergetag' ' git replace -d $HASH10 ' +test_expect_success '--convert-graft-file' ' + git checkout -b with-graft-file && + test_commit root2 && + git reset --hard root2^ && + test_commit root1 && + test_commit after-root1 && + test_tick && + git merge -m merge-root2 root2 && + + : add and convert graft file && + printf "%s\n%s %s\n\n# comment\n%s\n" \ + $(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \ + >.git/info/grafts && + git replace --convert-graft-file && + test_path_is_missing .git/info/grafts && + + : verify that the history is now "grafted" && + git rev-list HEAD >out && + test_line_count = 4 out && + + : create invalid graft file and verify that it is not deleted && + test_when_finished "rm -f .git/info/grafts" && + echo $EMPTY_BLOB $EMPTY_TREE >.git/info/grafts && + test_must_fail git replace --convert-graft-file 2>err && + test_i18ngrep "$EMPTY_BLOB $EMPTY_TREE" err && + test_i18ngrep "$EMPTY_BLOB $EMPTY_TREE" .git/info/grafts +' + test_done
Signature · 1 line
2.17.0.windows.1.36.gdf4ca5fb72a
PATCH v7 8/12 Deprecate support for .git/info/grafts
In reply to Johannes SchindelinThe grafts feature was a convenient way to "stitch together" ancient history to the fresh start of linux.git.
Its implementation is, however, not up to Git's standards, as there are too many ways where it can lead to surprising and unwelcome behavior.
For example, when pushing from a repository with active grafts, it is possible to miss commits that have been "grafted out", resulting in a broken state on the other side.
Also, the grafts feature is limited to "rewriting" commits' list of parents, it cannot replace anything else.
The much younger feature implemented as git replace set out to remedy those limitations and dangerous bugs.
Seeing as git replace is pretty mature by now (since 4228e8bc98 (replace: add --graft option, 2014-07-19) it can perform the graft file's duties), it is time to deprecate support for the graft file, and to retire it eventually.
--- advice.c | 2 ++ advice.h | 1 + commit.c | 10 ++++++++++ t/t6001-rev-list-graft.sh | 9 +++++++++ 4 files changed, 22 insertions(+)
advice.c +2 -0
@@ -19,6 +19,7 @@ int advice_rm_hints = 1;int advice_add_embedded_repo =1 ;int advice_ignored_hook =1 ;int advice_waiting_for_editor =1 ; +int advice_graft_file_deprecated =1 ; static struct { const char *name;
@@ -42,6 +43,7 @@ static struct { { "addembeddedrepo", &advice_add_embedded_repo }, { "ignoredhook", &advice_ignored_hook }, { "waitingforeditor", &advice_waiting_for_editor }, + { "graftfiledeprecated", &advice_graft_file_deprecated },/* make this an alias for backward compatibility */ { "pushnonfastforward", &advice_push_update_rejected }
advice.h +1 -0
@@ -21,6 +21,7 @@ extern int advice_rm_hints;extern int advice_add_embedded_repo;extern int advice_ignored_hook;extern int advice_waiting_for_editor; +extern int advice_graft_file_deprecated;int git_default_advice_config (const char *var,const char *value); __attribute__((format (printf, 1, 2)))
commit.c +10 -0
@@ -12,6 +12,7 @@#include "prio-queue.h" #include "sha1-lookup.h" #include "wt-status.h" +#include "advice.h" static struct commit_extra_header *read_commit_extra_header_lines (const char *buf,size_t len,const char **);
@@ -176,6 +177,15 @@ static int read_graft_file(const char *graft_file)struct strbuf buf =STRBUF_INIT ;if (!fp)return -1 ; +if (advice_graft_file_deprecated) +advise (_ ("Support for <GIT_DIR>/info/grafts is deprecated\n" +"and will be removed in a future Git version.\n" +"\n" +"Please use \"git replace --convert-graft-file\"\n" +"to convert the grafts into replace refs.\n" +"\n" +"Turn this message off by running\n" +"\"git config advice.graftFileDeprecated false\"" ));while (!strbuf_getwholeline (&buf, fp,'\n' )) {/* The format is just "Commit Parent1 Parent2 ...\n" */ struct commit_graft *graft =read_graft_line (&buf);
t/t6001-rev-list-graft.sh +9 -0
@@ -110,4 +110,13 @@ do " done + +test_expect_success 'show advice that grafts are deprecated' ' + git show HEAD 2>err && + test_i18ngrep "git replace" err && + test_config advice.graftFileDeprecated false && + git show HEAD 2>err && + test_i18ngrep ! "git replace" err +' + test_done
Signature · 1 line
2.17.0.windows.1.36.gdf4ca5fb72a
PATCH 1/1 advice: don't pointlessly suggest --convert-graft-file
The advice to run 'git replace --convert-graft-file' added in f9f99b3f7d ("Deprecate support for .git/info/grafts", 2018-04-29) didn't add an exception for the 'git replace --convert-graft-file' codepath itself.
As a result we'd suggest running --convert-graft-file while the user was running --convert-graft-file, which makes no sense. Before:
$ git replace --convert-graft-file
hint: Support for <GIT_DIR>/info/grafts is deprecated
hint: and will be removed in a future Git version.
hint:
hint: Please use "git replace --convert-graft-file"
hint: to convert the grafts into replace refs.
hint:
hint: Turn this message off by running
hint: "git config advice.graftFileDeprecated false"Add a check for that case and skip printing the advice while the user is busy following our advice.
--- builtin/replace.c | 1 + t/t6050-replace.sh | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-)
builtin/replace.c +1 -0
@@ -495,6 +495,7 @@ static int convert_graft_file(int force)if (!fp)return -1 ; + advice_graft_file_deprecated =0 ;while (strbuf_getline (&buf, fp) !=EOF ) {if (*buf.buf =='#' )continue ;
t/t6050-replace.sh +4 -1
@@ -461,7 +461,10 @@ test_expect_success '--convert-graft-file' ' printf "%s\n%s %s\n\n# comment\n%s\n" \ $(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \ >.git/info/grafts && - git replace --convert-graft-file && + git status 2>stderr && + test_i18ngrep "hint:.*grafts is deprecated" stderr && + git replace --convert-graft-file 2>stderr && + test_i18ngrep ! "hint:.*grafts is deprecated" stderr && test_path_is_missing .git/info/grafts && : verify that the history is now "grafted" &&
Signature · 1 line
2.20.0.rc1.379.g1dd7ef354c
Re: [PATCH] advice: don't pointlessly suggest --convert-graft-file
Quoted 8 lines · Ævar Arnfjörð Bjarmason
The advice to run 'git replace --convert-graft-file' added in
f9f99b3f7d("Deprecate support for .git/info/grafts", 2018-04-29) didn't add an exception for the 'git replace --convert-graft-file' codepath itself.As a result we'd suggest running --convert-graft-file while the user was running --convert-graft-file, which makes no sense. Before:
Quoted 9 lines
$ git replace --convert-graft-file
hint: Support for <GIT_DIR>/info/grafts is deprecated
hint: and will be removed in a future Git version.
hint:
hint: Please use "git replace --convert-graft-file"
hint: to convert the grafts into replace refs.
hint:
hint: Turn this message off by running
hint: "git config advice.graftFileDeprecated false"That's a good one. The glitch is real, the improvement is obvious, and the implementation seems to be straight-forward and sensible.
How did you find one, is the real question ;-)
Re: [PATCH] advice: don't pointlessly suggest --convert-graft-file
In reply to Ævar Arnfjörð BjarmasonHi Ævar,
Quoted 32 lines · Ævar Arnfjörð Bjarmason
The advice to run 'git replace --convert-graft-file' added in
f9f99b3f7d("Deprecate support for .git/info/grafts", 2018-04-29) didn't add an exception for the 'git replace --convert-graft-file' codepath itself.As a result we'd suggest running --convert-graft-file while the user was running --convert-graft-file, which makes no sense. Before:
$ git replace --convert-graft-file hint: Support for <GIT_DIR>/info/grafts is deprecated hint: and will be removed in a future Git version. hint: hint: Please use "git replace --convert-graft-file" hint: to convert the grafts into replace refs. hint: hint: Turn this message off by running hint: "git config advice.graftFileDeprecated false"Add a check for that case and skip printing the advice while the user is busy following our advice.
Signed-off-by Ævar Arnfjörð Bjarmason <[email protected]>--- builtin/replace.c | 1 + t/t6050-replace.sh | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-)builtin/replace.c +0
-0
builtin/replace.c static int convert_graft_file(int force) · line 501
Quoted 7 lines
builtin/replace.c +1
-0@@ -495,6 +495,7 @@ static int convert_graft_file(int force)if (!fp)return -1 ; + advice_graft_file_deprecated =0 ;while (strbuf_getline (&buf, fp) !=EOF ) {if (*buf.buf =='#' )continue ;
As long as we keep this code in the one-shot code path, it is probably okay. Otherwise we'd have to save the original value and restoring it before returning from this function.
But then, we might never move convert_graft_file() into libgit.a.
Thanks,
Dscho
Quoted 20 lines
t/t6050-replace.sh +4
-1@@ -461,7 +461,10 @@ test_expect_success '--convert-graft-file' ' printf "%s\n%s %s\n\n# comment\n%s\n" \ $(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \ >.git/info/grafts && - git replace --convert-graft-file && + git status 2>stderr && + test_i18ngrep "hint:.*grafts is deprecated" stderr && + git replace --convert-graft-file 2>stderr && + test_i18ngrep ! "hint:.*grafts is deprecated" stderr && test_path_is_missing .git/info/grafts && : verify that the history is now "grafted" &&Signature · 1 line
2.20.0.rc1.379.g1dd7ef354c
PATCH v7 9/12 filter-branch: stop suggesting to use grafts
In reply to Johannes SchindelinThe graft file is deprecated now, so let's use replace refs in the example in filter-branch's man page instead.
--- Documentation/git-filter-branch.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
Documentation/git-filter-branch.txt +1 -1
@@ -288,7 +288,7 @@ git filter-branch --parent-filter \ or even simpler: ----------------------------------------------- -echo "$commit-id $graft-id" >> .git/info/grafts +git replace --graft $commit-id $graft-id git filter-branch $graft-id..HEAD -----------------------------------------------
Signature · 1 line
2.17.0.windows.1.36.gdf4ca5fb72a
PATCH v7 10/12 technical/shallow: stop referring to grafts
In reply to Johannes SchindelinNow that grafts are deprecated, we should start to assume that readers have no idea what grafts are. So it makes more sense to make the description of the "shallow" feature stand on its own.
--- Documentation/technical/shallow.txt | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-)
Documentation/technical/shallow.txt +4 -9
@@ -8,15 +8,10 @@ repo, and therefore grafts are introduced pretending that these commits have no parents. ********************************************************* -The basic idea is to write the SHA-1s of shallow commits into -$GIT_DIR/shallow, and handle its contents like the contents -of $GIT_DIR/info/grafts (with the difference that shallow -cannot contain parent information). - -This information is stored in a new file instead of grafts, or -even the config, since the user should not touch that file -at all (even throughout development of the shallow clone, it -was never manually edited!). +$GIT_DIR/shallow lists commit object names and tells Git to +pretend as if they are root commits (e.g. "git log" traversal +stops after showing them; "git fsck" does not complain saying +the commits listed on their "parent" lines do not exist). Each line contains exactly one SHA-1. When read, a commit_graft will be constructed, which has nr_parent < 0 to make it easier
Signature · 1 line
2.17.0.windows.1.36.gdf4ca5fb72a
PATCH v7 11/12 technical/shallow: describe why shallow cannot use replace refs
In reply to Johannes SchindelinIt is tempting to do away with commit_graft altogether (in the long haul), now that grafts are deprecated.
However, the shallow feature needs a couple of things that the replace refs cannot fulfill. Let's point that out in the documentation.
--- Documentation/technical/shallow.txt | 7 +++++++ 1 file changed, 7 insertions(+)
Documentation/technical/shallow.txt +7 -0
@@ -17,6 +17,13 @@ Each line contains exactly one SHA-1. When read, a commit_graft will be constructed, which has nr_parent < 0 to make it easier to discern from user provided grafts. +Note that the shallow feature could not be changed easily to +use replace refs: a commit containing a `mergetag` is not allowed +to be replaced, not even by a root commit. Such a commit can be +made shallow, though. Also, having a `shallow` file explicitly +listing all the commits made shallow makes it a *lot* easier to +do shallow-specific things such as to deepen the history. + Since fsck-objects relies on the library to read the objects, it honours shallow commits automatically.
Signature · 1 line
2.17.0.windows.1.36.gdf4ca5fb72a
PATCH v7 12/12 Remove obsolete script to convert grafts to replace refs
In reply to Johannes SchindelinThe functionality is now implemented as git replace --convert-graft-file.
--- contrib/convert-grafts-to-replace-refs.sh | 28 ----------------------- 1 file changed, 28 deletions(-) delete mode 100755 contrib/convert-grafts-to-replace-refs.sh
contrib/convert-grafts-to-replace-refs.sh deleted +0 -28
@@ -1,28 +0,0 @@ -#!/bin/sh - -# You should execute this script in the repository where you -# want to convert grafts to replace refs. - -GRAFTS_FILE="${GIT_DIR:-.git}/info/grafts" - -. $(git --exec-path)/git-sh-setup - -test -f "$GRAFTS_FILE" || die "Could not find graft file: '$GRAFTS_FILE'" - -grep '^[^# ]' "$GRAFTS_FILE" | -while read definition -do - if test -n "$definition" - then - echo "Converting: $definition" - git replace --graft $definition || - die "Conversion failed for: $definition" - fi -done - -mv "$GRAFTS_FILE" "$GRAFTS_FILE.bak" || - die "Could not rename '$GRAFTS_FILE' to '$GRAFTS_FILE.bak'" - -echo "Success!" -echo "All the grafts in '$GRAFTS_FILE' have been converted to replace refs!" -echo "The grafts file '$GRAFTS_FILE' has been renamed: '$GRAFTS_FILE.bak'"
Signature · 1 line
2.17.0.windows.1.36.gdf4ca5fb72a