tag: keep the message file in case ref transaction fails
cover tag: keep the message file in case ref transaction fails
The ref transaction can fail after the message has been written using the editor. The ref transaction is attempted after the message file (TAG_EDITMSG) has been unlinked, so there is no backup tag message file to retry the command.[1]
This is unfortunate if someone has written more than e.g. “v1.99.4” in the editor. (I don’t know if people write long tag messages in practice.)
Hold on to the tag message file until after the ref transaction in order to preserve the backup.
† 1: On commit 91428f078b (The eighteenth batch, 2023-05-10)
§ Reproduction script
``` cd /tmp dir=$(mktemp -d) cd $dir git init git commit --allow-empty -mInit git tag release/v1 # Fails git tag -a release ```
Error message:
``` fatal: cannot lock ref 'refs/tags/release': 'refs/tags/release/v1' exists; cannot create 'refs/tags/release' ```
Better error message and behavior:
``` The tag message has been left in .git/TAG_EDITMSG fatal: cannot lock ref 'refs/tags/release': 'refs/tags/release/v1' exists; cannot create 'refs/tags/release' ```
§ Alternatives considered
My first thought was to find a way to “dry run” the ref update before opening the editor (the edge case of the ref update command succeeding the first time but not the second *real* time seemed incredibly unlikely to happen by happenstance, so I saw no reason to consider that). However that seemed like it would involve more code and conditionals, and I don’t know if the dry-run mode is even supported.
A benefit of this alternative approach would be to error out immediately instead of opening the editor. But trying to create a tag which collides with an existing “namespace” seems very unlikely to happen in practice.[2] Losing a file is much worse than being inconvenienced to retry the command, so I decided to just focus on the former problem.
Most importantly though this approach was within my ability to implement.
† 2: Just observe my “Reproduction script”: one tries to create release after
someone else made release/v1. But what is just “release”? What follows (next version) that? But why am I arguing against my change…
§ CI
https://github.com/LemmingAvalanche/git/actions/runs/4972149825
§ Carbon copy
The suggested contacts seemed too long:
``` $ ./contrib/contacts/git-contacts v2.40.1.. Junio C Hamano <> René Scharfe <> Jonathan Tan <> Jonathan Nieder <> Jeff King <> Stefan Beller <> Denton Liu <> Robert Dailey <> ```
So I added only Jeff King based on commit 3927bbe9a4 (tag: delete TAG_EDITMSG only on successful tag, 2008-12-06).
§ Patches
1. Test the happy case
2. Test the unhappy case (fails without the next patch)
3. The change
—
Cheers.
Kristoffer Haugsbakk (3):
t/t7004-tag: add regression test for existing behavior t/t7004-tag: add failing tag message file test
tag: keep the message file in case ref transaction fails
builtin/tag.c | 24 +++++++++++++++--------- t/t7004-tag.sh | 19 +++++++++++++++++++ 2 files changed, 34 insertions(+), 9 deletions(-)
Signature · 1 line
2.40.1
PATCH 1/3 t/t7004-tag: add regression test for existing behavior
The standard tag message file is unlinked if the tag is created.
---
Notes (series):
test_when_finished because this test interferes with the next one without it.
t/t7004-tag.sh | 9 +++++++++ 1 file changed, 9 insertions(+)
t/t7004-tag.sh +9 -0
@@ -2127,4 +2127,13 @@ test_expect_success 'Does --[no-]contains stop at commits? Yes!' ' test_cmp expected actual ' +test_expect_success 'If tag is created then tag message file is unlinked' ' + test_when_finished "git tag -d foo" && + write_script fakeeditor <<-\EOF && + echo Message >.git/TAG_EDITMSG + EOF + GIT_EDITOR=./fakeeditor git tag -a foo && + ! test -e .git/TAG_EDITMSG +' + test_done
Signature · 1 line
2.40.1
Re: [PATCH 1/3] t/t7004-tag: add regression test for existing behavior
Quoted 8 lines · Kristoffer Haugsbakk
The standard tag message file is unlinked if the tag is created.
Signed-off-by Kristoffer Haugsbakk <[email protected]>---
Notes (series):
test_when_finishedbecause this test interferes with the next one without it.
Use of when-finished to remove a tag makes sense.
Unlike COMMIT_EDITMSG that is documented in "git commit --help", we do not promise the users that the temporary file that is used for preparing a tag message is "$GIT_DIR/TAG_EDITMSG" in any of our documentation. That needs to be done at the same time, or before, this new test makes such a promise.
Quoted 8 lines
t/t7004-tag.sh | 9 +++++++++ 1 file changed, 9 insertions(+)t/t7004-tag.sh +0
-0
t/t7004-tag.sh test_expect_success 'Does --[no-]contains stop at commits? Yes!' ' · line 2136
Quoted 10 lines
t/t7004-tag.sh +7
-0@@ -2127,4 +2127,13 @@ test_expect_success 'Does --[no-]contains stop at commits? Yes!' ' test_cmp expected actual ' +test_expect_success 'If tag is created then tag message file is unlinked' ' + test_when_finished "git tag -d foo" && + write_script fakeeditor <<-\EOF && + echo Message >.git/TAG_EDITMSG + EOF + GIT_EDITOR=./fakeeditor git tag -a foo && + ! test -e .git/TAG_EDITMSG
I'd recommend using "test_path_is_missing" instead for better test-debugging experience.
Thanks.
PATCH 2/3 t/t7004-tag: add failing tag message file test
In reply to Kristoffer Haugsbakk--- t/t7004-tag.sh | 10 ++++++++++ 1 file changed, 10 insertions(+)
t/t7004-tag.sh +10 -0
@@ -2136,4 +2136,14 @@ test_expect_success 'If tag is created then tag message file is unlinked' ' ! test -e .git/TAG_EDITMSG ' +test_expect_success 'If tag cannot be created then tag message file is not unlinked' ' + test_when_finished "git tag -d foo/bar" && + write_script fakeeditor <<-\EOF && + echo Message >.git/TAG_EDITMSG + EOF + git tag foo/bar && + ! GIT_EDITOR=./fakeeditor git tag -a foo && + test -e .git/TAG_EDITMSG +' + test_done
Signature · 1 line
2.40.1
Re: [PATCH 2/3] t/t7004-tag: add failing tag message file test
Quoted 4 lines · Kristoffer Haugsbakk
Signed-off-by Kristoffer Haugsbakk <[email protected]>--- t/t7004-tag.sh | 10 ++++++++++ 1 file changed, 10 insertions(+)
Does this document the current behaviour, i.e. before applying the patch [3/3]? Or is this a new test designed to fail until [3/3] is applied?
If the latter, please don't [*].
Instead, combine this with [3/3] and make it [2/2] that changes the behaviour of the command and protects the new behaviour from future breakages in a single step. Those who are truly curious to see why the code change in it is necessary can apply the "code change plus new test" patch, and then temporarily revert only the code change part in their working tree to see how the test breaks without the code change.
Quoted 5 lines
t/t7004-tag.sh +0
-0
t/t7004-tag.sh test_expect_success 'If tag is created then tag message file is unlinked' ' · line 2145
Quoted 10 lines
t/t7004-tag.sh +7
-0@@ -2136,4 +2136,14 @@ test_expect_success 'If tag is created then tag message file is unlinked' ' ! test -e .git/TAG_EDITMSG ' +test_expect_success 'If tag cannot be created then tag message file is not unlinked' ' + test_when_finished "git tag -d foo/bar" && + write_script fakeeditor <<-\EOF && + echo Message >.git/TAG_EDITMSG + EOF + git tag foo/bar && + ! GIT_EDITOR=./fakeeditor git tag -a foo &&
Imitate other tests that expect a controlled failure from our command, and write something like
test_must_fail env GIT_EDITOR=./fakeeditor git tag -a foo
so that a segfaulting "git tag" will not count as "failing as expected".
t/t7004-tag.sh test_expect_success 'If tag is created then tag message file is unlinked' ' · line 2146
Quoted 1 line
t/t7004-tag.sh +1
-0+ test -e .git/TAG_EDITMSG
Use "test_path_exists" instead.
Thanks.
[Footnote]
* Introducing this as a failing test "test_expect_failure" in [2/3] and then flip it to "test_expect_success" in [3/3] would make tests pass without applying [3/3], but generally it is not a recommended practice.
This is because such a [3/3] patch would only show the line with the test title and the behaviour of the test will not be shown in the diff context. That hurts reviewability.
Re: [PATCH 2/3] t/t7004-tag: add failing tag message file test
In reply to Kristoffer HaugsbakkQuoted 4 lines · Kristoffer Haugsbakk
Signed-off-by Kristoffer Haugsbakk <[email protected]>--- t/t7004-tag.sh | 10 ++++++++++ 1 file changed, 10 insertions(+)
Does this document the current behaviour, i.e. before applying the patch [3/3]? Or is this a new test designed to fail until [3/3] is applied?
If the latter, please don't [*].
Instead, combine this with [3/3] and make it [2/2] that changes the behaviour of the command and protects the new behaviour from future breakages in a single step. Those who are truly curious to see why the code change in it is necessary can apply the "code change plus new test" patch, and then temporarily revert only the code change part in their working tree to see how the test breaks without the code change.
Quoted 5 lines
t/t7004-tag.sh +0
-0
t/t7004-tag.sh test_expect_success 'If tag is created then tag message file is unlinked' ' · line 2145
Quoted 10 lines
t/t7004-tag.sh +7
-0@@ -2136,4 +2136,14 @@ test_expect_success 'If tag is created then tag message file is unlinked' ' ! test -e .git/TAG_EDITMSG ' +test_expect_success 'If tag cannot be created then tag message file is not unlinked' ' + test_when_finished "git tag -d foo/bar" && + write_script fakeeditor <<-\EOF && + echo Message >.git/TAG_EDITMSG + EOF + git tag foo/bar && + ! GIT_EDITOR=./fakeeditor git tag -a foo &&
Imitate other tests that expect a controlled failure from our command, and write something like
test_must_fail env GIT_EDITOR=./fakeeditor git tag -a foo
so that a segfaulting "git tag" will not count as "failing as expected".
t/t7004-tag.sh test_expect_success 'If tag is created then tag message file is unlinked' ' · line 2146
Quoted 1 line
t/t7004-tag.sh +1
-0+ test -e .git/TAG_EDITMSG
Use "test_path_exists" instead.
Thanks.
[Footnote]
* Introducing this as a failing test "test_expect_failure" in [2/3] and then flip it to "test_expect_success" in [3/3] would make tests pass without applying [3/3], but generally it is not a recommended practice.
This is because such a [3/3] patch would only show the line with the test title and the behaviour of the test will not be shown in the diff context. That hurts reviewability.
Re: [PATCH 2/3] t/t7004-tag: add failing tag message file test
Quoted 5 lines · Junio C Hamano
Does this document the current behaviour, i.e. before applying the patch [3/3]? Or is this a new test designed to fail until [3/3] is applied?
If the latter, please don't [*].
Yep, I was going for the latter. I’ll combine [2/3] and [3/3].
Quoted 7 lines
Imitate other tests that expect a controlled failure from our command, and write something like
test_must_failenv GIT_EDITOR=./fakeeditor git tag -a fooso that a segfaulting "git tag" will not count as "failing as expected".
Ah, I was wondering how to use test_must_fail on a command with an environment variable. Thanks.
Signature · 1 line
Kristoffer Haugsbakk
PATCH 3/3 tag: keep the message file in case ref transaction fails
In reply to Kristoffer HaugsbakkThe ref transaction can fail after the user has written their tag message. In particular, if there exists a tag foo/bar and git tag -a foo is said then the command will only fail once it tries to write refs/tags/foo, which is after one has closed the editor.
Hold on to the message file for a little longer so that it is not unlinked before the fatal error occurs.
---
Notes (series):
I tried to maintain the proper formatting by using clang-format via Emacs on the affected lines.
builtin/tag.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-)
builtin/tag.c +15 -9
@@ -266,11 +266,10 @@ static const char message_advice_nested_tag[] =static void create_tag (const struct object_id *object,const char *object_ref,const char *tag,struct strbuf *buf,struct create_tag_options *opt, -struct object_id *prev,struct object_id *result) +struct object_id *prev,struct object_id *result,char *path) {enum object_type type;struct strbuf header =STRBUF_INIT ; -char *path =NULL ; type =oid_object_info (the_repository, object,NULL );if (type <=OBJ_NONE )
@@ -294,7 +293,6 @@ static void create_tag(const struct object_id *object, const char *object_ref,int fd;/* write the template message before editing: */ - path =git_pathdup ("TAG_EDITMSG" ); fd =xopen (path,O_CREAT |O_TRUNC |O_WRONLY ,0600 );if (opt->message_given) {
@@ -336,10 +334,6 @@ static void create_tag(const struct object_id *object, const char *object_ref, path);exit (128 ); } -if (path) { -unlink_or_warn (path); -free (path); - } }static void create_reflog_msg (const struct object_id *oid,struct strbuf *sb)
@@ -487,6 +481,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) };int ret =0 ;const char *only_in_list =NULL ; +char *path =NULL ;setup_ref_filter_porcelain_msg ();
@@ -621,7 +616,9 @@ int cmd_tag(int argc, const char **argv, const char *prefix)if (create_tag_object) {if (force_sign_annotate && !annotate) opt.sign =1 ; -create_tag (&object, object_ref, tag, &buf, &opt, &prev, &object); + path =git_pathdup ("TAG_EDITMSG" ); +create_tag (&object, object_ref, tag, &buf, &opt, &prev, &object, + path); } transaction =ref_transaction_begin (&err);
@@ -629,8 +626,17 @@ int cmd_tag(int argc, const char **argv, const char *prefix) ref_transaction_update(transaction, ref.buf, &object, &prev, create_reflog ? REF_FORCE_CREATE_REFLOG : 0, reflog_msg.buf, &err) || - ref_transaction_commit(transaction, &err)) + ref_transaction_commit(transaction, &err)) { +if (path) +fprintf (stderr, +_ ("The tag message has been left in %s\n" ), + path);die ("%s" , err.buf); + } +if (path) { +unlink_or_warn (path); +free (path); + }ref_transaction_free (transaction);if (force && !is_null_oid (&prev) && !oideq (&prev, &object)) printf(_("Updated tag '%s' (was %s)\n"), tag,
Signature · 1 line
2.40.1
Quoted 10 lines · Kristoffer Haugsbakk
The ref transaction can fail after the user has written their tag message. In particular, if there exists a tag
foo/barandgit tag -a foois said then the command will only fail once it tries to writerefs/tags/foo, which is after one has closed the editor.Hold on to the message file for a little longer so that it is not unlinked before the fatal error occurs.
Signed-off-by Kristoffer Haugsbakk <[email protected]>---
As I said in my review of [1/3], TAG_EDITMSG should be documented just like COMMIT_EDITMSG is documented.
Other than that, looking OK to me (I didn't read the patch very carefully so I may have missed leaking and/or use after free, though).
Thanks for working on this.
v2 cover tag: keep the message file in case ref transaction fails
In reply to Kristoffer Haugsbakk§ Introduction (v2)
The following material is the same compared to v1 up to but not including “Changes from the previous round (v1)”
(The CI link is also new.)
Cheers.
―
The ref transaction can fail after the message has been written using the editor. The ref transaction is attempted after the message file (TAG_EDITMSG) has been unlinked, so there is no backup tag message file to retry the command.[1]
This is unfortunate if someone has written more than e.g. “v1.99.4” in the editor. (I don’t know if people write long tag messages in practice.)
Hold on to the tag message file until after the ref transaction in order to preserve the backup.
† 1: On commit 91428f078b (The eighteenth batch, 2023-05-10)
§ Reproduction script
``` cd /tmp dir=$(mktemp -d) cd $dir git init git commit --allow-empty -mInit git tag release/v1 # Fails git tag -a release ```
Error message:
``` fatal: cannot lock ref 'refs/tags/release': 'refs/tags/release/v1' exists; cannot create 'refs/tags/release' ```
Better error message and behavior:
``` The tag message has been left in .git/TAG_EDITMSG fatal: cannot lock ref 'refs/tags/release': 'refs/tags/release/v1' exists; cannot create 'refs/tags/release' ```
§ Alternatives considered
My first thought was to find a way to “dry run” the ref update before opening the editor (the edge case of the ref update command succeeding the first time but not the second *real* time seemed incredibly unlikely to happen by happenstance, so I saw no reason to consider that). However that seemed like it would involve more code and conditionals, and I don’t know if the dry-run mode is even supported.
A benefit of this alternative approach would be to error out immediately instead of opening the editor. But trying to create a tag which collides with an existing “namespace” seems very unlikely to happen in practice.[2] Losing a file is much worse than being inconvenienced to retry the command, so I decided to just focus on the former problem.
Most importantly though this approach was within my ability to implement.
† 2: Just observe my “Reproduction script”: one tries to create release after
someone else made release/v1. But what is just “release”? What follows (next version) that? But why am I arguing against my change…
§ CI
https://github.com/LemmingAvalanche/git/actions/runs/4984100467
§ Changes compared to the previous round
• Document TAG_EDITMSG
• Improve test structure
• Combine/squash the regression test for the change and the actual change
• In the previous round the regression test commit would fail the test suite since it’s a test_expect_success and it comes before the actual change
§ Patches (compared to previous round)
1. (new) Document TAG_EDITMSG
• So that we match COMMIT_EDITMSG
2. (was [1/3]) Test successful tag creation
• Tweak commit message subject
• Also remove .git/TAG_EDITMSG in test_when_finished
3. (was [2–3/3]) The main change plus a regression test
• Tweak commit message and reflow paragraphs
• Not a change but add a question for the reviewers (see the “Note”)
Kristoffer Haugsbakk (3):
doc: tag: document TAG_EDITMSG
t/t7004-tag: add regression test for successful tag creation
tag: keep the message file in case ref transaction fails
Documentation/git-tag.txt | 10 ++++++++++ builtin/tag.c | 24 +++++++++++++++--------- t/t7004-tag.sh | 19 +++++++++++++++++++ 3 files changed, 44 insertions(+), 9 deletions(-)
Range-diff against v1:
-: ---------- > 1: 0e0e592853 doc: tag: document `TAG_EDITMSG` 1: 87b709d856 ! 2: aabeb4568e t/t7004-tag: add regression test for existing behavior
@@ Metadata
Author: Kristoffer Haugsbakk <[email protected]>
## Commit message ##
- t/t7004-tag: add regression test for existing behavior
+ t/t7004-tag: add regression test for successful tag creation
The standard tag message file is unlinked if the tag is created.
@@ t/t7004-tag.sh: test_expect_success 'Does --[no-]contains stop at commits? Yes!'
+ echo Message >.git/TAG_EDITMSG
+ EOF
+ GIT_EDITOR=./fakeeditor git tag -a foo &&
-+ ! test -e .git/TAG_EDITMSG
++ ! test_path_exists .git/TAG_EDITMSG
+'
+
test_done
2: 1f24aa43f7 < -: ---------- t/t7004-tag: add failing tag message file test 3: 999af290af ! 3: e67b6416b7 tag: keep the message file in case ref transaction fails
@@ Metadata
## Commit message ##
tag: keep the message file in case ref transaction fails
- The ref transaction can fail after the user has written their tag message. In
- particular, if there exists a tag foo/bar and git tag -a foo is said then
- the command will only fail once it tries to write refs/tags/foo, which is
- after one has closed the editor.
+ The ref transaction can fail after the user has written their tag
+ message. In particular, if there exists a tag foo/bar and `git tag -a
+ foo` is said then the command will only fail once it tries to write
+ refs/tags/foo, which is after the file has been unlinked.
- Hold on to the message file for a little longer so that it is not unlinked
- before the fatal error occurs.
+ Hold on to the message file for a little longer so that it is not
+ unlinked before the fatal error occurs.
+ Cc: Jeff King <[email protected]>
## Notes (series) ##
- I tried to maintain the proper formatting by using clang-format via Emacs on
- the affected lines.
+ I duplicated this message (this isn’t obvious in the diff):
+
+ fprintf(stderr,
+ _("The tag message has been left in %s\n"),
+ path);
+
+ Should this be factored into a static function instead?
+
+ § Changes from previous round
+
+ Squash (combine) the update to tag.c with the test so that the fix and
+ the regression test is added in one step.
+
+ This makes more sense than what I was going for since the test suite
+ would fail on patch 2/3 of the previous round.
+
+ Link: https://lore.kernel.org/git/[email protected]/T/#u
## builtin/tag.c ##
@@ builtin/tag.c: static const char message_advice_nested_tag[] =
@@ builtin/tag.c: int cmd_tag(int argc, const char **argv, const char *prefix)
ref_transaction_free(transaction);
if (force && !is_null_oid(&prev) && !oideq(&prev, &object))
printf(_("Updated tag '%s' (was %s)\n"), tag,
+
+ ## t/t7004-tag.sh ##
+@@ t/t7004-tag.sh: test_expect_success 'If tag is created then tag message file is unlinked' '
+ ! test_path_exists .git/TAG_EDITMSG
+ '
+
++test_expect_success 'If tag cannot be created then tag message file is not unlinked' '
++ test_when_finished "git tag -d foo/bar && rm .git/TAG_EDITMSG" &&
++ write_script fakeeditor <<-\EOF &&
++ echo Message >.git/TAG_EDITMSG
++ EOF
++ git tag foo/bar &&
++ test_must_fail env GIT_EDITOR=./fakeeditor git tag -a foo &&
++ test_path_exists .git/TAG_EDITMSG
++'
++
+ test_done
Signature · 1 line
2.40.1
PATCH v2 1/3 doc: tag: document `TAG_EDITMSG`
Document TAG_EDITMSG which we have told the user about on unsuccessful command invocations since commit 3927bbe9a4 (tag: delete TAG_EDITMSG only on successful tag, 2008-12-06).
Introduce this documentation since we are going to add tests for the lifetime of this file in the case of command failure and success.
Use the documentation for COMMIT_EDITMSG from git-commit.txt as a template since these two files share the same purpose.[1]
† 1: from commit 3927bbe9a4:
“ This matches the behavior of COMMIT_EDITMSG, which stays around
in case of error.
---
Notes (series):
• I changed (from COMMIT_EDITMSG) “will be overwritten” to “may” since I don’t see the point in giving a guarantee
• Unsure if I was going to put this before or after “NOTES” as there seems to be no precedence
Suggested by Junio: https://lore.kernel.org/git/[email protected]/T/#u
Documentation/git-tag.txt | 10 ++++++++++ 1 file changed, 10 insertions(+)
Documentation/git-tag.txt +10 -0
@@ -377,6 +377,16 @@ $ GIT_COMMITTER_DATE="2006-10-02 10:31" git tag -s v1.0.1 include::date-formats.txt[] +FILES +----- + +`$GIT_DIR/TAG_EDITMSG`:: + This file contains the message of an in-progress annotated + tag. If `git tag` exits due to an error before creating an + annotated tag then the tag message that has been provided by the + user in an editor session will be available in this file, but + may be overwritten by the next invocation of `git tag`. + NOTES -----
Signature · 1 line
2.40.1
Re: [PATCH v2 1/3] doc: tag: document `TAG_EDITMSG`
Quoted 12 lines · Kristoffer Haugsbakk
Document
TAG_EDITMSGwhich we have told the user about on unsuccessful command invocations since commit3927bbe9a4(tag: deleteTAG_EDITMSGonly on successful tag, 2008-12-06).Introduce this documentation since we are going to add tests for the lifetime of this file in the case of command failure and success.
Use the documentation for
COMMIT_EDITMSGfromgit-commit.txtas a template since these two files share the same purpose.[1]† 1: from commit
3927bbe9a4:
Quoted 7 lines
“ This matches the behavior of
COMMIT_EDITMSG, which stays around
in case of error.Suggested-by Junio C Hamano <[email protected]>Cc Jeff King <[email protected]>Signed-off-by Kristoffer Haugsbakk <[email protected]>---
Excellent.
PATCH v2 2/3 t/t7004-tag: add regression test for successful tag creation
In reply to Kristoffer HaugsbakkThe standard tag message file is unlinked if the tag is created.
---
Notes (series):
test_when_finished because this test interferes with the next one without it.
t/t7004-tag.sh | 9 +++++++++ 1 file changed, 9 insertions(+)
t/t7004-tag.sh +9 -0
@@ -2127,4 +2127,13 @@ test_expect_success 'Does --[no-]contains stop at commits? Yes!' ' test_cmp expected actual ' +test_expect_success 'If tag is created then tag message file is unlinked' ' + test_when_finished "git tag -d foo" && + write_script fakeeditor <<-\EOF && + echo Message >.git/TAG_EDITMSG + EOF + GIT_EDITOR=./fakeeditor git tag -a foo && + ! test_path_exists .git/TAG_EDITMSG +' + test_done
Signature · 1 line
2.40.1
PATCH v2 3/3 tag: keep the message file in case ref transaction fails
In reply to Kristoffer HaugsbakkThe ref transaction can fail after the user has written their tag message. In particular, if there exists a tag foo/bar and git tag -a foo is said then the command will only fail once it tries to write refs/tags/foo, which is after the file has been unlinked.
Hold on to the message file for a little longer so that it is not unlinked before the fatal error occurs.
---
Notes (series):
I duplicated this message (this isn’t obvious in the diff):
fprintf(stderr,
_("The tag message has been left in %s\n"), path);
Should this be factored into a static function instead?
§ Changes from previous round
Squash (combine) the update to tag.c with the test so that the fix and the regression test is added in one step.
This makes more sense than what I was going for since the test suite would fail on patch 2/3 of the previous round.
builtin/tag.c | 24 +++++++++++++++--------- t/t7004-tag.sh | 10 ++++++++++ 2 files changed, 25 insertions(+), 9 deletions(-)
builtin/tag.c +15 -9
@@ -266,11 +266,10 @@ static const char message_advice_nested_tag[] =static void create_tag (const struct object_id *object,const char *object_ref,const char *tag,struct strbuf *buf,struct create_tag_options *opt, -struct object_id *prev,struct object_id *result) +struct object_id *prev,struct object_id *result,char *path) {enum object_type type;struct strbuf header =STRBUF_INIT ; -char *path =NULL ; type =oid_object_info (the_repository, object,NULL );if (type <=OBJ_NONE )
@@ -294,7 +293,6 @@ static void create_tag(const struct object_id *object, const char *object_ref,int fd;/* write the template message before editing: */ - path =git_pathdup ("TAG_EDITMSG" ); fd =xopen (path,O_CREAT |O_TRUNC |O_WRONLY ,0600 );if (opt->message_given) {
@@ -336,10 +334,6 @@ static void create_tag(const struct object_id *object, const char *object_ref, path);exit (128 ); } -if (path) { -unlink_or_warn (path); -free (path); - } }static void create_reflog_msg (const struct object_id *oid,struct strbuf *sb)
@@ -487,6 +481,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) };int ret =0 ;const char *only_in_list =NULL ; +char *path =NULL ;setup_ref_filter_porcelain_msg ();
@@ -621,7 +616,9 @@ int cmd_tag(int argc, const char **argv, const char *prefix)if (create_tag_object) {if (force_sign_annotate && !annotate) opt.sign =1 ; -create_tag (&object, object_ref, tag, &buf, &opt, &prev, &object); + path =git_pathdup ("TAG_EDITMSG" ); +create_tag (&object, object_ref, tag, &buf, &opt, &prev, &object, + path); } transaction =ref_transaction_begin (&err);
@@ -629,8 +626,17 @@ int cmd_tag(int argc, const char **argv, const char *prefix) ref_transaction_update(transaction, ref.buf, &object, &prev, create_reflog ? REF_FORCE_CREATE_REFLOG : 0, reflog_msg.buf, &err) || - ref_transaction_commit(transaction, &err)) + ref_transaction_commit(transaction, &err)) { +if (path) +fprintf (stderr, +_ ("The tag message has been left in %s\n" ), + path);die ("%s" , err.buf); + } +if (path) { +unlink_or_warn (path); +free (path); + }ref_transaction_free (transaction);if (force && !is_null_oid (&prev) && !oideq (&prev, &object)) printf(_("Updated tag '%s' (was %s)\n"), tag,
t/t7004-tag.sh +10 -0
@@ -2136,4 +2136,14 @@ test_expect_success 'If tag is created then tag message file is unlinked' ' ! test_path_exists .git/TAG_EDITMSG ' +test_expect_success 'If tag cannot be created then tag message file is not unlinked' ' + test_when_finished "git tag -d foo/bar && rm .git/TAG_EDITMSG" && + write_script fakeeditor <<-\EOF && + echo Message >.git/TAG_EDITMSG + EOF + git tag foo/bar && + test_must_fail env GIT_EDITOR=./fakeeditor git tag -a foo && + test_path_exists .git/TAG_EDITMSG +' + test_done
Signature · 1 line
2.40.1
Quoted 3 lines · Kristoffer Haugsbakk
+ GIT_EDITOR=./fakeeditor git tag -a foo &&
-+ ! test -e .git/TAG_EDITMSG
++ ! test_path_exists .git/TAG_EDITMSGThis is not quite right. test_path_exists is loud when its expectation that the path _exists_ is not met, i.e.
test_path_exists () {
test "$#" -ne 1 && BUG "1 param"
if ! test -e "$1"
then
echo "Path $1 doesn't exist"
false
fi
}But this test expects that .git/TAG_EDITMSG to be missing. When the test is run with "-v" to make the output from this 'echo' visible, we will keep getting the complaint when the test is happy, which is not quite what we want.
What you want to use is test_path_is_missing, without "!".
Quoted 10 lines
## Notes (series) ##
- I tried to maintain the proper formatting by usingclang-formatvia Emacs on
- the affected lines.
+ I duplicated this message (this isn’t obvious in the diff):
+
+ fprintf(stderr,
+_("The tag message has been left in %s\n"),
+ path);
+
+ Should this be factored into a static function instead?
When the third copy is made, we would definitely insist avoiding copies, but until then, I am indifferent. Others may have different opinions, though.
v3 cover tag: keep the message file in case ref transaction fails
In reply to Kristoffer Haugsbakk§ Introduction (v3)
The first part of this material is the same as v1 and v2; skip to “Based on” and onwards for the new things.
Cheers.
―
The ref transaction can fail after the message has been written using the editor. The ref transaction is attempted after the message file (TAG_EDITMSG) has been unlinked, so there is no backup tag message file to retry the command.[1]
This is unfortunate if someone has written more than e.g. “v1.99.4” in the editor. (I don’t know if people write long tag messages in practice.)
Hold on to the tag message file until after the ref transaction in order to preserve the backup.
† 1: On commit 91428f078b (The eighteenth batch, 2023-05-10)
§ Reproduction script
``` cd /tmp dir=$(mktemp -d) cd $dir git init git commit --allow-empty -mInit git tag release/v1 # Fails git tag -a release ```
Error message:
``` fatal: cannot lock ref 'refs/tags/release': 'refs/tags/release/v1' exists; cannot create 'refs/tags/release' ```
Better error message and behavior:
``` The tag message has been left in .git/TAG_EDITMSG fatal: cannot lock ref 'refs/tags/release': 'refs/tags/release/v1' exists; cannot create 'refs/tags/release' ```
§ Alternatives considered
My first thought was to find a way to “dry run” the ref update before opening the editor (the edge case of the ref update command succeeding the first time but not the second *real* time seemed incredibly unlikely to happen by happenstance, so I saw no reason to consider that). However that seemed like it would involve more code and conditionals, and I don’t know if the dry-run mode is even supported.
A benefit of this alternative approach would be to error out immediately instead of opening the editor. But trying to create a tag which collides with an existing “namespace” seems very unlikely to happen in practice.[2] Losing a file is much worse than being inconvenienced to retry the command, so I decided to just focus on the former problem.
Most importantly though this approach was within my ability to implement.
† 2: Just observe my “Reproduction script”: one tries to create release after
someone else made release/v1. But what is just “release”? What follows (next version) that? But why am I arguing against my change…
§ Based on (base commit)
v2.40.1
§ Changes compared to the previous round
• Replace ! test_path_exists with test_path_is_missing
Other:
• Drop “Cc” trailers (but keep Cc list)
§ Patches
Same number and order as last round.
1. Document TAG_EDITMSG
2. Test successful tag creation
• New: Use test_path_is_missing
3. The main change plus a regression test
Kristoffer Haugsbakk (3):
doc: tag: document TAG_EDITMSG
t/t7004-tag: add regression test for successful tag creation
tag: keep the message file in case ref transaction fails
Documentation/git-tag.txt | 10 ++++++++++ builtin/tag.c | 24 +++++++++++++++--------- t/t7004-tag.sh | 19 +++++++++++++++++++ 3 files changed, 44 insertions(+), 9 deletions(-)
Range-diff against v2:
1: 0e0e592853d ! 1: 882008e32a4 doc: tag: document TAG_EDITMSG
@@ Commit message
in case of error.
- Cc: Jeff King <[email protected]>
2: aabeb4568ee ! 2: d1490b8f0b7 t/t7004-tag: add regression test for successful tag creation
@@ Commit message
Signed-off-by: Kristoffer Haugsbakk <[email protected]>
## t/t7004-tag.sh ##
@@ t/t7004-tag.sh: test_expect_success 'Does --[no-]contains stop at commits? Yes!' '
test_cmp expected actual
@@ t/t7004-tag.sh: test_expect_success 'Does --[no-]contains stop at commits? Yes!'
+ echo Message >.git/TAG_EDITMSG
+ EOF
+ GIT_EDITOR=./fakeeditor git tag -a foo &&
-+ ! test_path_exists .git/TAG_EDITMSG
++ test_path_is_missing .git/TAG_EDITMSG
+'
+
test_done3: e67b6416b7e ! 3: 81bca0673d8 tag: keep the message file in case ref transaction fails
@@ Commit message
Hold on to the message file for a little longer so that it is not unlinked before the fatal error occurs.
- Cc: Jeff King <[email protected]>
@@ builtin/tag.c: int cmd_tag(int argc, const char **argv, const char *prefix)
## t/t7004-tag.sh ##
@@ t/t7004-tag.sh: test_expect_success 'If tag is created then tag message file is unlinked' '
- ! test_path_exists .git/TAG_EDITMSG
+ test_path_is_missing .git/TAG_EDITMSG
'
+test_expect_success 'If tag cannot be created then tag message file is not unlinked' '
Signature · 1 line
2.40.1
PATCH v3 1/3 doc: tag: document `TAG_EDITMSG`
Document TAG_EDITMSG which we have told the user about on unsuccessful command invocations since commit 3927bbe9a4 (tag: delete TAG_EDITMSG only on successful tag, 2008-12-06).
Introduce this documentation since we are going to add tests for the lifetime of this file in the case of command failure and success.
Use the documentation for COMMIT_EDITMSG from git-commit.txt as a template since these two files share the same purpose.[1]
† 1: from commit 3927bbe9a4:
“ This matches the behavior of COMMIT_EDITMSG, which stays around
in case of error.
---
Notes (series):
§ From v2
• I changed (from COMMIT_EDITMSG) “will be overwritten” to “may” since I don’t see the point in giving a guarantee
• Unsure if I was going to put this before or after “NOTES” as there seems to be no precedence
Suggested in: https://lore.kernel.org/git/[email protected]/T/#u
Documentation/git-tag.txt | 10 ++++++++++ 1 file changed, 10 insertions(+)
Documentation/git-tag.txt +10 -0
@@ -377,6 +377,16 @@ $ GIT_COMMITTER_DATE="2006-10-02 10:31" git tag -s v1.0.1 include::date-formats.txt[] +FILES +----- + +`$GIT_DIR/TAG_EDITMSG`:: + This file contains the message of an in-progress annotated + tag. If `git tag` exits due to an error before creating an + annotated tag then the tag message that has been provided by the + user in an editor session will be available in this file, but + may be overwritten by the next invocation of `git tag`. + NOTES -----
Signature · 1 line
2.40.1
PATCH v3 2/3 t/t7004-tag: add regression test for successful tag creation
In reply to Kristoffer HaugsbakkThe standard tag message file is unlinked if the tag is created.
---
Notes (series):
See: https://lore.kernel.org/git/[email protected]/T/#m594378bc6fe2545e638ce2d5f9c11861d4e33082
> What you want to use is test_path_is_missing, without "!".
t/t7004-tag.sh | 9 +++++++++ 1 file changed, 9 insertions(+)
t/t7004-tag.sh +9 -0
@@ -2127,4 +2127,13 @@ test_expect_success 'Does --[no-]contains stop at commits? Yes!' ' test_cmp expected actual ' +test_expect_success 'If tag is created then tag message file is unlinked' ' + test_when_finished "git tag -d foo" && + write_script fakeeditor <<-\EOF && + echo Message >.git/TAG_EDITMSG + EOF + GIT_EDITOR=./fakeeditor git tag -a foo && + test_path_is_missing .git/TAG_EDITMSG +' + test_done
Signature · 1 line
2.40.1
PATCH v3 3/3 tag: keep the message file in case ref transaction fails
In reply to Kristoffer HaugsbakkThe ref transaction can fail after the user has written their tag message. In particular, if there exists a tag foo/bar and git tag -a foo is said then the command will only fail once it tries to write refs/tags/foo, which is after the file has been unlinked.
Hold on to the message file for a little longer so that it is not unlinked before the fatal error occurs.
---
Notes (series):
§ From v2
(Noting for other reviewers (after v2):)
I duplicated this message (this isn’t obvious in the diff):
fprintf(stderr,
_("The tag message has been left in %s\n"), path);
builtin/tag.c | 24 +++++++++++++++--------- t/t7004-tag.sh | 10 ++++++++++ 2 files changed, 25 insertions(+), 9 deletions(-)
builtin/tag.c +15 -9
@@ -266,11 +266,10 @@ static const char message_advice_nested_tag[] =static void create_tag (const struct object_id *object,const char *object_ref,const char *tag,struct strbuf *buf,struct create_tag_options *opt, -struct object_id *prev,struct object_id *result) +struct object_id *prev,struct object_id *result,char *path) {enum object_type type;struct strbuf header =STRBUF_INIT ; -char *path =NULL ; type =oid_object_info (the_repository, object,NULL );if (type <=OBJ_NONE )
@@ -294,7 +293,6 @@ static void create_tag(const struct object_id *object, const char *object_ref,int fd;/* write the template message before editing: */ - path =git_pathdup ("TAG_EDITMSG" ); fd =xopen (path,O_CREAT |O_TRUNC |O_WRONLY ,0600 );if (opt->message_given) {
@@ -336,10 +334,6 @@ static void create_tag(const struct object_id *object, const char *object_ref, path);exit (128 ); } -if (path) { -unlink_or_warn (path); -free (path); - } }static void create_reflog_msg (const struct object_id *oid,struct strbuf *sb)
@@ -487,6 +481,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) };int ret =0 ;const char *only_in_list =NULL ; +char *path =NULL ;setup_ref_filter_porcelain_msg ();
@@ -621,7 +616,9 @@ int cmd_tag(int argc, const char **argv, const char *prefix)if (create_tag_object) {if (force_sign_annotate && !annotate) opt.sign =1 ; -create_tag (&object, object_ref, tag, &buf, &opt, &prev, &object); + path =git_pathdup ("TAG_EDITMSG" ); +create_tag (&object, object_ref, tag, &buf, &opt, &prev, &object, + path); } transaction =ref_transaction_begin (&err);
@@ -629,8 +626,17 @@ int cmd_tag(int argc, const char **argv, const char *prefix) ref_transaction_update(transaction, ref.buf, &object, &prev, create_reflog ? REF_FORCE_CREATE_REFLOG : 0, reflog_msg.buf, &err) || - ref_transaction_commit(transaction, &err)) + ref_transaction_commit(transaction, &err)) { +if (path) +fprintf (stderr, +_ ("The tag message has been left in %s\n" ), + path);die ("%s" , err.buf); + } +if (path) { +unlink_or_warn (path); +free (path); + }ref_transaction_free (transaction);if (force && !is_null_oid (&prev) && !oideq (&prev, &object)) printf(_("Updated tag '%s' (was %s)\n"), tag,
t/t7004-tag.sh +10 -0
@@ -2136,4 +2136,14 @@ test_expect_success 'If tag is created then tag message file is unlinked' ' test_path_is_missing .git/TAG_EDITMSG ' +test_expect_success 'If tag cannot be created then tag message file is not unlinked' ' + test_when_finished "git tag -d foo/bar && rm .git/TAG_EDITMSG" && + write_script fakeeditor <<-\EOF && + echo Message >.git/TAG_EDITMSG + EOF + git tag foo/bar && + test_must_fail env GIT_EDITOR=./fakeeditor git tag -a foo && + test_path_exists .git/TAG_EDITMSG +' + test_done
Signature · 1 line
2.40.1
Quoted 6 lines · Kristoffer Haugsbakk
§ Introduction (v3)
The first part of this material is the same as v1 and v2; skip to “Based on” and onwards for the new things.
Cheers.
Thanks, will queue. Let's merge it down to 'next' soonish.
Quoted 11 lines · Kristoffer Haugsbakk
The ref transaction can fail after the message has been written using the editor. The ref transaction is attempted after the message file (
TAG_EDITMSG) has been unlinked, so there is no backup tag message file to retry the command.[1]This is unfortunate if someone has written more than e.g. “v1.99.4” in the editor. (I don’t know if people write long tag messages in practice.) [...]
So I added only Jeff King based on commit
3927bbe9a4(tag: deleteTAG_EDITMSGonly on successful tag, 2008-12-06).
It has definitely been a while since that one. :) I agree what you are doing here is in the same spirit as that commit, though there is one small difference. 3927bbe9a4 was about saving the message file if we failed to create the object, since otherwise there is no record of it.
Your patch is about saving it from a failed ref update. But in that case we _do_ have the data saved in the object. You can find it with git-fsck:
[after running something like your test script]
$ git fsck Checking object directories: 100% (256/256), done. dangling tag b9bde516952a863690027a611575a79d4c786b8d
$ git cat-file tag b9bde516952a863690027a611575a79d4c786b8d object 8b1e708e32ae3af17825b3c713a3ab317824b932 type commit tag foo/bar tagger Jeff King <[email protected]> 1684314980 -0400
my message
That said, I'm willing to believe that most users wouldn't figure this out on their own, and saving TAG_EDITMSG might be more friendly. But one other alternative might be to mention the hash of that tag object, and possibly give advice on recovering it.
It's too bad we do not have "git tag -c" to match "git commit -c", which would allow us to say something like:
A tag object was created, but we failed to update the ref. After correcting any errors, you can recover the original tag message with:
git tag -c $oid [other options]
(where we'd replace $oid with the hash of the created tag object). The best alternatives I could come up with were:
# this is kind of obscure advice to give somebody, plus
# it makes a weird tag if you originally tried to tag "foo/bar"
# but then later switch to some other name. The "tag" field
# in the object won't match the ref.
git update-ref $ref $oid # this saves just the message but is horribly complicated
git cat-file tag $oid | sed '1,/^$/d' |
git tag -F -I dunno. There is a certain elegance to telling the user about what progress we _did_ make, but if there isn't an easy way to turn that into a retry of their command, it may not be all that useful.
So I'm not really opposed to your patch, but mostly just brainstorming alternatives.
I gave v3 a brief look, and it seems OK to me. It might be nice to factor out the duplicated advice message, but since we cannot share any of the other lines (one spot calls exit() and the other uses die()), I am OK with it as-is.
-Peff
Quoted 4 lines · Jeff King
That said, I'm willing to believe that most users wouldn't figure this out on their own, and saving
TAG_EDITMSGmight be more friendly. But one other alternative might be to mention the hash of that tag object, and possibly give advice on recovering it.
Hmph, then the advice message would suggest "update-ref"?
Ah, no. Because the message may be reused to create a tag with different tagname, which is very likely because one reason for the refusal to update the ref could be that the name was already taken, and that would create a mismatch between tagname and refname.
OK, so ...
Quoted 9 lines
It's too bad we do not have "git tag -c" to match "git commit -c", which would allow us to say something like:
A tag object was created, but we failed to update the ref. After correcting any errors, you can recover the original tag message with:
git tag -c $oid [other options]
Quoted 8 lines
(where we'd replace $oid with the hash of the created tag object). The best alternatives I could come up with were:
# this is kind of obscure advice to give somebody, plus # it makes a weird tag if you originally tried to tag "foo/bar" # but then later switch to some other name. The "tag" field # in the object won't match the ref. git update-ref $ref $oid
... I agree that this is not a very good advice to give, and ...
Quoted 3 lines
# this saves just the message but is horribly complicated
git cat-file tag $oid | sed '1,/^$/d' |
git tag -F -... this is a reasonable thing to have in a more user-friendly feature, like your -c above.
Quoted 3 lines
I dunno. There is a certain elegance to telling the user about what progress we _did_ make, but if there isn't an easy way to turn that into a retry of their command, it may not be all that useful.
Yeah, I am OK with "leaving TAG_EDITMSG behind" and a future "tag -c/-C $another" to coexist.
Quoted 6 lines · Junio C Hamano
Quoted 3 lines
I dunno. There is a certain elegance to telling the user about what progress we _did_ make, but if there isn't an easy way to turn that into a retry of their command, it may not be all that useful.
Yeah, I am OK with "leaving
TAG_EDITMSGbehind" and a future "tag -c/-C $another" to coexist.
Me too.
One thing I wondered is whether the obvious command to retry:
git tag -F .git/TAG_EDITMSG foo
would work, or if we would overwrite the file before it is read. But it does work, which is good. I wonder if we:
a. want to protect that with a test (since I could imagine a
refactoring where we try to copy the "-F" contents from file to file, rather than reading it into a memory buffer ahead of time)
b. want to tell users that is a good way to recover (though maybe that
is a rabbit hole of details as one subtlety is that it will be overwritten by an unrelated tag command).
But I am also happy to leave it to the user's imagination to pull the contents from the file with "cp" or their editor. :)
-Peff
Quoted 10 lines · Jeff King
One thing I wondered is whether the obvious command to retry:
git tag -F .git/TAG_EDITMSG foo
would work, or if we would overwrite the file before it is read. But it does work, which is good. I wonder if we:
b. want to tell users that is a good way to recover (though maybe that
is a rabbit hole of details as one subtlety is that it will be overwritten by an unrelated tag command).
If that route is taken, then making the advice worktree-friendly would probably be a good idea:
git tag -F $(git rev-parse --git-path TAG_EDITMSG) foo
Quoted 4 lines · Eric Sunshine
If that route is taken, then making the advice worktree-friendly would probably be a good idea:
git tag -F $(git rev-parse --git-path TAG_EDITMSG) foo
The current advice works with worktrees since it just uses path.
Signature · 1 line
Kristoffer Haugsbakk
Quoted 8 lines · Jeff King
It has definitely been a while since that one. :) I agree what you are doing here is in the same spirit as that commit, though there is one small difference.
3927bbe9a4was about saving the message file if we failed to create the object, since otherwise there is no record of it.Your patch is about saving it from a failed ref update. But in that case we _do_ have the data saved in the object. You can find it with git-fsck:
I didn’t even think of that. :) Too much tunnel-focus on that text file…
Quoted 12 lines
[after running something like your test script]
$ git fsck Checking object directories: 100% (256/256), done. dangling tag b9bde516952a863690027a611575a79d4c786b8d$ git cat-file tag b9bde516952a863690027a611575a79d4c786b8d object 8b1e708e32ae3af17825b3c713a3ab317824b932 type commit tag foo/bar tagger Jeff King <[email protected]> 1684314980 -0400
Quoted 4 lines
my message
That said, I'm willing to believe that most users wouldn't figure this out on their own, and saving
TAG_EDITMSGmight be more friendly.
The current error says:
``` fatal: cannot lock ref 'refs/tags/release': 'refs/tags/release/v1' exists; cannot create 'refs/tags/release' ```
As a user, I have no idea if anything (the tag object) was even created. The error just says that the tag couldn’t be created. (As a user the ref pointing to the annotated tag object and the object itself sound like basically the same thing.)
I might be able to guess that it *had* created something tangible like an object in the database since it got all the way to update the ref to point to it (just one more step…). But that feels like a stretch.
Quoted 2 lines
But one other alternative might be to mention the hash of that tag object, and possibly give advice on recovering it.
That could work. Like:
``` hint: git tag <new name> f73e152cb1560aff1446b208affba4cdcf5bea36 ```
So I copy that into my prompt and update the name. All is good.
But I might decide to be just copy the hash, press “Up” in the terminal, copy the hash at the end, and change the name:
``` git tag -a release-it f73e152cb1560aff1446b208affba4cdcf5bea36 ```
Next I’m in my text editor again. Not sure what that is about but okay.
*closes it*
```
hint: You have created a nested tag. The object referred to by your new tag is
hint: already a tag. If you meant to tag the object that it points to, use:
hint:
hint: git tag -f release-it f73e152cb1560aff1446b208affba4cdcf5bea36^{}
hint: Disable this message with "git config advice.nestedTag false"
```Quoted 2 lines
It's too bad we do not have "git tag -c" to match "git commit -c", which would allow us to say something like:
Sure. I appreciate that kind of symmetry. (I guess that’s not the most precise word in this context.)
Quoted 3 lines
I dunno. There is a certain elegance to telling the user about what progress we _did_ make, but if there isn't an easy way to turn that into a retry of their command, it may not be all that useful.
As a user, the only expensive part (potentially; refer back to footnote № 2 of the CV) is writing the tag message. So fudging around a bit with copying the backup text file into the new message (I would probably just copy it into the editor) is not a big deal. And retrying the command is just “Up” and a little line editing.
Quoted 2 lines
So I'm not really opposed to your patch, but mostly just brainstorming alternatives.
It’s very much appreciated.
Quoted 6 lines
I gave v3 a brief look, and it seems OK to me. It might be nice to factor out the duplicated advice message, but since we cannot share any of the other lines (one spot calls
exit()and the other usesdie()), I am OK with it as-is.-Peff
I’ll make a function for the message in the next round. It’s nice to not have to duplicate such strings/message ids, I think.
Cheers
Signature · 1 line
Kristoffer Haugsbakk