threads / patch / 44646

patch, 5 partsMake sequencer abort safer

Subject: [PATCH 4/5] Make sequencer abort safer

## tl;dr

18 messages between Dec 7, 2016 and Dec 10, 2016. Diffs are folded; open one to read it.

replies: 17people: 6as markdown or json

Stephan Beyer· Dec 7, 2016, 21:51 UTC · lore

[PATCH 1/5] am: Fix filename in safe_to_abort() error message

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 Okay let's give it a try. Some minor things that I found
 are also in this patchset (patch 01, 02 and 05).
 The branch can also be found on
   https://github.com/sbeyer/git/commits/sequencer-abort-safety
 builtin/am.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to builtin/am.c +1 −1
diff --git a/builtin/am.c b/builtin/am.c
index 6981f42ce..7cf40e6f2 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -2124,7 +2124,7 @@ static int safe_to_abort(const struct am_state *state)
 
 	if (read_state_file(&sb, state, "abort-safety", 1) > 0) {
 		if (get_oid_hex(sb.buf, &abort_safety))
-			die(_("could not parse %s"), am_path(state, "abort_safety"));
+			die(_("could not parse %s"), am_path(state, "abort-safety"));
 	} else
 		oidclr(&abort_safety);
 
-- 
2.11.0.27.g4eed97c
Stephan Beyer· Dec 7, 2016, 21:51 UTC · re: Stephan Beyer · lore

In contrast to "git am --abort", a sequencer abort did not check whether the current HEAD is the one that is expected. This can lead to loss of work (when not spotted and resolved using reflog before the garbage collector chimes in).

This behavior is now changed by mimicking "git am --abort": the abortion is done but HEAD is not changed when the current HEAD is not the expected HEAD.

A new file "sequencer/current" is added to save the expected HEAD.

The new behavior is only active when --abort is invoked on multiple picks. The problem does not occur for the single-pick case because it is handled differently.

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 sequencer.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 49 insertions(+)
Show changes to sequencer.c +49 −0
diff --git a/sequencer.c b/sequencer.c
index 30b10ba14..c9b560ac1 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -27,6 +27,7 @@ GIT_PATH_FUNC(git_path_seq_dir, "sequencer")
 static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo")
 static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts")
 static GIT_PATH_FUNC(git_path_head_file, "sequencer/head")
+static GIT_PATH_FUNC(git_path_curr_file, "sequencer/current")
 
 /*
  * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
@@ -310,6 +311,20 @@ static int error_dirty_index(struct replay_opts *opts)
 	return -1;
 }
 
+static void update_curr_file()
+{
+	struct object_id head;
+
+	/* Do nothing on a single-pick */
+	if (!file_exists(git_path_seq_dir()))
+		return;
+
+	if (!get_oid("HEAD", &head))
+		write_file(git_path_curr_file(), "%s", oid_to_hex(&head));
+	else
+		write_file(git_path_curr_file(), "%s", "");
+}
+
 static int fast_forward_to(const unsigned char *to, const unsigned char *from,
 			int unborn, struct replay_opts *opts)
 {
@@ -339,6 +354,7 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from,
 	strbuf_release(&sb);
 	strbuf_release(&err);
 	ref_transaction_free(transaction);
+	update_curr_file();
 	return 0;
 }
 
@@ -813,6 +829,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
 
 leave:
 	free_message(commit, &msg);
+	update_curr_file();
 
 	return res;
 }
@@ -1132,9 +1149,34 @@ static int save_head(const char *head)
 	return 0;
 }
 
+static int rollback_is_safe()
+{
+	struct strbuf sb = STRBUF_INIT;
+	struct object_id expected_head, actual_head;
+
+	if (strbuf_read_file(&sb, git_path_curr_file(), 0) >= 0) {
+		strbuf_trim(&sb);
+		if (get_oid_hex(sb.buf, &expected_head)) {
+			strbuf_release(&sb);
+			die(_("could not parse %s"), git_path_curr_file());
+		}
+		strbuf_release(&sb);
+	}
+	else if (errno == ENOENT)
+		oidclr(&expected_head);
+	else
+		die_errno(_("could not read '%s'"), git_path_curr_file());
+
+	if (get_oid("HEAD", &actual_head))
+		oidclr(&actual_head);
+
+	return !oidcmp(&actual_head, &expected_head);
+}
+
 static int reset_for_rollback(const unsigned char *sha1)
 {
 	const char *argv[4];	/* reset --merge <arg> + NULL */
+
 	argv[0] = "reset";
 	argv[1] = "--merge";
 	argv[2] = sha1_to_hex(sha1);
@@ -1189,6 +1231,12 @@ int sequencer_rollback(struct replay_opts *opts)
 		error(_("cannot abort from a branch yet to be born"));
 		goto fail;
 	}
+
+	if (!rollback_is_safe()) {
+		/* Do not error, just do not rollback */
+		warning(_("You seem to have moved HEAD. "
+			  "Not rewinding, check your HEAD!"));
+	} else
 	if (reset_for_rollback(sha1))
 		goto fail;
 	strbuf_release(&buf);
@@ -1393,6 +1441,7 @@ int sequencer_pick_revisions(struct replay_opts *opts)
 		return -1;
 	if (save_opts(opts))
 		return -1;
+	update_curr_file();
 	res = pick_commits(&todo_list, opts);
 	todo_list_release(&todo_list);
 	return res;
-- 
2.11.0.27.g4eed97c
Johannes Schindelin· Dec 8, 2016, 15:28 UTC · re: Stephan Beyer · lore

Re: [PATCH 4/5] Make sequencer abort safer

Hi,
On Wed, 7 Dec 2016, Stephan Beyer wrote:
Show 9 quoted lines
> diff --git a/sequencer.c b/sequencer.c
> index 30b10ba14..c9b560ac1 100644
> --- a/sequencer.c
> +++ b/sequencer.c
> @@ -27,6 +27,7 @@ GIT_PATH_FUNC(git_path_seq_dir, "sequencer")
>  static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo")
>  static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts")
>  static GIT_PATH_FUNC(git_path_head_file, "sequencer/head")
> +static GIT_PATH_FUNC(git_path_curr_file, "sequencer/current")

Is it required by law to have a four-letter infix, or can we have a nicer variable name (e.g. git_path_current_file)?

Ciao, Dscho

Junio C Hamano· Dec 8, 2016, 17:27 UTC · re: Johannes Schindelin · lore

Re: [PATCH 4/5] Make sequencer abort safer

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
Show 14 quoted lines
> On Wed, 7 Dec 2016, Stephan Beyer wrote:
>
>> diff --git a/sequencer.c b/sequencer.c
>> index 30b10ba14..c9b560ac1 100644
>> --- a/sequencer.c
>> +++ b/sequencer.c
>> @@ -27,6 +27,7 @@ GIT_PATH_FUNC(git_path_seq_dir, "sequencer")
>>  static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo")
>>  static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts")
>>  static GIT_PATH_FUNC(git_path_head_file, "sequencer/head")
>> +static GIT_PATH_FUNC(git_path_curr_file, "sequencer/current")
>
> Is it required by law to have a four-letter infix, or can we have a nicer
> variable name (e.g. git_path_current_file)?

I agree with you that, as other git_path_*_file variables match the actual name on the filesystem, this one should too, together with the update_curr_file() function.

By the way, this step seems to be a fix to an existing problem, and the new test added in 3/5 seems to be a demonstration of the issue. If that is the case, shouldn't the new test initially expect failure and updated by this step to expect success?

I'll queue this on top of step 4/5 as "SQUASH???" as usual. The other SQUASH??? that must come after 3/5 for t3510 should be trivial (the reverse of what appears here).

Thanks.
 sequencer.c                     | 22 +++++++++++-----------
 t/t3510-cherry-pick-sequence.sh |  2 +-
 2 files changed, 12 insertions(+), 12 deletions(-)
Show changes to 2 files +12 −12

sequencer.c, t/t3510-cherry-pick-sequence.sh

diff --git a/sequencer.c b/sequencer.c
index c9b560ac15..ce04377f8e 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -27,7 +27,7 @@ GIT_PATH_FUNC(git_path_seq_dir, "sequencer")
 static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo")
 static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts")
 static GIT_PATH_FUNC(git_path_head_file, "sequencer/head")
-static GIT_PATH_FUNC(git_path_curr_file, "sequencer/current")
+static GIT_PATH_FUNC(git_path_current_file, "sequencer/current")
 
 /*
  * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
@@ -311,7 +311,7 @@ static int error_dirty_index(struct replay_opts *opts)
 	return -1;
 }
 
-static void update_curr_file()
+static void update_current_file(void)
 {
 	struct object_id head;
 
@@ -320,9 +320,9 @@ static void update_curr_file()
 		return;
 
 	if (!get_oid("HEAD", &head))
-		write_file(git_path_curr_file(), "%s", oid_to_hex(&head));
+		write_file(git_path_current_file(), "%s", oid_to_hex(&head));
 	else
-		write_file(git_path_curr_file(), "%s", "");
+		write_file(git_path_current_file(), "%s", "");
 }
 
 static int fast_forward_to(const unsigned char *to, const unsigned char *from,
@@ -354,7 +354,7 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from,
 	strbuf_release(&sb);
 	strbuf_release(&err);
 	ref_transaction_free(transaction);
-	update_curr_file();
+	update_current_file();
 	return 0;
 }
 
@@ -829,7 +829,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
 
 leave:
 	free_message(commit, &msg);
-	update_curr_file();
+	update_current_file();
 
 	return res;
 }
@@ -1149,23 +1149,23 @@ static int save_head(const char *head)
 	return 0;
 }
 
-static int rollback_is_safe()
+static int rollback_is_safe(void)
 {
 	struct strbuf sb = STRBUF_INIT;
 	struct object_id expected_head, actual_head;
 
-	if (strbuf_read_file(&sb, git_path_curr_file(), 0) >= 0) {
+	if (strbuf_read_file(&sb, git_path_current_file(), 0) >= 0) {
 		strbuf_trim(&sb);
 		if (get_oid_hex(sb.buf, &expected_head)) {
 			strbuf_release(&sb);
-			die(_("could not parse %s"), git_path_curr_file());
+			die(_("could not parse %s"), git_path_current_file());
 		}
 		strbuf_release(&sb);
 	}
 	else if (errno == ENOENT)
 		oidclr(&expected_head);
 	else
-		die_errno(_("could not read '%s'"), git_path_curr_file());
+		die_errno(_("could not read '%s'"), git_path_current_file());
 
 	if (get_oid("HEAD", &actual_head))
 		oidclr(&actual_head);
@@ -1441,7 +1441,7 @@ int sequencer_pick_revisions(struct replay_opts *opts)
 		return -1;
 	if (save_opts(opts))
 		return -1;
-	update_curr_file();
+	update_current_file();
 	res = pick_commits(&todo_list, opts);
 	todo_list_release(&todo_list);
 	return res;
diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh
index efcd4fc485..372307c21b 100755
--- a/t/t3510-cherry-pick-sequence.sh
+++ b/t/t3510-cherry-pick-sequence.sh
@@ -147,7 +147,7 @@ test_expect_success '--abort to cancel single cherry-pick' '
 	git diff-index --exit-code HEAD
 '
 
-test_expect_failure '--abort does not unsafely change HEAD' '
+test_expect_success '--abort does not unsafely change HEAD' '
 	pristine_detach initial &&
 	test_must_fail git cherry-pick picked anotherpick &&
 	git reset --hard base &&
Stephan Beyer· Dec 8, 2016, 19:17 UTC · re: Junio C Hamano · lore

Re: [PATCH 4/5] Make sequencer abort safer

Hi,
I'm a little afraid of feeding Parkinson's law of triviality here, but... ;)
On 12/08/2016 06:27 PM, Junio C Hamano wrote:
Show 20 quoted lines
> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
>> On Wed, 7 Dec 2016, Stephan Beyer wrote:
>>
>>> diff --git a/sequencer.c b/sequencer.c
>>> index 30b10ba14..c9b560ac1 100644
>>> --- a/sequencer.c
>>> +++ b/sequencer.c
>>> @@ -27,6 +27,7 @@ GIT_PATH_FUNC(git_path_seq_dir, "sequencer")
>>>  static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo")
>>>  static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts")
>>>  static GIT_PATH_FUNC(git_path_head_file, "sequencer/head")
>>> +static GIT_PATH_FUNC(git_path_curr_file, "sequencer/current")
>>
>> Is it required by law to have a four-letter infix, or can we have a nicer
>> variable name (e.g. git_path_current_file)?
> 
> I agree with you that, as other git_path_*_file variables match the
> actual name on the filesystem, this one should too, together with
> the update_curr_file() function.

I totally agree with that (and I don't know why I used "curr", probably just because it looked consistent and good...).

However:
> -static void update_curr_file()
> +static void update_current_file(void)

This function name could lead to the impression that there is some current file (defined by a global state or whatever) that is updated.

So I'd rather rename the *file* to one of
 * sequencer/abort-safety (consistent to am, describes its purpose)
 * sequencer/safety (shorter, still describes the purpose)
 * sequencer/current-head (describes what it contains)
 * sequencer/last (a four-letter word, not totally unambiguous though)
> By the way, this step seems to be a fix to an existing problem, and
> the new test added in 3/5 seems to be a demonstration of the issue.
> If that is the case, shouldn't the new test initially expect failure
> and updated by this step to expect success?

That's usually a matter of taste that I sometimes also discuss with colleagues in other projects... However, for the git test suite with its "known breakage" behavior, your recommendation is surely the best way to do it (aside from introducing the test and the fix in one commit... but that does not show in the history that there actually was that breakage)

~Stephan
Junio C Hamano· Dec 9, 2016, 18:33 UTC · re: Stephan Beyer · lore

Re: [PATCH 4/5] Make sequencer abort safer

Stephan Beyer <s-beyer@gmx.net> writes:
Show 14 quoted lines
> However:
>
>> -static void update_curr_file()
>> +static void update_current_file(void)
>
> This function name could lead to the impression that there is some
> current file (defined by a global state or whatever) that is updated.
>
> So I'd rather rename the *file* to one of
>
>  * sequencer/abort-safety (consistent to am, describes its purpose)
>  * sequencer/safety (shorter, still describes the purpose)
>  * sequencer/current-head (describes what it contains)
>  * sequencer/last (a four-letter word, not totally unambiguous though)

OK, so here is a patch that needs to be squashed further on top of 4/5. I just picked the first one on your list ;-)

Thanks.
 sequencer.c | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)
Show changes to sequencer.c +10 −10
diff --git a/sequencer.c b/sequencer.c
index 874aaa4cd4..3ac4cb8d3b 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -27,7 +27,7 @@ GIT_PATH_FUNC(git_path_seq_dir, "sequencer")
 static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo")
 static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts")
 static GIT_PATH_FUNC(git_path_head_file, "sequencer/head")
-static GIT_PATH_FUNC(git_path_current_file, "sequencer/current")
+static GIT_PATH_FUNC(git_path_abort_safety_file, "sequencer/abort-safety")
 
 /*
  * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
@@ -306,7 +306,7 @@ static int error_dirty_index(struct replay_opts *opts)
 	return -1;
 }
 
-static void update_current_file(void)
+static void update_abort_safety_file(void)
 {
 	struct object_id head;
 
@@ -315,9 +315,9 @@ static void update_current_file(void)
 		return;
 
 	if (!get_oid("HEAD", &head))
-		write_file(git_path_current_file(), "%s", oid_to_hex(&head));
+		write_file(git_path_abort_safety_file(), "%s", oid_to_hex(&head));
 	else
-		write_file(git_path_current_file(), "%s", "");
+		write_file(git_path_abort_safety_file(), "%s", "");
 }
 
 static int fast_forward_to(const unsigned char *to, const unsigned char *from,
@@ -349,7 +349,7 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from,
 	strbuf_release(&sb);
 	strbuf_release(&err);
 	ref_transaction_free(transaction);
-	update_current_file();
+	update_abort_safety_file();
 	return 0;
 }
 
@@ -824,7 +824,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
 
 leave:
 	free_message(commit, &msg);
-	update_current_file();
+	update_abort_safety_file();
 
 	return res;
 }
@@ -1149,18 +1149,18 @@ static int rollback_is_safe(void)
 	struct strbuf sb = STRBUF_INIT;
 	struct object_id expected_head, actual_head;
 
-	if (strbuf_read_file(&sb, git_path_current_file(), 0) >= 0) {
+	if (strbuf_read_file(&sb, git_path_abort_safety_file(), 0) >= 0) {
 		strbuf_trim(&sb);
 		if (get_oid_hex(sb.buf, &expected_head)) {
 			strbuf_release(&sb);
-			die(_("could not parse %s"), git_path_current_file());
+			die(_("could not parse %s"), git_path_abort_safety_file());
 		}
 		strbuf_release(&sb);
 	}
 	else if (errno == ENOENT)
 		oidclr(&expected_head);
 	else
-		die_errno(_("could not read '%s'"), git_path_current_file());
+		die_errno(_("could not read '%s'"), git_path_abort_safety_file());
 
 	if (get_oid("HEAD", &actual_head))
 		oidclr(&actual_head);
@@ -1436,7 +1436,7 @@ int sequencer_pick_revisions(struct replay_opts *opts)
 		return -1;
 	if (save_opts(opts))
 		return -1;
-	update_current_file();
+	update_abort_safety_file();
 	res = pick_commits(&todo_list, opts);
 	todo_list_release(&todo_list);
 	return res;
Stephan Beyer· Dec 9, 2016, 19:01 UTC · re: Junio C Hamano · lore

[PATCH v2 1/5] am: Fix filename in safe_to_abort() error message

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 builtin/am.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to builtin/am.c +1 −1
diff --git a/builtin/am.c b/builtin/am.c
index 6981f42ce..7cf40e6f2 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -2124,7 +2124,7 @@ static int safe_to_abort(const struct am_state *state)
 
 	if (read_state_file(&sb, state, "abort-safety", 1) > 0) {
 		if (get_oid_hex(sb.buf, &abort_safety))
-			die(_("could not parse %s"), am_path(state, "abort_safety"));
+			die(_("could not parse %s"), am_path(state, "abort-safety"));
 	} else
 		oidclr(&abort_safety);
 
-- 
2.11.0.27.g74d6bea
Stephan Beyer· Dec 9, 2016, 19:01 UTC · re: Stephan Beyer · lore

[PATCH v2 3/5] Add test that cherry-pick --abort does not unsafely change HEAD

The test expects failure because it is a current breakage reported by Junio C Hamano.

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 t/t3510-cherry-pick-sequence.sh | 10 ++++++++++
 1 file changed, 10 insertions(+)
Show changes to t/t3510-cherry-pick-sequence.sh +10 −0
diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh
index 7b7a89dbd..efcd4fc48 100755
--- a/t/t3510-cherry-pick-sequence.sh
+++ b/t/t3510-cherry-pick-sequence.sh
@@ -147,6 +147,16 @@ test_expect_success '--abort to cancel single cherry-pick' '
 	git diff-index --exit-code HEAD
 '
 
+test_expect_failure '--abort does not unsafely change HEAD' '
+	pristine_detach initial &&
+	test_must_fail git cherry-pick picked anotherpick &&
+	git reset --hard base &&
+	test_must_fail git cherry-pick picked anotherpick &&
+	git cherry-pick --abort 2>actual &&
+	test_i18ngrep "You seem to have moved HEAD" actual &&
+	test_cmp_rev base HEAD
+'
+
 test_expect_success 'cherry-pick --abort to cancel multiple revert' '
 	pristine_detach anotherpick &&
 	test_expect_code 1 git revert base..picked &&
-- 
2.11.0.27.g74d6bea
Stephan Beyer· Dec 9, 2016, 19:01 UTC · re: Stephan Beyer · lore

[PATCH v2 2/5] am: Change safe_to_abort()'s not rewinding error into a warning

The error message tells the user that something went terribly wrong and the --abort could not be performed. But the --abort is performed, only without rewinding. By simply changing the error into a warning, we indicate the user that she must not try something like "git am --abort --force", instead she just has to check the HEAD.

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 builtin/am.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to builtin/am.c +1 −1
diff --git a/builtin/am.c b/builtin/am.c
index 7cf40e6f2..826f18ba1 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -2134,7 +2134,7 @@ static int safe_to_abort(const struct am_state *state)
 	if (!oidcmp(&head, &abort_safety))
 		return 1;
 
-	error(_("You seem to have moved HEAD since the last 'am' failure.\n"
+	warning(_("You seem to have moved HEAD since the last 'am' failure.\n"
 		"Not rewinding to ORIG_HEAD"));
 
 	return 0;
-- 
2.11.0.27.g74d6bea
Stephan Beyer· Dec 9, 2016, 19:01 UTC · re: Stephan Beyer · lore

[PATCH v2 4/5] Make sequencer abort safer

In contrast to "git am --abort", a sequencer abort did not check whether the current HEAD is the one that is expected. This can lead to loss of work (when not spotted and resolved using reflog before the garbage collector chimes in).

This behavior is now changed by mimicking "git am --abort": the abortion is done but HEAD is not changed when the current HEAD is not the expected HEAD.

A new file "sequencer/current" is added to save the expected HEAD.

The new behavior is only active when --abort is invoked on multiple picks. The problem does not occur for the single-pick case because it is handled differently.

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 sequencer.c                     | 48 +++++++++++++++++++++++++++++++++++++++++
 t/t3510-cherry-pick-sequence.sh |  2 +-
 2 files changed, 49 insertions(+), 1 deletion(-)
Show changes to 2 files +49 −1

sequencer.c, t/t3510-cherry-pick-sequence.sh

diff --git a/sequencer.c b/sequencer.c
index 30b10ba14..35c158471 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -27,6 +27,7 @@ GIT_PATH_FUNC(git_path_seq_dir, "sequencer")
 static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo")
 static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts")
 static GIT_PATH_FUNC(git_path_head_file, "sequencer/head")
+static GIT_PATH_FUNC(git_path_abort_safety_file, "sequencer/abort-safety")
 
 /*
  * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
@@ -310,6 +311,20 @@ static int error_dirty_index(struct replay_opts *opts)
 	return -1;
 }
 
+static void update_abort_safety_file(void)
+{
+	struct object_id head;
+
+	/* Do nothing on a single-pick */
+	if (!file_exists(git_path_seq_dir()))
+		return;
+
+	if (!get_oid("HEAD", &head))
+		write_file(git_path_abort_safety_file(), "%s", oid_to_hex(&head));
+	else
+		write_file(git_path_abort_safety_file(), "%s", "");
+}
+
 static int fast_forward_to(const unsigned char *to, const unsigned char *from,
 			int unborn, struct replay_opts *opts)
 {
@@ -339,6 +354,7 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from,
 	strbuf_release(&sb);
 	strbuf_release(&err);
 	ref_transaction_free(transaction);
+	update_abort_safety_file();
 	return 0;
 }
 
@@ -813,6 +829,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
 
 leave:
 	free_message(commit, &msg);
+	update_abort_safety_file();
 
 	return res;
 }
@@ -1132,6 +1149,30 @@ static int save_head(const char *head)
 	return 0;
 }
 
+static int rollback_is_safe(void)
+{
+	struct strbuf sb = STRBUF_INIT;
+	struct object_id expected_head, actual_head;
+
+	if (strbuf_read_file(&sb, git_path_abort_safety_file(), 0) >= 0) {
+		strbuf_trim(&sb);
+		if (get_oid_hex(sb.buf, &expected_head)) {
+			strbuf_release(&sb);
+			die(_("could not parse %s"), git_path_abort_safety_file());
+		}
+		strbuf_release(&sb);
+	}
+	else if (errno == ENOENT)
+		oidclr(&expected_head);
+	else
+		die_errno(_("could not read '%s'"), git_path_abort_safety_file());
+
+	if (get_oid("HEAD", &actual_head))
+		oidclr(&actual_head);
+
+	return !oidcmp(&actual_head, &expected_head);
+}
+
 static int reset_for_rollback(const unsigned char *sha1)
 {
 	const char *argv[4];	/* reset --merge <arg> + NULL */
@@ -1189,6 +1230,12 @@ int sequencer_rollback(struct replay_opts *opts)
 		error(_("cannot abort from a branch yet to be born"));
 		goto fail;
 	}
+
+	if (!rollback_is_safe()) {
+		/* Do not error, just do not rollback */
+		warning(_("You seem to have moved HEAD. "
+			  "Not rewinding, check your HEAD!"));
+	} else
 	if (reset_for_rollback(sha1))
 		goto fail;
 	strbuf_release(&buf);
@@ -1393,6 +1440,7 @@ int sequencer_pick_revisions(struct replay_opts *opts)
 		return -1;
 	if (save_opts(opts))
 		return -1;
+	update_abort_safety_file();
 	res = pick_commits(&todo_list, opts);
 	todo_list_release(&todo_list);
 	return res;
diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh
index efcd4fc48..372307c21 100755
--- a/t/t3510-cherry-pick-sequence.sh
+++ b/t/t3510-cherry-pick-sequence.sh
@@ -147,7 +147,7 @@ test_expect_success '--abort to cancel single cherry-pick' '
 	git diff-index --exit-code HEAD
 '
 
-test_expect_failure '--abort does not unsafely change HEAD' '
+test_expect_success '--abort does not unsafely change HEAD' '
 	pristine_detach initial &&
 	test_must_fail git cherry-pick picked anotherpick &&
 	git reset --hard base &&
-- 
2.11.0.27.g74d6bea
Christian Couder· Dec 10, 2016, 19:56 UTC · re: Stephan Beyer · lore

Re: [PATCH v2 4/5] Make sequencer abort safer

On Fri, Dec 9, 2016 at 8:01 PM, Stephan Beyer <s-beyer@gmx.net> wrote:
[...]
Show 13 quoted lines
> +static int rollback_is_safe(void)
> +{
> +       struct strbuf sb = STRBUF_INIT;
> +       struct object_id expected_head, actual_head;
> +
> +       if (strbuf_read_file(&sb, git_path_abort_safety_file(), 0) >= 0) {
> +               strbuf_trim(&sb);
> +               if (get_oid_hex(sb.buf, &expected_head)) {
> +                       strbuf_release(&sb);
> +                       die(_("could not parse %s"), git_path_abort_safety_file());
> +               }
> +               strbuf_release(&sb);
> +       }
Maybe the following is a bit simpler:
       if (strbuf_read_file(&sb, git_path_abort_safety_file(), 0) >= 0) {
               int res;
               strbuf_trim(&sb);
               res = get_oid_hex(sb.buf, &expected_head);
               strbuf_release(&sb);
               if (res)
                   die(_("could not parse %s"), git_path_abort_safety_file());
       }

Thanks, Christian.

Jeff King· Dec 10, 2016, 20:04 UTC · re: Christian Couder · lore

Re: [PATCH v2 4/5] Make sequencer abort safer

On Sat, Dec 10, 2016 at 08:56:26PM +0100, Christian Couder wrote:
Show 24 quoted lines
> > +static int rollback_is_safe(void)
> > +{
> > +       struct strbuf sb = STRBUF_INIT;
> > +       struct object_id expected_head, actual_head;
> > +
> > +       if (strbuf_read_file(&sb, git_path_abort_safety_file(), 0) >= 0) {
> > +               strbuf_trim(&sb);
> > +               if (get_oid_hex(sb.buf, &expected_head)) {
> > +                       strbuf_release(&sb);
> > +                       die(_("could not parse %s"), git_path_abort_safety_file());
> > +               }
> > +               strbuf_release(&sb);
> > +       }
> 
> Maybe the following is a bit simpler:
> 
>        if (strbuf_read_file(&sb, git_path_abort_safety_file(), 0) >= 0) {
>                int res;
>                strbuf_trim(&sb);
>                res = get_oid_hex(sb.buf, &expected_head);
>                strbuf_release(&sb);
>                if (res)
>                    die(_("could not parse %s"), git_path_abort_safety_file());
>        }

Is there any point in calling strbuf_release() if we're about to die anyway? I could see it if it were "return error()", but it's normal in our code base for die() to be abrupt.

-Peff
Stephan Beyer· Dec 10, 2016, 20:20 UTC · re: Jeff King · lore

Re: [PATCH v2 4/5] Make sequencer abort safer

On 12/10/2016 09:04 PM, Jeff King wrote:
Show 30 quoted lines
> On Sat, Dec 10, 2016 at 08:56:26PM +0100, Christian Couder wrote:
> 
>>> +static int rollback_is_safe(void)
>>> +{
>>> +       struct strbuf sb = STRBUF_INIT;
>>> +       struct object_id expected_head, actual_head;
>>> +
>>> +       if (strbuf_read_file(&sb, git_path_abort_safety_file(), 0) >= 0) {
>>> +               strbuf_trim(&sb);
>>> +               if (get_oid_hex(sb.buf, &expected_head)) {
>>> +                       strbuf_release(&sb);
>>> +                       die(_("could not parse %s"), git_path_abort_safety_file());
>>> +               }
>>> +               strbuf_release(&sb);
>>> +       }
>>
>> Maybe the following is a bit simpler:
>>
>>        if (strbuf_read_file(&sb, git_path_abort_safety_file(), 0) >= 0) {
>>                int res;
>>                strbuf_trim(&sb);
>>                res = get_oid_hex(sb.buf, &expected_head);
>>                strbuf_release(&sb);
>>                if (res)
>>                    die(_("could not parse %s"), git_path_abort_safety_file());
>>        }
> 
> Is there any point in calling strbuf_release() if we're about to die
> anyway? I could see it if it were "return error()", but it's normal in
> our code base for die() to be abrupt.

The point is that someone "libifies" the function some day; then "die()" becomes "return error()" almost automatically. Chances are high that the resulting memory leak is forgotten. That's one of the reasons why I like being strict about memory leaks.

However, I cannot tell if mine or Christian's variant is really "simpler" (with whatever measure) and I also don't care much.

~Stephan
Stephan Beyer· Dec 9, 2016, 19:01 UTC · re: Stephan Beyer · lore

[PATCH v2 5/5] sequencer: Remove useless get_dir() function

This function is used only once, for the removal of the directory. It is not used for the creation of the directory nor anywhere else.

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 sequencer.c | 7 +------
 1 file changed, 1 insertion(+), 6 deletions(-)
Show changes to sequencer.c +1 −6
diff --git a/sequencer.c b/sequencer.c
index 35c158471..aba096a0a 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -47,11 +47,6 @@ static inline int is_rebase_i(const struct replay_opts *opts)
 	return 0;
 }
 
-static const char *get_dir(const struct replay_opts *opts)
-{
-	return git_path_seq_dir();
-}
-
 static const char *get_todo_path(const struct replay_opts *opts)
 {
 	return git_path_todo_file();
@@ -160,7 +155,7 @@ int sequencer_remove_state(struct replay_opts *opts)
 		free(opts->xopts[i]);
 	free(opts->xopts);
 
-	strbuf_addf(&dir, "%s", get_dir(opts));
+	strbuf_addf(&dir, "%s", git_path_seq_dir());
 	remove_dir_recursively(&dir, 0);
 	strbuf_release(&dir);
 
-- 
2.11.0.27.g74d6bea
Stephan Beyer· Dec 7, 2016, 21:51 UTC · re: Stephan Beyer · lore

[PATCH 2/5] am: Change safe_to_abort()'s not rewinding error into a warning

The error message tells the user that something went terribly wrong and the --abort could not be performed. But the --abort is performed, only without rewinding. By simply changing the error into a warning, we indicate the user that she must not try something like "git am --abort --force", instead she just has to check the HEAD.

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 builtin/am.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to builtin/am.c +1 −1
diff --git a/builtin/am.c b/builtin/am.c
index 7cf40e6f2..826f18ba1 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -2134,7 +2134,7 @@ static int safe_to_abort(const struct am_state *state)
 	if (!oidcmp(&head, &abort_safety))
 		return 1;
 
-	error(_("You seem to have moved HEAD since the last 'am' failure.\n"
+	warning(_("You seem to have moved HEAD since the last 'am' failure.\n"
 		"Not rewinding to ORIG_HEAD"));
 
 	return 0;
-- 
2.11.0.27.g4eed97c
Stephan Beyer· Dec 7, 2016, 21:51 UTC · re: Stephan Beyer · lore

[PATCH 3/5] Add test that cherry-pick --abort does not unsafely change HEAD

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 t/t3510-cherry-pick-sequence.sh | 10 ++++++++++
 1 file changed, 10 insertions(+)
Show changes to t/t3510-cherry-pick-sequence.sh +10 −0
diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh
index 7b7a89dbd..372307c21 100755
--- a/t/t3510-cherry-pick-sequence.sh
+++ b/t/t3510-cherry-pick-sequence.sh
@@ -147,6 +147,16 @@ test_expect_success '--abort to cancel single cherry-pick' '
 	git diff-index --exit-code HEAD
 '
 
+test_expect_success '--abort does not unsafely change HEAD' '
+	pristine_detach initial &&
+	test_must_fail git cherry-pick picked anotherpick &&
+	git reset --hard base &&
+	test_must_fail git cherry-pick picked anotherpick &&
+	git cherry-pick --abort 2>actual &&
+	test_i18ngrep "You seem to have moved HEAD" actual &&
+	test_cmp_rev base HEAD
+'
+
 test_expect_success 'cherry-pick --abort to cancel multiple revert' '
 	pristine_detach anotherpick &&
 	test_expect_code 1 git revert base..picked &&
-- 
2.11.0.27.g4eed97c
Stephan Beyer· Dec 7, 2016, 21:51 UTC · re: Stephan Beyer · lore

[PATCH 5/5] sequencer: Remove useless get_dir() function

This function is used only once, for the removal of the directory. It is not used for the creation of the directory nor anywhere else.

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 sequencer.c | 7 +------
 1 file changed, 1 insertion(+), 6 deletions(-)
Show changes to sequencer.c +1 −6
diff --git a/sequencer.c b/sequencer.c
index c9b560ac1..689cfa5f1 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -47,11 +47,6 @@ static inline int is_rebase_i(const struct replay_opts *opts)
 	return 0;
 }
 
-static const char *get_dir(const struct replay_opts *opts)
-{
-	return git_path_seq_dir();
-}
-
 static const char *get_todo_path(const struct replay_opts *opts)
 {
 	return git_path_todo_file();
@@ -160,7 +155,7 @@ int sequencer_remove_state(struct replay_opts *opts)
 		free(opts->xopts[i]);
 	free(opts->xopts);
 
-	strbuf_addf(&dir, "%s", get_dir(opts));
+	strbuf_addf(&dir, "%s", git_path_seq_dir());
 	remove_dir_recursively(&dir, 0);
 	strbuf_release(&dir);
 
-- 
2.11.0.27.g4eed97c
Paul Tan· Dec 8, 2016, 10:21 UTC · re: Stephan Beyer · lore

Re: [PATCH 1/5] am: Fix filename in safe_to_abort() error message

Hi Stephan,
On Thu, Dec 8, 2016 at 5:51 AM, Stephan Beyer <s-beyer@gmx.net> wrote:
Show 10 quoted lines
> diff --git a/builtin/am.c b/builtin/am.c
> index 6981f42ce..7cf40e6f2 100644
> --- a/builtin/am.c
> +++ b/builtin/am.c
> @@ -2124,7 +2124,7 @@ static int safe_to_abort(const struct am_state *state)
>
>         if (read_state_file(&sb, state, "abort-safety", 1) > 0) {
>                 if (get_oid_hex(sb.buf, &abort_safety))
> -                       die(_("could not parse %s"), am_path(state, "abort_safety"));
> +                       die(_("could not parse %s"), am_path(state, "abort-safety"));
Ah, this is obviously correct. Sorry for the oversight.
Show 5 quoted lines
>         } else
>                 oidclr(&abort_safety);
>
> --
> 2.11.0.27.g4eed97c

Thanks, Paul

← back to recent threads