threads / patch / 27931

patch, 18 partsGSoC update: Sequencer for inclusion v3

Subject: [PATCH 00/18] GSoC update: Sequencer for inclusion v3

## tl;dr

79 messages between Jul 27, 2011 and Aug 1, 2011. Diffs are folded; open one to read it.

replies: 78people: 5as markdown or json

Ramkumar Ramachandra· Jul 27, 2011, 03:18 UTC · lore
Hi,

Nothing new has been introduced; a couple of minor changes have been made since last time (rewrite "config: Introduce functions to write non-standard file" and remove "Please, " in hint: messages). I will hencefourth refer to this series as "sequencer-stable" -- I've started building post midterm work* on top of this. If something critical comes up, I can always fixup this series, rebase and quickly continue my work.

While Junio and others are not entirely happy with "revert: Remove sequencer state when no commits are pending", and there is still some confusion about how '--abort' will work in the future, I think we have discussed these issues sufficiently.

* I haven't made anything public yet, because it's still in its
  infancy stages; I'll hopefully have something to show by the end of
  the week.
Thanks for reading.
-- Ram
Ramkumar Ramachandra (18):
  advice: Introduce error_resolve_conflict
  config: Introduce functions to write non-standard file
  revert: Simplify and inline add_message_to_msg
  revert: Don't check lone argument in get_encoding
  revert: Rename no_replay to record_origin
  revert: Propogate errors upwards from do_pick_commit
  revert: Eliminate global "commit" variable
  revert: Introduce struct to keep command-line options
  revert: Separate cmdline parsing from functional code
  revert: Don't create invalid replay_opts in parse_args
  revert: Save data for continuing after conflict resolution
  revert: Save command-line options for continuing operation
  revert: Make pick_commits functionally act on a commit list
  revert: Introduce --reset to remove sequencer state
  reset: Make reset remove the sequencer state
  revert: Remove sequencer state when no commits are pending
  revert: Don't implictly stomp pending sequencer operation
  revert: Introduce --continue to continue the operation
 Documentation/git-cherry-pick.txt |    6 +
 Documentation/git-revert.txt      |    6 +
 Documentation/sequencer.txt       |    9 +
 Makefile                          |    2 +
 advice.c                          |   31 ++-
 advice.h                          |    3 +-
 branch.c                          |    2 +
 builtin/revert.c                  |  743 +++++++++++++++++++++++++++++--------
 cache.h                           |    2 +
 config.c                          |   36 ++-
 sequencer.c                       |   19 +
 sequencer.h                       |   20 +
 t/7106-reset-sequence.sh          |   43 +++
 t/t3510-cherry-pick-sequence.sh   |  219 +++++++++++
 14 files changed, 969 insertions(+), 172 deletions(-)
 create mode 100644 Documentation/sequencer.txt
 create mode 100644 sequencer.c
 create mode 100644 sequencer.h
 create mode 100755 t/7106-reset-sequence.sh
 create mode 100755 t/t3510-cherry-pick-sequence.sh
-- 
1.7.4.rc1.7.g2cf08.dirty
Ramkumar Ramachandra· Jul 27, 2011, 03:18 UTC · re: Ramkumar Ramachandra · lore

[PATCH 01/18] advice: Introduce error_resolve_conflict

Enable future callers to report a conflict and not die immediately by introducing a new function called error_resolve_conflict. Re-implement die_resolve_conflict as a call to error_resolve_conflict followed by a call to die. Consequently, the message printed by die_resolve_conflict changes from

fatal: 'commit' is not possible because you have unmerged files.
       Please, fix them up in the work tree ...
       ...
to

error: 'commit' is not possible because you have unmerged files. hint: Please, fix them up in the work tree ... hint: ... fatal: Exiting because of an unresolved conflict.

Hints are printed using the same advise function introduced in v1.7.3-rc0~26^2~3 (Introduce advise() to print hints, 2010-08-11).

Inspired-by: Christian Couder <chistian.couder@gmail.com>
Helped-by: Jonathan Nieder <jrnieder@gmail.com>
Reviewed-by: Jonathan Nieder <jrnieder@gmail.com>
Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
---
 advice.c         |   31 ++++++++++++++++++++++++-------
 advice.h         |    3 ++-
 builtin/revert.c |    9 ---------
 3 files changed, 26 insertions(+), 17 deletions(-)
Show changes to 3 files +26 −17

advice.c, advice.h, builtin/revert.c

diff --git a/advice.c b/advice.c
index 0be4b5f..e02e632 100644
--- a/advice.c
+++ b/advice.c
@@ -19,6 +19,15 @@ static struct {
 	{ "detachedhead", &advice_detached_head },
 };
 
+void advise(const char *advice, ...)
+{
+	va_list params;
+
+	va_start(params, advice);
+	vreportf("hint: ", advice, params);
+	va_end(params);
+}
+
 int git_default_advice_config(const char *var, const char *value)
 {
 	const char *k = skip_prefix(var, "advice.");
@@ -34,16 +43,24 @@ int git_default_advice_config(const char *var, const char *value)
 	return 0;
 }
 
-void NORETURN die_resolve_conflict(const char *me)
+int error_resolve_conflict(const char *me)
 {
-	if (advice_resolve_conflict)
+	error("'%s' is not possible because you have unmerged files.", me);
+	if (advice_resolve_conflict) {
 		/*
 		 * Message used both when 'git commit' fails and when
 		 * other commands doing a merge do.
 		 */
-		die("'%s' is not possible because you have unmerged files.\n"
-		    "Please, fix them up in the work tree, and then use 'git add/rm <file>' as\n"
-		    "appropriate to mark resolution and make a commit, or use 'git commit -a'.", me);
-	else
-		die("'%s' is not possible because you have unmerged files.", me);
+		advise("Fix them up in the work tree,");
+		advise("and then use 'git add/rm <file>' as");
+		advise("appropriate to mark resolution and make a commit,");
+		advise("or use 'git commit -a'.");
+	}
+	return -1;
+}
+
+void NORETURN die_resolve_conflict(const char *me)
+{
+	error_resolve_conflict(me);
+	die("Exiting because of an unresolved conflict.");
 }
diff --git a/advice.h b/advice.h
index 3244ebb..e5d0af7 100644
--- a/advice.h
+++ b/advice.h
@@ -11,7 +11,8 @@ extern int advice_implicit_identity;
 extern int advice_detached_head;
 
 int git_default_advice_config(const char *var, const char *value);
-
+void advise(const char *advice, ...);
+int error_resolve_conflict(const char *me);
 extern void NORETURN die_resolve_conflict(const char *me);
 
 #endif /* ADVICE_H */
diff --git a/builtin/revert.c b/builtin/revert.c
index 1f27c63..2df3f3b 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -214,15 +214,6 @@ static void write_cherry_pick_head(void)
 	strbuf_release(&buf);
 }
 
-static void advise(const char *advice, ...)
-{
-	va_list params;
-
-	va_start(params, advice);
-	vreportf("hint: ", advice, params);
-	va_end(params);
-}
-
 static void print_advice(void)
 {
 	char *msg = getenv("GIT_CHERRY_PICK_HELP");
-- 
1.7.4.rc1.7.g2cf08.dirty
Ramkumar Ramachandra· Jul 27, 2011, 03:18 UTC · re: Ramkumar Ramachandra · lore

[PATCH 02/18] config: Introduce functions to write non-standard file

Introduce two new functions corresponding to "git_config_set" and "git_config_set_multivar" to write a non-standard configuration file. Expose thse new functions in cache.h for other git programs to use.

Helped-by: Jeff King <peff@peff.net>
Helped-by: Junio C Hamano <gitster@pobox.com>
Helped-by: Jonathan Nieder <jrnieder@gmail.com>
Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
---
 cache.h  |    2 ++
 config.c |   36 +++++++++++++++++++++++++++---------
 2 files changed, 29 insertions(+), 9 deletions(-)
Show changes to 2 files +29 −9

cache.h, config.c

diff --git a/cache.h b/cache.h
index 9e12d55..33d3428 100644
--- a/cache.h
+++ b/cache.h
@@ -1069,9 +1069,11 @@ extern int git_config_bool(const char *, const char *);
 extern int git_config_maybe_bool(const char *, const char *);
 extern int git_config_string(const char **, const char *, const char *);
 extern int git_config_pathname(const char **, const char *, const char *);
+extern int git_config_set_in_file(const char *, const char *, const char *);
 extern int git_config_set(const char *, const char *);
 extern int git_config_parse_key(const char *, char **, int *);
 extern int git_config_set_multivar(const char *, const char *, const char *, int);
+extern int git_config_set_multivar_in_file(const char *, const char *, const char *, const char *, int);
 extern int git_config_rename_section(const char *, const char *);
 extern const char *git_etc_gitconfig(void);
 extern int check_repository_format_version(const char *var, const char *value, void *cb);
diff --git a/config.c b/config.c
index 6b61a84..a0578b2 100644
--- a/config.c
+++ b/config.c
@@ -1092,6 +1092,12 @@ contline:
 	return offset;
 }
 
+int git_config_set_in_file(const char *config_filename,
+			const char *key, const char *value)
+{
+	return git_config_set_multivar_in_file(config_filename, key, value, NULL, 0);
+}
+
 int git_config_set(const char *key, const char *value)
 {
 	return git_config_set_multivar(key, value, NULL, 0);
@@ -1189,19 +1195,14 @@ out_free_ret_1:
  * - the config file is removed and the lock file rename()d to it.
  *
  */
-int git_config_set_multivar(const char *key, const char *value,
-	const char *value_regex, int multi_replace)
+int git_config_set_multivar_in_file(const char *config_filename,
+				const char *key, const char *value,
+				const char *value_regex, int multi_replace)
 {
 	int fd = -1, in_fd;
 	int ret;
-	char *config_filename;
 	struct lock_file *lock = NULL;
 
-	if (config_exclusive_filename)
-		config_filename = xstrdup(config_exclusive_filename);
-	else
-		config_filename = git_pathdup("config");
-
 	/* parse-key returns negative; flip the sign to feed exit(3) */
 	ret = 0 - git_config_parse_key(key, &store.key, &store.baselen);
 	if (ret)
@@ -1378,7 +1379,6 @@ int git_config_set_multivar(const char *key, const char *value,
 out_free:
 	if (lock)
 		rollback_lock_file(lock);
-	free(config_filename);
 	return ret;
 
 write_err_out:
@@ -1387,6 +1387,24 @@ write_err_out:
 
 }
 
+int git_config_set_multivar(const char *key, const char *value,
+			const char *value_regex, int multi_replace)
+{
+	const char *config_filename;
+	char *filename_buf = NULL;
+	int ret;
+
+	if (config_exclusive_filename)
+		config_filename = config_exclusive_filename;
+	else
+		config_filename = filename_buf = git_pathdup("config");
+
+	ret = git_config_set_multivar_in_file(config_filename, key, value,
+					value_regex, multi_replace);
+	free(filename_buf);
+	return ret;
+}
+
 static int section_name_match (const char *buf, const char *name)
 {
 	int i = 0, j = 0, dot = 0;
-- 
1.7.4.rc1.7.g2cf08.dirty
Jonathan Nieder· Jul 27, 2011, 03:39 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 02/18] config: Introduce functions to write non-standard file

Ramkumar Ramachandra wrote:
Show 17 quoted lines
> --- a/config.c
> +++ b/config.c
> @@ -1387,6 +1387,24 @@ write_err_out:
>  
>  }
>  
> +int git_config_set_multivar(const char *key, const char *value,
> +			const char *value_regex, int multi_replace)
> +{
> +	const char *config_filename;
> +	char *filename_buf = NULL;
> +	int ret;
> +
> +	if (config_exclusive_filename)
> +		config_filename = config_exclusive_filename;
> +	else
> +		config_filename = filename_buf = git_pathdup("config");

I know my sloppy example before is to blame for this, but the "filename_buf" variable name is needlessly verbose --- its semantics are just that it is something to be free()ed. So if I were submitting this I would use "buf".

The change description needs copyediting.
Anyway, with or without the variable name change,
Reviewed-by: Jonathan Nieder <jrnieder@gmail.com>
Thanks.  Patch 1 also looks sensible.
Jeff King· Jul 27, 2011, 05:42 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 02/18] config: Introduce functions to write non-standard file

On Wed, Jul 27, 2011 at 08:48:59AM +0530, Ramkumar Ramachandra wrote:
> Introduce two new functions corresponding to "git_config_set" and
> "git_config_set_multivar" to write a non-standard configuration file.
> Expose thse new functions in cache.h for other git programs to use.

Thanks, this one looks much nicer to me (though I also agree with Jonathan's style micronit).

-Peff
Ramkumar Ramachandra· Jul 28, 2011, 15:40 UTC · re: Jeff King · lore

Re: [PATCH 02/18] config: Introduce functions to write non-standard file

Hi,
Jeff King writes:
> Thanks, this one looks much nicer to me (though I also agree with
> Jonathan's style micronit).
Fixed.  Thanks.
-- Ram
Ramkumar Ramachandra· Jul 27, 2011, 03:19 UTC · re: Ramkumar Ramachandra · lore

[PATCH 03/18] revert: Simplify and inline add_message_to_msg

The add_message_to_msg function has some dead code, an unclear API, only one callsite. While it originally intended fill up an empty commit message with the commit object name while picking, it really doesn't do this -- a bug introduced in 9509af6 (Make git-revert & git-cherry-pick a builtin, 2007-03-01). Today, tests in t3505-cherry-pick-empty.sh indicate that not filling up an empty commit message is the desired behavior. Re-implement and inline the function accordingly.

Helped-by: Junio C Hamano <gitster@pobox.com>
Mentored-by: Jonathan Nieder <jrnieder@gmail.com>
Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
---
 builtin/revert.c |   28 ++++++++++++++--------------
 1 files changed, 14 insertions(+), 14 deletions(-)
Show changes to builtin/revert.c +14 −14
diff --git a/builtin/revert.c b/builtin/revert.c
index 2df3f3b..7dfe295 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -185,19 +185,6 @@ static char *get_encoding(const char *message)
 	return NULL;
 }
 
-static void add_message_to_msg(struct strbuf *msgbuf, const char *message)
-{
-	const char *p = message;
-	while (*p && (*p != '\n' || p[1] != '\n'))
-		p++;
-
-	if (!*p)
-		strbuf_addstr(msgbuf, sha1_to_hex(commit->object.sha1));
-
-	p += 2;
-	strbuf_addstr(msgbuf, p);
-}
-
 static void write_cherry_pick_head(void)
 {
 	int fd;
@@ -462,11 +449,24 @@ static int do_pick_commit(void)
 		}
 		strbuf_addstr(&msgbuf, ".\n");
 	} else {
+		const char *p;
+
 		base = parent;
 		base_label = msg.parent_label;
 		next = commit;
 		next_label = msg.label;
-		add_message_to_msg(&msgbuf, msg.message);
+
+		/*
+		 * Append the commit log message to msgbuf; it starts
+		 * after the tree, parent, author, committer
+		 * information followed by "\n\n".
+		 */
+		p = strstr(msg.message, "\n\n");
+		if (p) {
+			p += 2;
+			strbuf_addstr(&msgbuf, p);
+		}
+
 		if (no_replay) {
 			strbuf_addstr(&msgbuf, "(cherry picked from commit ");
 			strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
-- 
1.7.4.rc1.7.g2cf08.dirty
Jonathan Nieder· Jul 27, 2011, 04:18 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 03/18] revert: Simplify and inline add_message_to_msg

Ramkumar Ramachandra wrote:
> [Subject: [PATCH 03/18] revert: Simplify and inline add_message_to_msg]

Before, I wrote that it would be better to present this as a bugfix than as a code reorganization, since the former is more likely to affect people upgrading or to be relevant if this later turns out to have a regression and someone wonders why not to just revert it. Was I wrong?

Testcase below.  It makes a commit like this:
	tree f0db5ba931b3f121d2050e23ac3cd47ac2233306
	parent 43991719f0536a734e91e94f40361114477b3b5e
	author A U Thor <author@example.com> 1112912113 -0700
	committer C O Mitter <committer@example.com> 1112912113 -0700

with no blank line afterwards. I think this is relevant and not a "what if cosmic rays corrupt that pointer" kind of thing because people use non-git programs or libraries to write to git object stores pretty often. If this object were invalid, it would still not be a good behavior to segfault (better to die() or give some arbitrary-but-sane result). Not sure whether the object should be considered invalid (e.g., "git fsck" accepts it).

Unfortunately it fails for me even after the patch. If the test looks reasonable to you, it could be worth adding marked with "test_expect_failure".

 t/t3501-revert-cherry-pick.sh |   25 +++++++++++++++++++++++++
 1 files changed, 25 insertions(+), 0 deletions(-)
Show changes to t/t3501-revert-cherry-pick.sh +25 −0
diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh
index 595d2ff9..e46e7131 100755
--- a/t/t3501-revert-cherry-pick.sh
+++ b/t/t3501-revert-cherry-pick.sh
@@ -12,6 +12,12 @@ test_description='test cherry-pick and revert with renames
 
 . ./test-lib.sh
 
+remove_object() {
+	file=$(echo "$*" | sed 's#..#.git/objects/&#') &&
+	test -e "$file" &&
+	rm -f "$file"
+}
+
 test_expect_success setup '
 
 	for l in a b c d e f g h i j k l m n o
@@ -70,6 +76,25 @@ test_expect_success 'cherry-pick after renaming branch' '
 
 '
 
+test_expect_success 'cherry-pick commit with no delimiter after header' '
+
+	printf "%s\n" rename1 initial >expect &&
+
+	git cat-file commit rename1 >cmit &&
+	sed -e "/^\$/ Q" <cmit >newcmit &&
+	objname=$(git hash-object -t commit -w newcmit) &&
+	test_when_finished "remove_object $objname" &&
+
+	git checkout initial &&
+	git cherry-pick "$objname" &&
+
+	test_path_is_missing oops &&
+	test_path_is_file spoo &&
+	git log --format=%s >actual &&
+	test_cmp expect actual
+
+'
+
 test_expect_success 'revert after renaming branch' '
 
 	git checkout rename1 &&
-- 
1.7.6
Ramkumar Ramachandra· Jul 27, 2011, 17:26 UTC · re: Jonathan Nieder · lore

Re: [PATCH 03/18] revert: Simplify and inline add_message_to_msg

Hi,
Jonathan Nieder writes:
Show 8 quoted lines
> Ramkumar Ramachandra wrote:
>> [Subject: [PATCH 03/18] revert: Simplify and inline add_message_to_msg]
>
> Before, I wrote that it would be better to present this as a bugfix
> than as a code reorganization, since the former is more likely to
> affect people upgrading or to be relevant if this later turns out to
> have a regression and someone wonders why not to just revert it.  Was
> I wrong?

I like the new commit message: I like to think that the old code was misguided (intent: replace an empty commit message with the commit object name), rather than buggy. Since it's not possible to reproduce the "bug" without constructing a commit object by hand, I don't know if we should classify it a "bug".

Show 14 quoted lines
> Testcase below.  It makes a commit like this:
>
>        tree f0db5ba931b3f121d2050e23ac3cd47ac2233306
>        parent 43991719f0536a734e91e94f40361114477b3b5e
>        author A U Thor <author@example.com> 1112912113 -0700
>        committer C O Mitter <committer@example.com> 1112912113 -0700
>
> with no blank line afterwards.  I think this is relevant and not a
> "what if cosmic rays corrupt that pointer" kind of thing because
> people use non-git programs or libraries to write to git object stores
> pretty often.  If this object were invalid, it would still not be a
> good behavior to segfault (better to die() or give some
> arbitrary-but-sane result).  Not sure whether the object should be
> considered invalid (e.g., "git fsck" accepts it).

Thanks for writing the test. However, there are probably several other edge cases in other parts of Git which accept slightly malformed tree/ blob objects. Including this test is like telling developers: "We have to be prepared for the worst kind of malformed objects in every single Git application, not just objects created by us", and there is probably no end to this. In my opinion, it is alright to SIGSEGV when a different but incorrect implementation of Git creates a corrupt object store -- we should punish them for the mistake and set an example. Instead of attacking the problem at this level, I think we should get the lower layers like git-fsck to throw out malformed objects.

> Unfortunately it fails for me even after the patch.  If the test looks
> reasonable to you, it could be worth adding marked with
> "test_expect_failure".
Perhaps as part of a different series which addresses the issue of
malformed objects in general.  I don't think it is relevant for this
series.  The "other" series I'm thinking about should contain:
1. Documentation about the exact format of commit, tree, blob objects
the way the fast-import stream is documented.
2. Fixes to the lower layers like git fsck/repack/gc: it should throw
out objects that don't conform to the spec.
3. A dedicated list of tests that intentionally create malformed
objects to verify the spec/ restraining code.
(It should accommodate the major implementations and older versions of C Git)
Thanks.
-- Ram
Jonathan Nieder· Jul 27, 2011, 17:42 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 03/18] revert: Simplify and inline add_message_to_msg

Ramkumar Ramachandra wrote:
Show 7 quoted lines
>> Unfortunately it fails for me even after the patch.  If the test looks
>> reasonable to you, it could be worth adding marked with
>> "test_expect_failure".
>
> Perhaps as part of a different series which addresses the issue of
> malformed objects in general.  I don't think it is relevant for this
> series.  The "other" series I'm thinking about should contain:
I don't want to work on that, personally...

But git already has a strategy for this. It is quite simple: git can give insane results when fed malformed objects, but it does not blindly trust it and let scripts segfault, corrupt something else, escalate privileges, etc. Remember that malformed objects might even have been received from a remote machine with "git fetch" --- one simply cannot trust objects beyond the assertion "each object is a stream of bytes".

And on the other hand, git maintains sanity by _preserving_ invariants and providing good behavior when it deals with valid objects. To a rough approximation, "valid object" is a synonym for "git fsck accepts it" (but that approximation is only modulo bugs --- git fsck has both false positives and false negatives). It is perfectly legitimate for commands to get confused and give a wrong result when working with invalid objects, and this ability is a nice thing because it allows for some optimizations.

As for the example at hand: perhaps we should teach "git fsck" to call commit objects without a blank line after the header invalid. After all, historical implementations of commands like "git cherry-pick" are not ready to cope with them. But that is no excuse to pretend they don't exist! While the best thing about your patch (and the most invasive aspect of it) is that it improves that situation, for some reason you don't want to document this important aspect of its impact. It leaves me completely puzzled.

Ramkumar Ramachandra· Jul 27, 2011, 17:49 UTC · re: Jonathan Nieder · lore

Re: [PATCH 03/18] revert: Simplify and inline add_message_to_msg

Hi,
Jonathan Nieder writes:
Show 24 quoted lines
> But git already has a strategy for this.  It is quite simple: git can
> give insane results when fed malformed objects, but it does not blindly
> trust it and let scripts segfault, corrupt something else, escalate
> privileges, etc.  Remember that malformed objects might even have been
> received from a remote machine with "git fetch" --- one simply cannot
> trust objects beyond the assertion "each object is a stream of bytes".
>
> And on the other hand, git maintains sanity by _preserving_ invariants
> and providing good behavior when it deals with valid objects.  To a
> rough approximation, "valid object" is a synonym for "git fsck accepts
> it" (but that approximation is only modulo bugs --- git fsck has both
> false positives and false negatives).  It is perfectly legitimate for
> commands to get confused and give a wrong result when working with
> invalid objects, and this ability is a nice thing because it allows
> for some optimizations.
>
> As for the example at hand: perhaps we should teach "git fsck" to call
> commit objects without a blank line after the header invalid.  After
> all, historical implementations of commands like "git cherry-pick" are
> not ready to cope with them.  But that is no excuse to pretend they
> don't exist!  While the best thing about your patch (and the most
> invasive aspect of it) is that it improves that situation, for some
> reason you don't want to document this important aspect of its impact.
> It leaves me completely puzzled.

Ah, thanks for the elaborate explanation. I'll document this aspect in my patch accordingly.

-- Ram
Jonathan Nieder· Jul 27, 2011, 17:51 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 03/18] revert: Simplify and inline add_message_to_msg

Ramkumar Ramachandra wrote:
> Ah, thanks for the elaborate explanation.  I'll document this aspect
> in my patch accordingly.

Thanks. By the way, when an explanation is too elaborate, I'd be glad to hear feedback of the form "You could have just said X". :)

Ramkumar Ramachandra· Jul 27, 2011, 03:19 UTC · re: Ramkumar Ramachandra · lore

[PATCH 04/18] revert: Don't check lone argument in get_encoding

The get_encoding function has only one callsite, and its caller makes sure that a NULL argument isn't passed. Remove a string marked for translation that will never be shown by not unnecessarily double-checking the argument.

Suggested-by: Jonathan Nieder <jrnieder@gmail.com>
Suggested-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
---
 builtin/revert.c |    3 ---
 1 files changed, 0 insertions(+), 3 deletions(-)
Show changes to builtin/revert.c +0 −3
diff --git a/builtin/revert.c b/builtin/revert.c
index 7dfe295..30b39c0 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -167,9 +167,6 @@ static char *get_encoding(const char *message)
 {
 	const char *p = message, *eol;
 
-	if (!p)
-		die (_("Could not read commit message of %s"),
-				sha1_to_hex(commit->object.sha1));
 	while (*p && *p != '\n') {
 		for (eol = p + 1; *eol && *eol != '\n'; eol++)
 			; /* do nothing */
-- 
1.7.4.rc1.7.g2cf08.dirty
Jonathan Nieder· Jul 27, 2011, 04:32 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 04/18] revert: Don't check lone argument in get_encoding

Ramkumar Ramachandra wrote:
> The get_encoding function has only one callsite, and its caller makes
> sure that a NULL argument isn't passed.  Remove a string marked for
> translation that will never be shown by not unnecessarily
> double-checking the argument.
The above just doesn't parse for me.  Isn't the truth something like:
"The only place get_encoding uses the 'commit' global is when writing
 an error message explaining that its text was NULL.  Since the only
 caller to get_encoding already ensures that the text is not NULL, we
 can remove this check, with two beneficial consequences:
 1) the function doesn't use the "commit" global any more, bringing
    us closer to removing that global altogether;
 2) translators no longer need to localize this error message that
    would never be shown.
 In a sense, after this patch there is an implied assert() replacing
 the "if ... die()" from before, since the function dereferences the
 pointer to commit object text and would segfault immediately if some
 future caller starts passing in NULL."

Please forgive the unclear phrasing; it is getting close to the time I should sleep. Hopefully the intent is clear (i.e., please, imagine that the reader does not already know what is going on and help him).

Sorry to nitpick so.  The patch proper is obviously good.
Ramkumar Ramachandra· Jul 27, 2011, 03:19 UTC · re: Ramkumar Ramachandra · lore

[PATCH 05/18] revert: Rename no_replay to record_origin

The "-x" command-line option is used to record the name of the original commits being picked in the commit message. The variable corresponding to this option is named "no_replay" for historical reasons; the name is especially confusing because the term "replay" is used to describe what cherry-pick does (for example, in the documentation of the "--mainline" option). So, give the variable corresponding to the "-x" command-line option a better name: "record_origin".

Mentored-by: Jonathan Nieder <jrnieder@gmail.com>
Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
---
 builtin/revert.c |    8 ++++----
 1 files changed, 4 insertions(+), 4 deletions(-)
Show changes to builtin/revert.c +4 −4
diff --git a/builtin/revert.c b/builtin/revert.c
index 30b39c0..794c050 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -35,7 +35,7 @@ static const char * const cherry_pick_usage[] = {
 	NULL
 };
 
-static int edit, no_replay, no_commit, mainline, signoff, allow_ff;
+static int edit, record_origin, no_commit, mainline, signoff, allow_ff;
 static enum { REVERT, CHERRY_PICK } action;
 static struct commit *commit;
 static int commit_argc;
@@ -91,7 +91,7 @@ static void parse_args(int argc, const char **argv)
 
 	if (action == CHERRY_PICK) {
 		struct option cp_extra[] = {
-			OPT_BOOLEAN('x', NULL, &no_replay, "append commit name"),
+			OPT_BOOLEAN('x', NULL, &record_origin, "append commit name"),
 			OPT_BOOLEAN(0, "ff", &allow_ff, "allow fast-forward"),
 			OPT_END(),
 		};
@@ -464,7 +464,7 @@ static int do_pick_commit(void)
 			strbuf_addstr(&msgbuf, p);
 		}
 
-		if (no_replay) {
+		if (record_origin) {
 			strbuf_addstr(&msgbuf, "(cherry picked from commit ");
 			strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
 			strbuf_addstr(&msgbuf, ")\n");
@@ -559,7 +559,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
 			die(_("cherry-pick --ff cannot be used with --signoff"));
 		if (no_commit)
 			die(_("cherry-pick --ff cannot be used with --no-commit"));
-		if (no_replay)
+		if (record_origin)
 			die(_("cherry-pick --ff cannot be used with -x"));
 		if (edit)
 			die(_("cherry-pick --ff cannot be used with --edit"));
-- 
1.7.4.rc1.7.g2cf08.dirty
Ramkumar Ramachandra· Jul 27, 2011, 03:19 UTC · re: Ramkumar Ramachandra · lore

[PATCH 06/18] revert: Propogate errors upwards from do_pick_commit

Currently, revert_or_cherry_pick can fail in two ways. If it encounters a conflict, it returns a positive number indicating the intended exit status for the git wrapper to pass on; for all other errors, it calls die(). The latter behavior is inconsiderate towards callers, as it denies them the opportunity to recover from errors and do other things.

After this patch, revert_or_cherry_pick will still return a positive return value to indicate an exit status for conflicts as before, while for some other errors, it will print an error message and return -1 instead of die()-ing. The cmd_revert and cmd_cherry_pick are adjusted to handle the fatal errors by die()-ing themselves.

While the full benefits of this patch will only be seen once all the "die" calls are replaced with calls to "error", its immediate impact is to change some "fatal:" messages to say "error:" and to add a new "fatal: cherry-pick failed" message at the end when the operation fails.

Inspired-by: Christian Couder <chriscool@tuxfamily.org>
Mentored-by: Jonathan Nieder <jrnieder@gmail.com>
Helped-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
---
 builtin/revert.c |   67 ++++++++++++++++++++++++++++-------------------------
 1 files changed, 35 insertions(+), 32 deletions(-)
Show changes to builtin/revert.c +35 −32
diff --git a/builtin/revert.c b/builtin/revert.c
index 794c050..ee9b4cd 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -241,25 +241,20 @@ static struct tree *empty_tree(void)
 	return tree;
 }
 
-static NORETURN void die_dirty_index(const char *me)
+static int error_dirty_index(const char *me)
 {
-	if (read_cache_unmerged()) {
-		die_resolve_conflict(me);
-	} else {
-		if (advice_commit_before_merge) {
-			if (action == REVERT)
-				die(_("Your local changes would be overwritten by revert.\n"
-					  "Please, commit your changes or stash them to proceed."));
-			else
-				die(_("Your local changes would be overwritten by cherry-pick.\n"
-					  "Please, commit your changes or stash them to proceed."));
-		} else {
-			if (action == REVERT)
-				die(_("Your local changes would be overwritten by revert.\n"));
-			else
-				die(_("Your local changes would be overwritten by cherry-pick.\n"));
-		}
-	}
+	if (read_cache_unmerged())
+		return error_resolve_conflict(me);
+
+	/* Different translation strings for cherry-pick and revert */
+	if (action == CHERRY_PICK)
+		error(_("Your local changes would be overwritten by cherry-pick."));
+	else
+		error(_("Your local changes would be overwritten by revert."));
+
+	if (advice_commit_before_merge)
+		advise(_("Commit your changes or stash them to proceed."));
+	return -1;
 }
 
 static int fast_forward_to(const unsigned char *to, const unsigned char *from)
@@ -376,9 +371,9 @@ static int do_pick_commit(void)
 			die (_("Your index file is unmerged."));
 	} else {
 		if (get_sha1("HEAD", head))
-			die (_("You do not have a valid HEAD"));
+			return error(_("You do not have a valid HEAD"));
 		if (index_differs_from("HEAD", 0))
-			die_dirty_index(me);
+			return error_dirty_index(me);
 	}
 	discard_cache();
 
@@ -391,20 +386,20 @@ static int do_pick_commit(void)
 		struct commit_list *p;
 
 		if (!mainline)
-			die(_("Commit %s is a merge but no -m option was given."),
-			    sha1_to_hex(commit->object.sha1));
+			return error(_("Commit %s is a merge but no -m option was given."),
+				sha1_to_hex(commit->object.sha1));
 
 		for (cnt = 1, p = commit->parents;
 		     cnt != mainline && p;
 		     cnt++)
 			p = p->next;
 		if (cnt != mainline || !p)
-			die(_("Commit %s does not have parent %d"),
-			    sha1_to_hex(commit->object.sha1), mainline);
+			return error(_("Commit %s does not have parent %d"),
+				sha1_to_hex(commit->object.sha1), mainline);
 		parent = p->item;
 	} else if (0 < mainline)
-		die(_("Mainline was specified but commit %s is not a merge."),
-		    sha1_to_hex(commit->object.sha1));
+		return error(_("Mainline was specified but commit %s is not a merge."),
+			sha1_to_hex(commit->object.sha1));
 	else
 		parent = commit->parents->item;
 
@@ -414,12 +409,12 @@ static int do_pick_commit(void)
 	if (parent && parse_commit(parent) < 0)
 		/* TRANSLATORS: The first %s will be "revert" or
 		   "cherry-pick", the second %s a SHA1 */
-		die(_("%s: cannot parse parent commit %s"),
-		    me, sha1_to_hex(parent->object.sha1));
+		return error(_("%s: cannot parse parent commit %s"),
+			me, sha1_to_hex(parent->object.sha1));
 
 	if (get_message(commit->buffer, &msg) != 0)
-		die(_("Cannot get commit message for %s"),
-				sha1_to_hex(commit->object.sha1));
+		return error(_("Cannot get commit message for %s"),
+			sha1_to_hex(commit->object.sha1));
 
 	/*
 	 * "commit" is an existing commit.  We would want to apply
@@ -580,14 +575,22 @@ static int revert_or_cherry_pick(int argc, const char **argv)
 
 int cmd_revert(int argc, const char **argv, const char *prefix)
 {
+	int res;
 	if (isatty(0))
 		edit = 1;
 	action = REVERT;
-	return revert_or_cherry_pick(argc, argv);
+	res = revert_or_cherry_pick(argc, argv);
+	if (res < 0)
+		die(_("revert failed"));
+	return res;
 }
 
 int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
 {
+	int res;
 	action = CHERRY_PICK;
-	return revert_or_cherry_pick(argc, argv);
+	res = revert_or_cherry_pick(argc, argv);
+	if (res < 0)
+		die(_("cherry-pick failed"));
+	return res;
 }
-- 
1.7.4.rc1.7.g2cf08.dirty
Jonathan Nieder· Jul 27, 2011, 04:39 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 06/18] revert: Propogate errors upwards from do_pick_commit

Ramkumar Ramachandra wrote:
Show 6 quoted lines
> Currently, revert_or_cherry_pick can fail in two ways.  If it
> encounters a conflict, it returns a positive number indicating the
> intended exit status for the git wrapper to pass on; for all other
> errors, it calls die().  The latter behavior is inconsiderate towards
> callers, as it denies them the opportunity to recover from errors and
> do other things.

Patch 5 is good. I still find patch 6 unconvincing, since it makes the behavior inconsistent (there are still many ways for revert_or_cherry_pick to exit() or die()), does not help with anything that comes later, and it is more of a burden than most of the rest of the patches to review (since one has to look at context not contained in the diff to see if it is safe to continue running after an error that previously would exit). In the long term, I really do want this change, though. If you'd like, I can rebase to put it at the end of the series (which shouldn't be hard).

Ramkumar Ramachandra· Jul 27, 2011, 09:47 UTC · re: Jonathan Nieder · lore

Re: [PATCH 06/18] revert: Propogate errors upwards from do_pick_commit

Hi Jonathan,
Jonathan Nieder writes:
Show 7 quoted lines
> Patch 5 is good.  I still find patch 6 unconvincing, since it makes
> the behavior inconsistent (there are still many ways for
> revert_or_cherry_pick to exit() or die()), does not help with anything
> that comes later, and it is more of a burden than most of the rest of
> the patches to review (since one has to look at context not contained
> in the diff to see if it is safe to continue running after an error
> that previously would exit).
:sigh:
> In the long term, I really do want this
> change, though.  If you'd like, I can rebase to put it at the end of
> the series (which shouldn't be hard).
That would be awesome -- the rebase won't be easy at all though.
-- Ram
Ramkumar Ramachandra· Jul 27, 2011, 03:19 UTC · re: Ramkumar Ramachandra · lore

[PATCH 07/18] revert: Eliminate global "commit" variable

Functions which act on commits currently rely on a file-scope static variable to be set before they're called. Consequently, the API and corresponding callsites are ugly and unclear. Remove this variable and change their API to accept the commit to act on as additional argument so that the callsites change from looking like

commit = prepare_a_commit(); act_on_commit();

to looking like

commit = prepare_a_commit(); act_on_commit(commit);

This change is also in line with our long-term goal of exposing some of these functions through a public API.

Inspired-by: Christian Couder <chriscool@tuxfamily.org>
Helped-by: Jonathan Nieder <jrnieder@gmail.com>
Helped-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
---
 builtin/revert.c |   22 +++++++++++-----------
 1 files changed, 11 insertions(+), 11 deletions(-)
Show changes to builtin/revert.c +11 −11
diff --git a/builtin/revert.c b/builtin/revert.c
index ee9b4cd..7c4a9c5 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -37,7 +37,6 @@ static const char * const cherry_pick_usage[] = {
 
 static int edit, record_origin, no_commit, mainline, signoff, allow_ff;
 static enum { REVERT, CHERRY_PICK } action;
-static struct commit *commit;
 static int commit_argc;
 static const char **commit_argv;
 static int allow_rerere_auto;
@@ -116,25 +115,25 @@ struct commit_message {
 	const char *message;
 };
 
-static int get_message(const char *raw_message, struct commit_message *out)
+static int get_message(struct commit *commit, struct commit_message *out)
 {
 	const char *encoding;
 	const char *abbrev, *subject;
 	int abbrev_len, subject_len;
 	char *q;
 
-	if (!raw_message)
+	if (!commit->buffer)
 		return -1;
-	encoding = get_encoding(raw_message);
+	encoding = get_encoding(commit->buffer);
 	if (!encoding)
 		encoding = "UTF-8";
 	if (!git_commit_encoding)
 		git_commit_encoding = "UTF-8";
 
 	out->reencoded_message = NULL;
-	out->message = raw_message;
+	out->message = commit->buffer;
 	if (strcmp(encoding, git_commit_encoding))
-		out->reencoded_message = reencode_string(raw_message,
+		out->reencoded_message = reencode_string(commit->buffer,
 					git_commit_encoding, encoding);
 	if (out->reencoded_message)
 		out->message = out->reencoded_message;
@@ -182,7 +181,7 @@ static char *get_encoding(const char *message)
 	return NULL;
 }
 
-static void write_cherry_pick_head(void)
+static void write_cherry_pick_head(struct commit *commit)
 {
 	int fd;
 	struct strbuf buf = STRBUF_INIT;
@@ -350,7 +349,7 @@ static int run_git_commit(const char *defmsg)
 	return run_command_v_opt(args, RUN_GIT_CMD);
 }
 
-static int do_pick_commit(void)
+static int do_pick_commit(struct commit *commit)
 {
 	unsigned char head[20];
 	struct commit *base, *next, *parent;
@@ -412,7 +411,7 @@ static int do_pick_commit(void)
 		return error(_("%s: cannot parse parent commit %s"),
 			me, sha1_to_hex(parent->object.sha1));
 
-	if (get_message(commit->buffer, &msg) != 0)
+	if (get_message(commit, &msg) != 0)
 		return error(_("Cannot get commit message for %s"),
 			sha1_to_hex(commit->object.sha1));
 
@@ -465,7 +464,7 @@ static int do_pick_commit(void)
 			strbuf_addstr(&msgbuf, ")\n");
 		}
 		if (!no_commit)
-			write_cherry_pick_head();
+			write_cherry_pick_head(commit);
 	}
 
 	if (!strategy || !strcmp(strategy, "recursive") || action == REVERT) {
@@ -543,6 +542,7 @@ static void read_and_refresh_cache(const char *me)
 static int revert_or_cherry_pick(int argc, const char **argv)
 {
 	struct rev_info revs;
+	struct commit *commit;
 
 	git_config(git_default_config, NULL);
 	me = action == REVERT ? "revert" : "cherry-pick";
@@ -565,7 +565,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
 	prepare_revs(&revs);
 
 	while ((commit = get_revision(&revs))) {
-		int res = do_pick_commit();
+		int res = do_pick_commit(commit);
 		if (res)
 			return res;
 	}
-- 
1.7.4.rc1.7.g2cf08.dirty
Jonathan Nieder· Jul 27, 2011, 04:43 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 07/18] revert: Eliminate global "commit" variable

Ramkumar Ramachandra wrote:
Show 13 quoted lines
> Functions which act on commits currently rely on a file-scope static
> variable to be set before they're called.  Consequently, the API and
> corresponding callsites are ugly and unclear.  Remove this variable
> and change their API to accept the commit to act on as additional
> argument so that the callsites change from looking like
>
> commit = prepare_a_commit();
> act_on_commit();
>
> to looking like
>
> commit = prepare_a_commit();
> act_on_commit(commit);
Very sane, and from a cursory look, it seems to be implemented well.

Tiny commit message nit: code, diagrams, and other text for which line breaks are significant are more easily read when indented so the reader knows you are not just editing with a funny line width (e.g., grepping for : at ends of lines in the pager shown by "git log" should show some examples).

Ramkumar Ramachandra· Jul 27, 2011, 08:59 UTC · re: Jonathan Nieder · lore

Re: [PATCH 07/18] revert: Eliminate global "commit" variable

Hi,
Jonathan Nieder writes:
Show 5 quoted lines
> Tiny commit message nit: code, diagrams, and other text for which line
> breaks are significant are more easily read when indented so the
> reader knows you are not just editing with a funny line width (e.g.,
> grepping for : at ends of lines in the pager shown by "git log" should
> show some examples).
Fixed.  Thanks.
-- Ram
Ramkumar Ramachandra· Jul 27, 2011, 03:19 UTC · re: Ramkumar Ramachandra · lore

[PATCH 08/18] revert: Introduce struct to keep command-line options

The current code uses a set of file-scope static variables to tell the cherry-pick/ revert machinery how to replay the changes, and initializes them by parsing the command-line arguments. In later steps in this series, we would like to introduce an API function that calls into this machinery directly and have a way to tell it what to do. Hence, introduce a structure to group these variables, so that the API can take them as a single replay_options parameter. The only exception is the variable "me" -- remove it since it not an independent option, and can be inferred from the action.

Unfortunately, this patch introduces a minor regression. Parsing strategy-option violates a C89 rule: Initializers cannot refer to variables whose address is not known at compile time. Currently, this rule is violated by some other parts of Git as well, and it is possible to get GCC to report these instances using the "-std=c89 -pedantic" option.

Inspired-by: Christian Couder <chriscool@tuxfamily.org>
Mentored-by: Jonathan Nieder <jrnieder@gmail.com>
Helped-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
---
 builtin/revert.c |  199 ++++++++++++++++++++++++++++++------------------------
 1 files changed, 112 insertions(+), 87 deletions(-)
Show changes to builtin/revert.c +112 −87
diff --git a/builtin/revert.c b/builtin/revert.c
index 7c4a9c5..58e0dd5 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -35,76 +35,94 @@ static const char * const cherry_pick_usage[] = {
 	NULL
 };
 
-static int edit, record_origin, no_commit, mainline, signoff, allow_ff;
-static enum { REVERT, CHERRY_PICK } action;
-static int commit_argc;
-static const char **commit_argv;
-static int allow_rerere_auto;
-
-static const char *me;
-
-/* Merge strategy. */
-static const char *strategy;
-static const char **xopts;
-static size_t xopts_nr, xopts_alloc;
+enum replay_action { REVERT, CHERRY_PICK };
+
+struct replay_opts {
+	enum replay_action action;
+
+	/* Boolean options */
+	int edit;
+	int record_origin;
+	int no_commit;
+	int signoff;
+	int allow_ff;
+	int allow_rerere_auto;
+
+	int mainline;
+	int commit_argc;
+	const char **commit_argv;
+
+	/* Merge strategy */
+	const char *strategy;
+	const char **xopts;
+	size_t xopts_nr, xopts_alloc;
+};
 
 #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
 
+static const char *action_name(const struct replay_opts *opts)
+{
+	return opts->action == REVERT ? "revert" : "cherry-pick";
+}
+
 static char *get_encoding(const char *message);
 
-static const char * const *revert_or_cherry_pick_usage(void)
+static const char * const *revert_or_cherry_pick_usage(struct replay_opts *opts)
 {
-	return action == REVERT ? revert_usage : cherry_pick_usage;
+	return opts->action == REVERT ? revert_usage : cherry_pick_usage;
 }
 
 static int option_parse_x(const struct option *opt,
 			  const char *arg, int unset)
 {
+	struct replay_opts **opts_ptr = opt->value;
+	struct replay_opts *opts = *opts_ptr;
+
 	if (unset)
 		return 0;
 
-	ALLOC_GROW(xopts, xopts_nr + 1, xopts_alloc);
-	xopts[xopts_nr++] = xstrdup(arg);
+	ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc);
+	opts->xopts[opts->xopts_nr++] = xstrdup(arg);
 	return 0;
 }
 
-static void parse_args(int argc, const char **argv)
+static void parse_args(int argc, const char **argv, struct replay_opts *opts)
 {
-	const char * const * usage_str = revert_or_cherry_pick_usage();
+	const char * const * usage_str = revert_or_cherry_pick_usage(opts);
 	int noop;
 	struct option options[] = {
-		OPT_BOOLEAN('n', "no-commit", &no_commit, "don't automatically commit"),
-		OPT_BOOLEAN('e', "edit", &edit, "edit the commit message"),
+		OPT_BOOLEAN('n', "no-commit", &opts->no_commit, "don't automatically commit"),
+		OPT_BOOLEAN('e', "edit", &opts->edit, "edit the commit message"),
 		{ OPTION_BOOLEAN, 'r', NULL, &noop, NULL, "no-op (backward compatibility)",
 		  PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 0 },
-		OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
-		OPT_INTEGER('m', "mainline", &mainline, "parent number"),
-		OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
-		OPT_STRING(0, "strategy", &strategy, "strategy", "merge strategy"),
-		OPT_CALLBACK('X', "strategy-option", &xopts, "option",
+		OPT_BOOLEAN('s', "signoff", &opts->signoff, "add Signed-off-by:"),
+		OPT_INTEGER('m', "mainline", &opts->mainline, "parent number"),
+		OPT_RERERE_AUTOUPDATE(&opts->allow_rerere_auto),
+		OPT_STRING(0, "strategy", &opts->strategy, "strategy", "merge strategy"),
+		OPT_CALLBACK('X', "strategy-option", &opts, "option",
 			"option for merge strategy", option_parse_x),
 		OPT_END(),
 		OPT_END(),
 		OPT_END(),
 	};
 
-	if (action == CHERRY_PICK) {
+	if (opts->action == CHERRY_PICK) {
 		struct option cp_extra[] = {
-			OPT_BOOLEAN('x', NULL, &record_origin, "append commit name"),
-			OPT_BOOLEAN(0, "ff", &allow_ff, "allow fast-forward"),
+			OPT_BOOLEAN('x', NULL, &opts->record_origin, "append commit name"),
+			OPT_BOOLEAN(0, "ff", &opts->allow_ff, "allow fast-forward"),
 			OPT_END(),
 		};
 		if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra))
 			die(_("program error"));
 	}
 
-	commit_argc = parse_options(argc, argv, NULL, options, usage_str,
-				    PARSE_OPT_KEEP_ARGV0 |
-				    PARSE_OPT_KEEP_UNKNOWN);
-	if (commit_argc < 2)
+	opts->commit_argc = parse_options(argc, argv, NULL, options, usage_str,
+					PARSE_OPT_KEEP_ARGV0 |
+					PARSE_OPT_KEEP_UNKNOWN);
+	if (opts->commit_argc < 2)
 		usage_with_options(usage_str, options);
 
-	commit_argv = argv;
+	opts->commit_argv = argv;
 }
 
 struct commit_message {
@@ -240,13 +258,13 @@ static struct tree *empty_tree(void)
 	return tree;
 }
 
-static int error_dirty_index(const char *me)
+static int error_dirty_index(struct replay_opts *opts)
 {
 	if (read_cache_unmerged())
-		return error_resolve_conflict(me);
+		return error_resolve_conflict(action_name(opts));
 
 	/* Different translation strings for cherry-pick and revert */
-	if (action == CHERRY_PICK)
+	if (opts->action == CHERRY_PICK)
 		error(_("Your local changes would be overwritten by cherry-pick."));
 	else
 		error(_("Your local changes would be overwritten by revert."));
@@ -269,7 +287,8 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from)
 
 static int do_recursive_merge(struct commit *base, struct commit *next,
 			      const char *base_label, const char *next_label,
-			      unsigned char *head, struct strbuf *msgbuf)
+			      unsigned char *head, struct strbuf *msgbuf,
+			      struct replay_opts *opts)
 {
 	struct merge_options o;
 	struct tree *result, *next_tree, *base_tree, *head_tree;
@@ -290,7 +309,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
 	next_tree = next ? next->tree : empty_tree();
 	base_tree = base ? base->tree : empty_tree();
 
-	for (xopt = xopts; xopt != xopts + xopts_nr; xopt++)
+	for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++)
 		parse_merge_opt(&o, *xopt);
 
 	clean = merge_trees(&o,
@@ -301,7 +320,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
 	    (write_cache(index_fd, active_cache, active_nr) ||
 	     commit_locked_index(&index_lock)))
 		/* TRANSLATORS: %s will be "revert" or "cherry-pick" */
-		die(_("%s: Unable to write new index file"), me);
+		die(_("%s: Unable to write new index file"), action_name(opts));
 	rollback_lock_file(&index_lock);
 
 	if (!clean) {
@@ -330,7 +349,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
  * If we are revert, or if our cherry-pick results in a hand merge,
  * we had better say that the current user is responsible for that.
  */
-static int run_git_commit(const char *defmsg)
+static int run_git_commit(const char *defmsg, struct replay_opts *opts)
 {
 	/* 6 is max possible length of our args array including NULL */
 	const char *args[6];
@@ -338,9 +357,9 @@ static int run_git_commit(const char *defmsg)
 
 	args[i++] = "commit";
 	args[i++] = "-n";
-	if (signoff)
+	if (opts->signoff)
 		args[i++] = "-s";
-	if (!edit) {
+	if (!opts->edit) {
 		args[i++] = "-F";
 		args[i++] = defmsg;
 	}
@@ -349,7 +368,7 @@ static int run_git_commit(const char *defmsg)
 	return run_command_v_opt(args, RUN_GIT_CMD);
 }
 
-static int do_pick_commit(struct commit *commit)
+static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
 {
 	unsigned char head[20];
 	struct commit *base, *next, *parent;
@@ -359,7 +378,7 @@ static int do_pick_commit(struct commit *commit)
 	struct strbuf msgbuf = STRBUF_INIT;
 	int res;
 
-	if (no_commit) {
+	if (opts->no_commit) {
 		/*
 		 * We do not intend to commit immediately.  We just want to
 		 * merge the differences in, so let's compute the tree
@@ -372,7 +391,7 @@ static int do_pick_commit(struct commit *commit)
 		if (get_sha1("HEAD", head))
 			return error(_("You do not have a valid HEAD"));
 		if (index_differs_from("HEAD", 0))
-			return error_dirty_index(me);
+			return error_dirty_index(opts);
 	}
 	discard_cache();
 
@@ -384,32 +403,32 @@ static int do_pick_commit(struct commit *commit)
 		int cnt;
 		struct commit_list *p;
 
-		if (!mainline)
+		if (!opts->mainline)
 			return error(_("Commit %s is a merge but no -m option was given."),
 				sha1_to_hex(commit->object.sha1));
 
 		for (cnt = 1, p = commit->parents;
-		     cnt != mainline && p;
+		     cnt != opts->mainline && p;
 		     cnt++)
 			p = p->next;
-		if (cnt != mainline || !p)
+		if (cnt != opts->mainline || !p)
 			return error(_("Commit %s does not have parent %d"),
-				sha1_to_hex(commit->object.sha1), mainline);
+				sha1_to_hex(commit->object.sha1), opts->mainline);
 		parent = p->item;
-	} else if (0 < mainline)
+	} else if (0 < opts->mainline)
 		return error(_("Mainline was specified but commit %s is not a merge."),
 			sha1_to_hex(commit->object.sha1));
 	else
 		parent = commit->parents->item;
 
-	if (allow_ff && parent && !hashcmp(parent->object.sha1, head))
+	if (opts->allow_ff && parent && !hashcmp(parent->object.sha1, head))
 		return fast_forward_to(commit->object.sha1, head);
 
 	if (parent && parse_commit(parent) < 0)
 		/* TRANSLATORS: The first %s will be "revert" or
 		   "cherry-pick", the second %s a SHA1 */
 		return error(_("%s: cannot parse parent commit %s"),
-			me, sha1_to_hex(parent->object.sha1));
+			action_name(opts), sha1_to_hex(parent->object.sha1));
 
 	if (get_message(commit, &msg) != 0)
 		return error(_("Cannot get commit message for %s"),
@@ -424,7 +443,7 @@ static int do_pick_commit(struct commit *commit)
 
 	defmsg = git_pathdup("MERGE_MSG");
 
-	if (action == REVERT) {
+	if (opts->action == REVERT) {
 		base = commit;
 		base_label = msg.label;
 		next = parent;
@@ -458,18 +477,18 @@ static int do_pick_commit(struct commit *commit)
 			strbuf_addstr(&msgbuf, p);
 		}
 
-		if (record_origin) {
+		if (opts->record_origin) {
 			strbuf_addstr(&msgbuf, "(cherry picked from commit ");
 			strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
 			strbuf_addstr(&msgbuf, ")\n");
 		}
-		if (!no_commit)
+		if (!opts->no_commit)
 			write_cherry_pick_head(commit);
 	}
 
-	if (!strategy || !strcmp(strategy, "recursive") || action == REVERT) {
+	if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REVERT) {
 		res = do_recursive_merge(base, next, base_label, next_label,
-					 head, &msgbuf);
+					 head, &msgbuf, opts);
 		write_message(&msgbuf, defmsg);
 	} else {
 		struct commit_list *common = NULL;
@@ -479,23 +498,23 @@ static int do_pick_commit(struct commit *commit)
 
 		commit_list_insert(base, &common);
 		commit_list_insert(next, &remotes);
-		res = try_merge_command(strategy, xopts_nr, xopts, common,
-					sha1_to_hex(head), remotes);
+		res = try_merge_command(opts->strategy, opts->xopts_nr, opts->xopts,
+					common, sha1_to_hex(head), remotes);
 		free_commit_list(common);
 		free_commit_list(remotes);
 	}
 
 	if (res) {
-		error(action == REVERT
+		error(opts->action == REVERT
 		      ? _("could not revert %s... %s")
 		      : _("could not apply %s... %s"),
 		      find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV),
 		      msg.subject);
 		print_advice();
-		rerere(allow_rerere_auto);
+		rerere(opts->allow_rerere_auto);
 	} else {
-		if (!no_commit)
-			res = run_git_commit(defmsg);
+		if (!opts->no_commit)
+			res = run_git_commit(defmsg, opts);
 	}
 
 	free_message(&msg);
@@ -504,18 +523,18 @@ static int do_pick_commit(struct commit *commit)
 	return res;
 }
 
-static void prepare_revs(struct rev_info *revs)
+static void prepare_revs(struct rev_info *revs, struct replay_opts *opts)
 {
 	int argc;
 
 	init_revisions(revs, NULL);
 	revs->no_walk = 1;
-	if (action != REVERT)
+	if (opts->action != REVERT)
 		revs->reverse = 1;
 
-	argc = setup_revisions(commit_argc, commit_argv, revs, NULL);
+	argc = setup_revisions(opts->commit_argc, opts->commit_argv, revs, NULL);
 	if (argc > 1)
-		usage(*revert_or_cherry_pick_usage());
+		usage(*revert_or_cherry_pick_usage(opts));
 
 	if (prepare_revision_walk(revs))
 		die(_("revision walk setup failed"));
@@ -524,48 +543,48 @@ static void prepare_revs(struct rev_info *revs)
 		die(_("empty commit set passed"));
 }
 
-static void read_and_refresh_cache(const char *me)
+static void read_and_refresh_cache(struct replay_opts *opts)
 {
 	static struct lock_file index_lock;
 	int index_fd = hold_locked_index(&index_lock, 0);
 	if (read_index_preload(&the_index, NULL) < 0)
-		die(_("git %s: failed to read the index"), me);
+		die(_("git %s: failed to read the index"), action_name(opts));
 	refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
 	if (the_index.cache_changed) {
 		if (write_index(&the_index, index_fd) ||
 		    commit_locked_index(&index_lock))
-			die(_("git %s: failed to refresh the index"), me);
+			die(_("git %s: failed to refresh the index"), action_name(opts));
 	}
 	rollback_lock_file(&index_lock);
 }
 
-static int revert_or_cherry_pick(int argc, const char **argv)
+static int revert_or_cherry_pick(int argc, const char **argv,
+				struct replay_opts *opts)
 {
 	struct rev_info revs;
 	struct commit *commit;
 
 	git_config(git_default_config, NULL);
-	me = action == REVERT ? "revert" : "cherry-pick";
-	setenv(GIT_REFLOG_ACTION, me, 0);
-	parse_args(argc, argv);
+	setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
+	parse_args(argc, argv, opts);
 
-	if (allow_ff) {
-		if (signoff)
+	if (opts->allow_ff) {
+		if (opts->signoff)
 			die(_("cherry-pick --ff cannot be used with --signoff"));
-		if (no_commit)
+		if (opts->no_commit)
 			die(_("cherry-pick --ff cannot be used with --no-commit"));
-		if (record_origin)
+		if (opts->record_origin)
 			die(_("cherry-pick --ff cannot be used with -x"));
-		if (edit)
+		if (opts->edit)
 			die(_("cherry-pick --ff cannot be used with --edit"));
 	}
 
-	read_and_refresh_cache(me);
+	read_and_refresh_cache(opts);
 
-	prepare_revs(&revs);
+	prepare_revs(&revs, opts);
 
 	while ((commit = get_revision(&revs))) {
-		int res = do_pick_commit(commit);
+		int res = do_pick_commit(commit, opts);
 		if (res)
 			return res;
 	}
@@ -576,10 +595,13 @@ static int revert_or_cherry_pick(int argc, const char **argv)
 int cmd_revert(int argc, const char **argv, const char *prefix)
 {
 	int res;
+	struct replay_opts opts;
+
+	memset(&opts, 0, sizeof(struct replay_opts));
 	if (isatty(0))
-		edit = 1;
-	action = REVERT;
-	res = revert_or_cherry_pick(argc, argv);
+		opts.edit = 1;
+	opts.action = REVERT;
+	res = revert_or_cherry_pick(argc, argv, &opts);
 	if (res < 0)
 		die(_("revert failed"));
 	return res;
@@ -588,8 +610,11 @@ int cmd_revert(int argc, const char **argv, const char *prefix)
 int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
 {
 	int res;
-	action = CHERRY_PICK;
-	res = revert_or_cherry_pick(argc, argv);
+	struct replay_opts opts;
+
+	memset(&opts, 0, sizeof(struct replay_opts));
+	opts.action = CHERRY_PICK;
+	res = revert_or_cherry_pick(argc, argv, &opts);
 	if (res < 0)
 		die(_("cherry-pick failed"));
 	return res;
-- 
1.7.4.rc1.7.g2cf08.dirty
Ramkumar Ramachandra· Jul 27, 2011, 03:19 UTC · re: Ramkumar Ramachandra · lore

[PATCH 09/18] revert: Separate cmdline parsing from functional code

Currently, revert_or_cherry_pick sets up a default git config, parses command-line arguments, before preparing to pick commits. This makes for a bad API as the central worry of callers is to assert whether or not a conflict occured while cherry picking. The current API is like:

if (revert_or_cherry_pick(argc, argv, opts) < 0)
   print "Something failed, we're not sure what"

Simplify and rename revert_or_cherry_pick to pick_commits so that it only has the responsibility of setting up the revision walker and picking commits in a loop. Transfer the remaining work to its callers. Now, the API is simplified as:

if (parse_args(argc, argv, opts) < 0)
   print "Can't parse arguments"
if (pick_commits(opts) < 0)
   print "Error encountered in picking machinery"

Later in the series, pick_commits will also serve as the starting point for continuing a cherry-pick or revert.

Inspired-by: Christian Couder <chriscool@tuxfamily.org>
Helped-by: Jonathan Nieder <jrnieder@gmail.com>
Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
---
 builtin/revert.c |   14 +++++++-------
 1 files changed, 7 insertions(+), 7 deletions(-)
Show changes to builtin/revert.c +7 −7
diff --git a/builtin/revert.c b/builtin/revert.c
index 58e0dd5..ab7f0bb 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -558,16 +558,12 @@ static void read_and_refresh_cache(struct replay_opts *opts)
 	rollback_lock_file(&index_lock);
 }
 
-static int revert_or_cherry_pick(int argc, const char **argv,
-				struct replay_opts *opts)
+static int pick_commits(struct replay_opts *opts)
 {
 	struct rev_info revs;
 	struct commit *commit;
 
-	git_config(git_default_config, NULL);
 	setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
-	parse_args(argc, argv, opts);
-
 	if (opts->allow_ff) {
 		if (opts->signoff)
 			die(_("cherry-pick --ff cannot be used with --signoff"));
@@ -601,7 +597,9 @@ int cmd_revert(int argc, const char **argv, const char *prefix)
 	if (isatty(0))
 		opts.edit = 1;
 	opts.action = REVERT;
-	res = revert_or_cherry_pick(argc, argv, &opts);
+	git_config(git_default_config, NULL);
+	parse_args(argc, argv, &opts);
+	res = pick_commits(&opts);
 	if (res < 0)
 		die(_("revert failed"));
 	return res;
@@ -614,7 +612,9 @@ int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
 
 	memset(&opts, 0, sizeof(struct replay_opts));
 	opts.action = CHERRY_PICK;
-	res = revert_or_cherry_pick(argc, argv, &opts);
+	git_config(git_default_config, NULL);
+	parse_args(argc, argv, &opts);
+	res = pick_commits(&opts);
 	if (res < 0)
 		die(_("cherry-pick failed"));
 	return res;
-- 
1.7.4.rc1.7.g2cf08.dirty
Ramkumar Ramachandra· Jul 27, 2011, 03:19 UTC · re: Ramkumar Ramachandra · lore

[PATCH 10/18] revert: Don't create invalid replay_opts in parse_args

The "--ff" command-line option cannot be used with some other command-line options. However, parse_args still parses these incompatible options into a replay_opts structure for use by the rest of the program. Although pick_commits, the current gatekeeper to the cherry-pick machinery, checks the validity of the replay_opts structure before before starting its operation, there will be multiple entry points to the cherry-pick machinery in future. To futureproof the code and catch these errors in one place, make sure that an invalid replay_opts structure is not created by parse_args in the first place. We still check the replay_opts structure for validity in pick_commits, but this is an assert() now to emphasize that it's the caller's responsibility to get it right.

Inspired-by: Christian Couder <chriscool@tuxfamily.org>
Mentored-by: Jonathan Nieder <jrnieder@gmail.com>
Helped-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
---
 builtin/revert.c |   38 +++++++++++++++++++++++++++-----------
 1 files changed, 27 insertions(+), 11 deletions(-)
Show changes to builtin/revert.c +27 −11
diff --git a/builtin/revert.c b/builtin/revert.c
index ab7f0bb..5e26a43 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -86,9 +86,26 @@ static int option_parse_x(const struct option *opt,
 	return 0;
 }
 
+static void verify_opt_compatible(const char *me, const char *base_opt, ...)
+{
+	const char *this_opt;
+	va_list ap;
+	int set;
+
+	va_start(ap, base_opt);
+	while ((this_opt = va_arg(ap, const char *))) {
+		set = va_arg(ap, int);
+		if (set)
+			die(_("%s: %s cannot be used with %s"),
+				me, this_opt, base_opt);
+	}
+	va_end(ap);
+}
+
 static void parse_args(int argc, const char **argv, struct replay_opts *opts)
 {
 	const char * const * usage_str = revert_or_cherry_pick_usage(opts);
+	const char *me = action_name(opts);
 	int noop;
 	struct option options[] = {
 		OPT_BOOLEAN('n', "no-commit", &opts->no_commit, "don't automatically commit"),
@@ -122,6 +139,13 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
 	if (opts->commit_argc < 2)
 		usage_with_options(usage_str, options);
 
+	if (opts->allow_ff)
+		verify_opt_compatible(me, "--ff",
+				"--signoff", opts->signoff,
+				"--no-commit", opts->no_commit,
+				"-x", opts->record_origin,
+				"--edit", opts->edit,
+				NULL);
 	opts->commit_argv = argv;
 }
 
@@ -564,17 +588,9 @@ static int pick_commits(struct replay_opts *opts)
 	struct commit *commit;
 
 	setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
-	if (opts->allow_ff) {
-		if (opts->signoff)
-			die(_("cherry-pick --ff cannot be used with --signoff"));
-		if (opts->no_commit)
-			die(_("cherry-pick --ff cannot be used with --no-commit"));
-		if (opts->record_origin)
-			die(_("cherry-pick --ff cannot be used with -x"));
-		if (opts->edit)
-			die(_("cherry-pick --ff cannot be used with --edit"));
-	}
-
+	if (opts->allow_ff)
+		assert(!(opts->signoff || opts->no_commit ||
+				opts->record_origin || opts->edit));
 	read_and_refresh_cache(opts);
 
 	prepare_revs(&revs, opts);
-- 
1.7.4.rc1.7.g2cf08.dirty
Jonathan Nieder· Jul 27, 2011, 04:46 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 10/18] revert: Don't create invalid replay_opts in parse_args

Ramkumar Ramachandra wrote:
> To futureproof
> the code and catch these errors in one place, make sure that an
> invalid replay_opts structure is not created by parse_args in the
> first place.

FWIW, patches 8-10 all have reasonable goals and look like they address them in a sane way from a quick glance.

Christian Couder· Jul 31, 2011, 12:31 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 10/18] revert: Don't create invalid replay_opts in parse_args

On Wednesday 27 July 2011 05:19:07 Ramkumar Ramachandra wrote:
Show 16 quoted lines
>
> +static void verify_opt_compatible(const char *me, const char *base_opt,
> ...) +{
> +	const char *this_opt;
> +	va_list ap;
> +	int set;
> +
> +	va_start(ap, base_opt);
> +	while ((this_opt = va_arg(ap, const char *))) {
> +		set = va_arg(ap, int);
> +		if (set)
> +			die(_("%s: %s cannot be used with %s"),
> +				me, this_opt, base_opt);
> +	}
> +	va_end(ap);
> +}
Question: returning in the middle of va_start() - va_end() may not be ok with 
some compilers, but I don't know how safe it is to exit()?
Show 9 quoted lines
> +	/*
> +	 * Sequence of picks finished successfully; cleanup by
> +	 * removing the .git/sequencer directory
> +	 */
> +	strbuf_reset(&buf);
> +	strbuf_addf(&buf, "%s", git_path(SEQ_DIR));
> +	remove_dir_recursively(&buf, 0);
>  	return 0;
>  }

The "strbuf_reset(&buf)" is not needed. But a "strbuf_release(&buf)" could be added before the return.

Thanks, Christian.

Ramkumar Ramachandra· Aug 1, 2011, 17:37 UTC · re: Christian Couder · lore

Re: [PATCH 10/18] revert: Don't create invalid replay_opts in parse_args

Hi Christian,
Christian Couder writes:
Show 19 quoted lines
> On Wednesday 27 July 2011 05:19:07 Ramkumar Ramachandra wrote:
>> +static void verify_opt_compatible(const char *me, const char *base_opt,
>> ...) +{
>> +     const char *this_opt;
>> +     va_list ap;
>> +     int set;
>> +
>> +     va_start(ap, base_opt);
>> +     while ((this_opt = va_arg(ap, const char *))) {
>> +             set = va_arg(ap, int);
>> +             if (set)
>> +                     die(_("%s: %s cannot be used with %s"),
>> +                             me, this_opt, base_opt);
>> +     }
>> +     va_end(ap);
>> +}
>
> Question: returning in the middle of va_start() - va_end() may not be ok with
> some compilers, but I don't know how safe it is to exit()?

Interesting observation. Even if it's not a problem, I suppose there's no harm in putting a va_end before the die() statement -- Valgrind will probably be happier anyway.

Show 12 quoted lines
>> +     /*
>> +      * Sequence of picks finished successfully; cleanup by
>> +      * removing the .git/sequencer directory
>> +      */
>> +     strbuf_reset(&buf);
>> +     strbuf_addf(&buf, "%s", git_path(SEQ_DIR));
>> +     remove_dir_recursively(&buf, 0);
>>       return 0;
>>  }
>
> The "strbuf_reset(&buf)" is not needed. But a "strbuf_release(&buf)" could be
> added before the return.
Right, thanks.  Fixed.
-- Ram
Ramkumar Ramachandra· Jul 27, 2011, 03:19 UTC · re: Ramkumar Ramachandra · lore

[PATCH 11/18] revert: Save data for continuing after conflict resolution

Ever since v1.7.2-rc1~4^2~7 (revert: allow cherry-picking more than one commit, 2010-06-02), a single invocation of "git cherry-pick" or "git revert" can perform picks of several individual commits. To implement features like "--continue" to continue the whole operation, we will need to store some information about the state and the plan at the beginning. Introduce a ".git/sequencer/head" file to store this state, and ".git/sequencer/todo" file to store the plan. The head file contains the SHA-1 of the HEAD before the start of the operation, and the todo file contains an instruction sheet whose format is inspired by the format of the "rebase -i" instruction sheet. As a result, a typical todo file looks like:

pick 8537f0e submodule add: test failure when url is not configured pick 4d68932 submodule add: allow relative repository path pick f22a17e submodule add: clean up duplicated code pick 59a5775 make copy_ref globally available

Since SHA-1 hex is abbreviated using an find_unique_abbrev(), it is unambiguous. This does not guarantee that there will be no ambiguity when more objects are added to the repository.

That these two files alone are not enough to implement a "--continue" that remembers the command-line options specified; later patches in the series save them too.

These new files are unrelated to the existing .git/CHERRY_PICK_HEAD, which will still be useful while committing after a conflict resolution.

Inspired-by: Christian Couder <chriscool@tuxfamily.org>
Helped-by: Jonathan Nieder <jrnieder@gmail.com>
Helped-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
---
 builtin/revert.c                |  222 +++++++++++++++++++++++++++++++++++++-
 t/t3510-cherry-pick-sequence.sh |   48 +++++++++
 2 files changed, 264 insertions(+), 6 deletions(-)
 create mode 100755 t/t3510-cherry-pick-sequence.sh
Show changes to 2 files +264 −6

builtin/revert.c, t/t3510-cherry-pick-sequence.sh

diff --git a/builtin/revert.c b/builtin/revert.c
index 5e26a43..b3b1bf8 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -13,6 +13,7 @@
 #include "rerere.h"
 #include "merge-recursive.h"
 #include "refs.h"
+#include "dir.h"
 
 /*
  * This implements the builtins revert and cherry-pick.
@@ -59,6 +60,11 @@ struct replay_opts {
 };
 
 #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
+#define MAYBE_UNUSED __attribute__((__unused__))
+
+#define SEQ_DIR         "sequencer"
+#define SEQ_HEAD_FILE   "sequencer/head"
+#define SEQ_TODO_FILE   "sequencer/todo"
 
 static const char *action_name(const struct replay_opts *opts)
 {
@@ -269,7 +275,7 @@ static void write_message(struct strbuf *msgbuf, const char *filename)
 		die_errno(_("Could not write to %s."), filename);
 	strbuf_release(msgbuf);
 	if (commit_lock_file(&msg_file) < 0)
-		die(_("Error wrapping up %s"), filename);
+		die(_("Error wrapping up %s."), filename);
 }
 
 static struct tree *empty_tree(void)
@@ -582,10 +588,199 @@ static void read_and_refresh_cache(struct replay_opts *opts)
 	rollback_lock_file(&index_lock);
 }
 
-static int pick_commits(struct replay_opts *opts)
+/*
+ * Append a commit to the end of the commit_list.
+ *
+ * next starts by pointing to the variable that holds the head of an
+ * empty commit_list, and is updated to point to the "next" field of
+ * the last item on the list as new commits are appended.
+ *
+ * Usage example:
+ *
+ *     struct commit_list *list;
+ *     struct commit_list **next = &list;
+ *
+ *     next = commit_list_append(c1, next);
+ *     next = commit_list_append(c2, next);
+ *     assert(commit_list_count(list) == 2);
+ *     return list;
+ */
+struct commit_list **commit_list_append(struct commit *commit,
+					struct commit_list **next)
+{
+	struct commit_list *new = xmalloc(sizeof(struct commit_list));
+	new->item = commit;
+	*next = new;
+	new->next = NULL;
+	return &new->next;
+}
+
+static int format_todo(struct strbuf *buf, struct commit_list *todo_list,
+		struct replay_opts *opts)
+{
+	struct commit_list *cur = NULL;
+	struct commit_message msg = { NULL, NULL, NULL, NULL, NULL };
+	const char *sha1_abbrev = NULL;
+	const char *action_str = opts->action == REVERT ? "revert" : "pick";
+
+	for (cur = todo_list; cur; cur = cur->next) {
+		sha1_abbrev = find_unique_abbrev(cur->item->object.sha1, DEFAULT_ABBREV);
+		if (get_message(cur->item, &msg))
+			return error(_("Cannot get commit message for %s"), sha1_abbrev);
+		strbuf_addf(buf, "%s %s %s\n", action_str, sha1_abbrev, msg.subject);
+	}
+	return 0;
+}
+
+static struct commit *parse_insn_line(char *start, struct replay_opts *opts)
+{
+	unsigned char commit_sha1[20];
+	char sha1_abbrev[40];
+	struct commit *commit;
+	enum replay_action action;
+	int insn_len = 0;
+	char *p, *q;
+
+	if (!prefixcmp(start, "pick ")) {
+		action = CHERRY_PICK;
+		insn_len = strlen("pick");
+		p = start + insn_len + 1;
+	}
+	else if (!prefixcmp(start, "revert ")) {
+		action = REVERT;
+		insn_len = strlen("revert");
+		p = start + insn_len + 1;
+	}
+	else
+		return NULL;
+
+	q = strchr(p, ' ');
+	if (!q)
+		return NULL;
+	q++;
+
+	strlcpy(sha1_abbrev, p, q - p);
+
+	/*
+	 * Verify that the action matches up with the one in
+	 * opts; we don't support arbitrary instructions
+	 */
+	if (action != opts->action) {
+		const char *action_str;
+		action_str = action == REVERT ? "revert" : "cherry-pick";
+		error(_("Cannot %s during a %s"), action_str, action_name(opts));
+		return NULL;
+	}
+
+	if ((get_sha1(sha1_abbrev, commit_sha1) < 0)
+		|| !(commit = lookup_commit_reference(commit_sha1)))
+		return NULL;
+
+	return commit;
+}
+
+static void MAYBE_UNUSED read_populate_todo(struct commit_list **todo_list,
+					struct replay_opts *opts)
+{
+	const char *todo_file = git_path(SEQ_TODO_FILE);
+	struct strbuf buf = STRBUF_INIT;
+	struct commit_list **next;
+	struct commit *commit;
+	char *p;
+	int fd;
+
+	fd = open(todo_file, O_RDONLY);
+	if (fd < 0) {
+		strbuf_release(&buf);
+		die_errno(_("Could not open %s."), todo_file);
+	}
+	if (strbuf_read(&buf, fd, 0) < buf.len) {
+		close(fd);
+		strbuf_release(&buf);
+		die(_("Could not read %s."), todo_file);
+	}
+	close(fd);
+
+	next = todo_list;
+	for (p = buf.buf; *p; p = strchr(p, '\n') + 1) {
+		if (!(commit = parse_insn_line(p, opts)))
+			goto error;
+		next = commit_list_append(commit, next);
+	}
+	if (!*todo_list)
+		goto error;
+	strbuf_release(&buf);
+	return;
+error:
+	strbuf_release(&buf);
+	die(_("Unusable instruction sheet: %s"), todo_file);
+}
+
+static void walk_revs_populate_todo(struct commit_list **todo_list,
+				struct replay_opts *opts)
 {
 	struct rev_info revs;
 	struct commit *commit;
+	struct commit_list **next;
+
+	prepare_revs(&revs, opts);
+
+	next = todo_list;
+	while ((commit = get_revision(&revs)))
+		next = commit_list_append(commit, next);
+}
+
+static void create_seq_dir(void)
+{
+	const char *seq_dir = git_path(SEQ_DIR);
+
+	if (!file_exists(seq_dir) && mkdir(seq_dir, 0777) < 0)
+		die_errno(_("Could not create sequencer directory '%s'."), seq_dir);
+}
+
+static void save_head(const char *head)
+{
+	const char *head_file = git_path(SEQ_HEAD_FILE);
+	static struct lock_file head_lock;
+	struct strbuf buf = STRBUF_INIT;
+	int fd;
+
+	fd = hold_lock_file_for_update(&head_lock, head_file, LOCK_DIE_ON_ERROR);
+	strbuf_addf(&buf, "%s\n", head);
+	if (write_in_full(fd, buf.buf, buf.len) < 0)
+		die_errno(_("Could not write to %s."), head_file);
+	if (commit_lock_file(&head_lock) < 0)
+		die(_("Error wrapping up %s."), head_file);
+}
+
+static void save_todo(struct commit_list *todo_list, struct replay_opts *opts)
+{
+	const char *todo_file = git_path(SEQ_TODO_FILE);
+	static struct lock_file todo_lock;
+	struct strbuf buf = STRBUF_INIT;
+	int fd;
+
+	fd = hold_lock_file_for_update(&todo_lock, todo_file, LOCK_DIE_ON_ERROR);
+	if (format_todo(&buf, todo_list, opts) < 0)
+		die(_("Could not format %s."), todo_file);
+	if (write_in_full(fd, buf.buf, buf.len) < 0) {
+		strbuf_release(&buf);
+		die_errno(_("Could not write to %s."), todo_file);
+	}
+	if (commit_lock_file(&todo_lock) < 0) {
+		strbuf_release(&buf);
+		die(_("Error wrapping up %s."), todo_file);
+	}
+	strbuf_release(&buf);
+}
+
+static int pick_commits(struct replay_opts *opts)
+{
+	struct commit_list *todo_list = NULL;
+	struct strbuf buf = STRBUF_INIT;
+	unsigned char sha1[20];
+	struct commit_list *cur;
+	int res;
 
 	setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
 	if (opts->allow_ff)
@@ -593,14 +788,29 @@ static int pick_commits(struct replay_opts *opts)
 				opts->record_origin || opts->edit));
 	read_and_refresh_cache(opts);
 
-	prepare_revs(&revs, opts);
-
-	while ((commit = get_revision(&revs))) {
-		int res = do_pick_commit(commit, opts);
+	walk_revs_populate_todo(&todo_list, opts);
+	create_seq_dir();
+	if (get_sha1("HEAD", sha1)) {
+		if (opts->action == REVERT)
+			return error(_("Can't revert as initial commit"));
+		return error(_("Can't cherry-pick into empty head"));
+	} else
+		save_head(sha1_to_hex(sha1));
+	save_todo(todo_list, opts);
+
+	for (cur = todo_list; cur; cur = cur->next) {
+		save_todo(cur, opts);
+		res = do_pick_commit(cur->item, opts);
 		if (res)
 			return res;
 	}
 
+	/*
+	 * Sequence of picks finished successfully; cleanup by
+	 * removing the .git/sequencer directory
+	 */
+	strbuf_addf(&buf, "%s", git_path(SEQ_DIR));
+	remove_dir_recursively(&buf, 0);
 	return 0;
 }
 
diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh
new file mode 100755
index 0000000..64eaa20
--- /dev/null
+++ b/t/t3510-cherry-pick-sequence.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+test_description='Test cherry-pick continuation features
+
+  + anotherpick: rewrites foo to d
+  + picked: rewrites foo to c
+  + unrelatedpick: rewrites unrelated to reallyunrelated
+  + base: rewrites foo to b
+  + initial: writes foo as a, unrelated as unrelated
+
+'
+
+. ./test-lib.sh
+
+pristine_detach () {
+	git checkout -f "$1^0" &&
+	git read-tree -u --reset HEAD &&
+	git clean -d -f -f -q -x
+}
+
+test_expect_success setup '
+	echo unrelated >unrelated &&
+	git add unrelated &&
+	test_commit initial foo a &&
+	test_commit base foo b &&
+	test_commit unrelatedpick unrelated reallyunrelated &&
+	test_commit picked foo c &&
+	test_commit anotherpick foo d &&
+	git config advice.detachedhead false
+
+'
+
+test_expect_success 'cherry-pick persists data on failure' '
+	pristine_detach initial &&
+	test_must_fail git cherry-pick base..anotherpick &&
+	test_path_is_dir .git/sequencer &&
+	test_path_is_file .git/sequencer/head &&
+	test_path_is_file .git/sequencer/todo &&
+	rm -rf .git/sequencer
+'
+
+test_expect_success 'cherry-pick cleans up sequencer state upon success' '
+	pristine_detach initial &&
+	git cherry-pick initial..picked &&
+	test_path_is_missing .git/sequencer
+'
+
+test_done
-- 
1.7.4.rc1.7.g2cf08.dirty
Jonathan Nieder· Jul 27, 2011, 05:02 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 11/18] revert: Save data for continuing after conflict resolution

Ramkumar Ramachandra wrote:
> --- a/builtin/revert.c
> +++ b/builtin/revert.c
[...]
Show 9 quoted lines
> @@ -269,7 +275,7 @@ static void write_message(struct strbuf *msgbuf, const char *filename)
>  		die_errno(_("Could not write to %s."), filename);
>  	strbuf_release(msgbuf);
>  	if (commit_lock_file(&msg_file) < 0)
> -		die(_("Error wrapping up %s"), filename);
> +		die(_("Error wrapping up %s."), filename);
>  }
>  
>  static struct tree *empty_tree(void)
Snuck in to the wrong patch, I guess?
[...]
> @@ -582,10 +588,199 @@ static struct commit *parse_insn_line(char *start, struct replay_opts *opts)
[...]
Show 13 quoted lines
> +	if (!prefixcmp(start, "pick ")) {
> +		action = CHERRY_PICK;
> +		insn_len = strlen("pick");
> +		p = start + insn_len + 1;
> +	}
> +	else if (!prefixcmp(start, "revert ")) {
> +		action = REVERT;
> +		insn_len = strlen("revert");
> +		p = start + insn_len + 1;
> +	}
> +	else
> +		return NULL;
> +
Style: git uses "cuddled braces".
	if (...) {
		...
	} else if (...) {
		...
	} ...
[...]
Show 5 quoted lines
> +	if ((get_sha1(sha1_abbrev, commit_sha1) < 0)
> +		|| !(commit = lookup_commit_reference(commit_sha1)))
> +		return NULL;
> +
> +	return commit;
Would be clearer to write:
	if (get_sha1(...))
		return NULL;
	return lookup_commit_reference(...);
[...]
Show 5 quoted lines
> @@ -59,6 +60,11 @@ struct replay_opts {
>  };
>  
>  #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
> +#define MAYBE_UNUSED __attribute__((__unused__))
[...]
> +}
> +
> +static void MAYBE_UNUSED read_populate_todo(struct commit_list **todo_list,
> +					struct replay_opts *opts)

This MAYBE_UNUSED with no conditional involved feels ugly. Why is it needed? Why not define read_populate_todo in the patch that first uses it, or barring that, tolerate a warning in this step within the patch series?

[...]
> +		if (!(commit = parse_insn_line(p, opts)))
> +			goto error;

See Documentation/CodingGuidelines: "We try to avoid assignments inside if()".

Show 20 quoted lines
> +static void walk_revs_populate_todo(struct commit_list **todo_list,
> +				struct replay_opts *opts)
>  {
>  	struct rev_info revs;
>  	struct commit *commit;
> +	struct commit_list **next;
> +
> +	prepare_revs(&revs, opts);
> +
> +	next = todo_list;
> +	while ((commit = get_revision(&revs)))
> +		next = commit_list_append(commit, next);
> +}
> +
> +static void create_seq_dir(void)
> +{
> +	const char *seq_dir = git_path(SEQ_DIR);
> +
> +	if (!file_exists(seq_dir) && mkdir(seq_dir, 0777) < 0)
> +		die_errno(_("Could not create sequencer directory '%s'."), seq_dir);
What happens if seq_dir exists and is a file?
[...]
> @@ -593,14 +788,29 @@ static int pick_commits(struct replay_opts *opts)
[...]
Show 6 quoted lines
> +	if (get_sha1("HEAD", sha1)) {
> +		if (opts->action == REVERT)
> +			return error(_("Can't revert as initial commit"));
> +		return error(_("Can't cherry-pick into empty head"));
> +	} else
> +		save_head(sha1_to_hex(sha1));
Clearer without the "else":
	if (get_sha1(...)) {
		...
		return error(...);
	}
	save_head(sha1);
Show 8 quoted lines
> +	save_todo(todo_list, opts);
> +
> +	for (cur = todo_list; cur; cur = cur->next) {
> +		save_todo(cur, opts);
> +		res = do_pick_commit(cur->item, opts);
>  		if (res)
>  			return res;
>  	}
Isn't the save_todo() before the "for" loop redundant?
Show 7 quoted lines
>  
> +	/*
> +	 * Sequence of picks finished successfully; cleanup by
> +	 * removing the .git/sequencer directory
> +	 */
> +	strbuf_addf(&buf, "%s", git_path(SEQ_DIR));
> +	remove_dir_recursively(&buf, 0);

What is the content of "buf" before? If it's supposed to be empty, I'd suggest using "strbuf_reset" to make sure that remains so for the reader's peace of mind.

Ciao, Jonathan

Ramkumar Ramachandra· Jul 27, 2011, 10:19 UTC · re: Jonathan Nieder · lore

Re: [PATCH 11/18] revert: Save data for continuing after conflict resolution

Hi,
Jonathan Nieder writes:
Show 16 quoted lines
> Ramkumar Ramachandra wrote:
>> @@ -59,6 +60,11 @@ struct replay_opts {
>>  };
>>
>>  #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
>> +#define MAYBE_UNUSED __attribute__((__unused__))
> [...]
>> +}
>> +
>> +static void MAYBE_UNUSED read_populate_todo(struct commit_list **todo_list,
>> +                                     struct replay_opts *opts)
>
> This MAYBE_UNUSED with no conditional involved feels ugly.  Why is it
> needed?  Why not define read_populate_todo in the patch that first
> uses it, or barring that, tolerate a warning in this step within the
> patch series?

Junio complained about the warnings here [1], so I added MAYBE_UNUSED to suppress them temporarily. Would you really like to see read_populate_todo and read_populate_opts being introduced MUCH later in the '--continue' patch?

[1]: http://thread.gmane.org/gmane.comp.version-control.git/176899
Thanks.
-- Ram
Jonathan Nieder· Jul 27, 2011, 15:56 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 11/18] revert: Save data for continuing after conflict resolution

Ramkumar Ramachandra wrote:
Show 6 quoted lines
> Junio complained about the warnings here [1], so I added MAYBE_UNUSED
> to suppress them temporarily.  Would you really like to see
> read_populate_todo and read_populate_opts being introduced MUCH later
> in the '--continue' patch?
>
> [1]: http://thread.gmane.org/gmane.comp.version-control.git/176899
Hmm.  Yes, I suppose I would.

But as usual, what is relevant is not what I would like but what leads to clear, functional, and maintainable code. It is from that point of view that the MAYBE_UNUSED is most problematic --- it suppresses a useful and true hint when compiling that the codebase implements some functionality that has no user. For example, the warning would be triggered if the function gets renamed and added again and this old version is forgotten.

Ramkumar Ramachandra· Jul 27, 2011, 18:03 UTC · re: Jonathan Nieder · lore

Re: [PATCH 11/18] revert: Save data for continuing after conflict resolution

Hi Jonathan,
Jonathan Nieder writes:
Show 9 quoted lines
> Ramkumar Ramachandra wrote:
>> Junio complained about the warnings here [1], so I added MAYBE_UNUSED
>> to suppress them temporarily.  Would you really like to see
>> read_populate_todo and read_populate_opts being introduced MUCH later
>> in the '--continue' patch?
>>
>> [1]: http://thread.gmane.org/gmane.comp.version-control.git/176899
>
> Hmm.  Yes, I suppose I would.
Fixed.
Show 7 quoted lines
> But as usual, what is relevant is not what I would like but what leads
> to clear, functional, and maintainable code.  It is from that point of
> view that the MAYBE_UNUSED is most problematic --- it suppresses a
> useful and true hint when compiling that the codebase implements some
> functionality that has no user.  For example, the warning would be
> triggered if the function gets renamed and added again and this old
> version is forgotten.

I actually think of it as a trade-off: On one hand, I want to introduce functions to read and write configuration files in the same patch. On the other hand, I want to introduce functions when they're actually used.

-- Ram
Junio C Hamano· Jul 27, 2011, 22:51 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 11/18] revert: Save data for continuing after conflict resolution

Ramkumar Ramachandra <artagnon@gmail.com> writes:
Show 12 quoted lines
> +	if (!prefixcmp(start, "pick ")) {
> +		action = CHERRY_PICK;
> +		insn_len = strlen("pick");
> +		p = start + insn_len + 1;
> +	}
> +	else if (!prefixcmp(start, "revert ")) {
> +		action = REVERT;
> +		insn_len = strlen("revert");
> +		p = start + insn_len + 1;
> +	}
> +	else
> +		return NULL;
(style)
        if (...) {
                ...
        } else if (...) {
                ...
        } else {
                ...
        }
Ramkumar Ramachandra· Jul 27, 2011, 03:19 UTC · re: Ramkumar Ramachandra · lore

[PATCH 12/18] revert: Save command-line options for continuing operation

In the same spirit as ".git/sequencer/head" and ".git/sequencer/todo", introduce ".git/sequencer/opts" to persist the replay_opts structure for continuing after a conflict resolution. Use the gitconfig format for this file so that it looks like:

[core]
	signoff = true
	record-origin = true
	mainline = 1
	strategy = recursive
	strategy-option = patience
	strategy-option = ours
Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
---
 builtin/revert.c                |   78 +++++++++++++++++++++++++++++++++++++++
 t/t3510-cherry-pick-sequence.sh |   28 +++++++++++++-
 2 files changed, 105 insertions(+), 1 deletions(-)
Show changes to 2 files +105 −1

builtin/revert.c, t/t3510-cherry-pick-sequence.sh

diff --git a/builtin/revert.c b/builtin/revert.c
index b3b1bf8..b02d3d2 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -65,6 +65,7 @@ struct replay_opts {
 #define SEQ_DIR         "sequencer"
 #define SEQ_HEAD_FILE   "sequencer/head"
 #define SEQ_TODO_FILE   "sequencer/todo"
+#define SEQ_OPTS_FILE   "sequencer/opts"
 
 static const char *action_name(const struct replay_opts *opts)
 {
@@ -716,6 +717,49 @@ error:
 	die(_("Unusable instruction sheet: %s"), todo_file);
 }
 
+static int populate_opts_cb(const char *key, const char *value, void *data)
+{
+	struct replay_opts *opts = data;
+	int error_flag = 1;
+
+	if (!value)
+		error_flag = 0;
+	else if (!strcmp(key, "core.no-commit"))
+		opts->no_commit = git_config_bool_or_int(key, value, &error_flag);
+	else if (!strcmp(key, "core.edit"))
+		opts->edit = git_config_bool_or_int(key, value, &error_flag);
+	else if (!strcmp(key, "core.signoff"))
+		opts->signoff = git_config_bool_or_int(key, value, &error_flag);
+	else if (!strcmp(key, "core.record-origin"))
+		opts->record_origin = git_config_bool_or_int(key, value, &error_flag);
+	else if (!strcmp(key, "core.allow-ff"))
+		opts->allow_ff = git_config_bool_or_int(key, value, &error_flag);
+	else if (!strcmp(key, "core.mainline"))
+		opts->mainline = git_config_int(key, value);
+	else if (!strcmp(key, "core.strategy"))
+		git_config_string(&opts->strategy, key, value);
+	else if (!strcmp(key, "core.strategy-option")) {
+		ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc);
+		opts->xopts[opts->xopts_nr++] = xstrdup(value);
+	} else
+		return error(_("Invalid key: %s"), key);
+
+	if (!error_flag)
+		return error(_("Invalid value for %s: %s"), key, value);
+
+	return 0;
+}
+
+static void MAYBE_UNUSED read_populate_opts(struct replay_opts **opts_ptr)
+{
+	const char *opts_file = git_path(SEQ_OPTS_FILE);
+
+	if (!file_exists(opts_file))
+		return;
+	if (git_config_from_file(populate_opts_cb, opts_file, *opts_ptr) < 0)
+		die(_("Malformed options sheet: %s"), opts_file);
+}
+
 static void walk_revs_populate_todo(struct commit_list **todo_list,
 				struct replay_opts *opts)
 {
@@ -774,6 +818,39 @@ static void save_todo(struct commit_list *todo_list, struct replay_opts *opts)
 	strbuf_release(&buf);
 }
 
+static void save_opts(struct replay_opts *opts)
+{
+	const char *opts_file = git_path(SEQ_OPTS_FILE);
+	struct strbuf buf = STRBUF_INIT;
+	int i;
+
+	if (opts->no_commit)
+		git_config_set_in_file(opts_file, "core.no-commit", "true");
+	if (opts->edit)
+		git_config_set_in_file(opts_file, "core.edit", "true");
+	if (opts->signoff)
+		git_config_set_in_file(opts_file, "core.signoff", "true");
+	if (opts->record_origin)
+		git_config_set_in_file(opts_file, "core.record-origin", "true");
+	if (opts->allow_ff)
+		git_config_set_in_file(opts_file, "core.allow-ff", "true");
+	if (opts->mainline) {
+		strbuf_reset(&buf);
+		strbuf_addf(&buf, "%d", opts->mainline);
+		git_config_set_in_file(opts_file, "core.mainline", buf.buf);
+	}
+	if (opts->strategy)
+		git_config_set_in_file(opts_file, "core.strategy", opts->strategy);
+	if (opts->xopts) {
+		for (i = 0; i < opts->xopts_nr; i ++)
+			git_config_set_multivar_in_file(opts_file,
+							"core.strategy-option",
+							opts->xopts[i], "^$", 0);
+	}
+
+	strbuf_release(&buf);
+}
+
 static int pick_commits(struct replay_opts *opts)
 {
 	struct commit_list *todo_list = NULL;
@@ -796,6 +873,7 @@ static int pick_commits(struct replay_opts *opts)
 		return error(_("Can't cherry-pick into empty head"));
 	} else
 		save_head(sha1_to_hex(sha1));
+	save_opts(opts);
 	save_todo(todo_list, opts);
 
 	for (cur = todo_list; cur; cur = cur->next) {
diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh
index 64eaa20..79d868f 100755
--- a/t/t3510-cherry-pick-sequence.sh
+++ b/t/t3510-cherry-pick-sequence.sh
@@ -32,10 +32,36 @@ test_expect_success setup '
 
 test_expect_success 'cherry-pick persists data on failure' '
 	pristine_detach initial &&
-	test_must_fail git cherry-pick base..anotherpick &&
+	test_must_fail git cherry-pick -s base..anotherpick &&
 	test_path_is_dir .git/sequencer &&
 	test_path_is_file .git/sequencer/head &&
 	test_path_is_file .git/sequencer/todo &&
+	test_path_is_file .git/sequencer/opts &&
+	rm -rf .git/sequencer
+'
+
+test_expect_success 'cherry-pick persists opts correctly' '
+	pristine_detach initial &&
+	test_must_fail git cherry-pick -s -m 1 --strategy=recursive -X patience -X ours base..anotherpick &&
+	test_path_is_dir .git/sequencer &&
+	test_path_is_file .git/sequencer/head &&
+	test_path_is_file .git/sequencer/todo &&
+	test_path_is_file .git/sequencer/opts &&
+	echo "true" >expect
+	git config --file=.git/sequencer/opts --get-all core.signoff >actual &&
+	test_cmp expect actual &&
+	echo "1" >expect
+	git config --file=.git/sequencer/opts --get-all core.mainline >actual &&
+	test_cmp expect actual &&
+	echo "recursive" >expect
+	git config --file=.git/sequencer/opts --get-all core.strategy >actual &&
+	test_cmp expect actual &&
+	cat >expect <<-\EOF
+	patience
+	ours
+	EOF
+	git config --file=.git/sequencer/opts --get-all core.strategy-option >actual &&
+	test_cmp expect actual &&
 	rm -rf .git/sequencer
 '
 
-- 
1.7.4.rc1.7.g2cf08.dirty
Jonathan Nieder· Jul 27, 2011, 05:05 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 12/18] revert: Save command-line options for continuing operation

Ramkumar Ramachandra wrote:
Show 12 quoted lines
> In the same spirit as ".git/sequencer/head" and ".git/sequencer/todo",
> introduce ".git/sequencer/opts" to persist the replay_opts structure
> for continuing after a conflict resolution.  Use the gitconfig format
> for this file so that it looks like:
>
> [core]
> 	signoff = true
> 	record-origin = true
> 	mainline = 1
> 	strategy = recursive
> 	strategy-option = patience
> 	strategy-option = ours
Maybe "[options]" instead of "[core]" would be more self-explanatory.

If this is meant to be closely analagous to the gitconfig file, then variable names should not contain dashes. It would be nice if some day the config parser could learn to treat dashes as insignificant, just like it already treats case distinctions as insignificant.

Ramkumar Ramachandra· Jul 27, 2011, 09:51 UTC · re: Jonathan Nieder · lore

Re: [PATCH 12/18] revert: Save command-line options for continuing operation

Hi again,
Jonathan Nieder writes:
Show 15 quoted lines
> Ramkumar Ramachandra wrote:
>> In the same spirit as ".git/sequencer/head" and ".git/sequencer/todo",
>> introduce ".git/sequencer/opts" to persist the replay_opts structure
>> for continuing after a conflict resolution.  Use the gitconfig format
>> for this file so that it looks like:
>>
>> [core]
>>       signoff = true
>>       record-origin = true
>>       mainline = 1
>>       strategy = recursive
>>       strategy-option = patience
>>       strategy-option = ours
>
> Maybe "[options]" instead of "[core]" would be more self-explanatory.
Fixed, thanks.
> If this is meant to be closely analagous to the gitconfig file, then
> variable names should not contain dashes.
Huh?  Quoting Documentation/config.txt:

All the other lines (and the remainder of the line after the section header) are recognized as setting variables, in the form 'name = value'. If there is no equal sign on the line, the entire line is taken as 'name' and the variable is recognized as boolean "true". The variable names are case-insensitive and only alphanumeric characters and `-` are allowed. There can be more than one value for a given variable; we say then that variable is multivalued.

Btw, the multi-value thing is something I wasn't aware about earlier; that's what led to the " | " ugliness in my earlier patch.

> It would be nice if some
> day the config parser could learn to treat dashes as insignificant,
> just like it already treats case distinctions as insignificant.

Is the documentation out of date? I can't find any special handling for `-` in the code either. What are you talking about?

-- Ram
Jonathan Nieder· Jul 27, 2011, 14:21 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 12/18] revert: Save command-line options for continuing operation

Ramkumar Ramachandra wrote:
> Jonathan Nieder writes:
Show 6 quoted lines
>> It would be nice if some
>> day the config parser could learn to treat dashes as insignificant,
>> just like it already treats case distinctions as insignificant.
>
> Is the documentation out of date? I can't find any special handling
> for `-` in the code either.  What are you talking about?

"It would be nice if some day the config parser could learn" implies "The config parser does not behave like this", no?

One problem with dashes in names of real configuration variables is that most git multi-word configuration variables are already named using InsignificantBumpyCaps, which means the user does not have to remember where the word breaks are as language drifts (e.g., is it a "file name" or a "filename"?). A convention of using dashes to separate words would probably have been about as good, while inconsistently sometimes using dashes and sometimes not would be far worse.

Of course, the .git/sequencer/args file is not gitconfig, and changes in its format are not as hard (since unlike for gitconfig, any backward-compatibility synonyms probably do not need to be mentioned in documentation). So I am not too worried and this was mostly an aside.

Ramkumar Ramachandra· Jul 27, 2011, 15:49 UTC · re: Jonathan Nieder · lore

Re: [PATCH 12/18] revert: Save command-line options for continuing operation

Hi again,
Jonathan Nieder writes:
Show 11 quoted lines
> Ramkumar Ramachandra wrote:
>> Jonathan Nieder writes:
>>> It would be nice if some
>>> day the config parser could learn to treat dashes as insignificant,
>>> just like it already treats case distinctions as insignificant.
>>
>> Is the documentation out of date? I can't find any special handling
>> for `-` in the code either.  What are you talking about?
>
> "It would be nice if some day the config parser could learn" implies
> "The config parser does not behave like this", no?
Ah, I misread that.  Sorry.
Show 8 quoted lines
> One problem with dashes in names of real configuration variables is
> that most git multi-word configuration variables are already named
> using InsignificantBumpyCaps, which means the user does not have to
> remember where the word breaks are as language drifts (e.g., is it a
> "file name" or a "filename"?).  A convention of using dashes to
> separate words would probably have been about as good, while
> inconsistently sometimes using dashes and sometimes not would be far
> worse.

Hm, case insensitivity is nice, but ignoring dashes may not be such a good idea. I suppose we can use them where it's totally sensible and unambiguous like in this case (command-line options).

Show 5 quoted lines
> Of course, the .git/sequencer/args file is not gitconfig, and changes
> in its format are not as hard (since unlike for gitconfig, any
> backward-compatibility synonyms probably do not need to be mentioned
> in documentation).  So I am not too worried and this was mostly an
> aside.
Cool.  Thanks.
-- Ram
Ramkumar Ramachandra· Jul 27, 2011, 03:19 UTC · re: Ramkumar Ramachandra · lore

[PATCH 13/18] revert: Make pick_commits functionally act on a commit list

Apart from its central objective of calling into the picking mechanism, pick_commits creates a sequencer directory, prepares a todo list, and even acts upon the "--reset" subcommand. This makes for a bad API since the central worry of callers is to figure out whether or not any conflicts were encountered during the cherry picking. The current API is like:

if (pick_commits(opts) < 0)
   print "Something failed, we're not sure what"

So, change pick_commits so that it's only responsible for picking commits in a loop and reporting any errors, leaving the rest to a new function called pick_revisions. Consequently, the API of pick_commits becomes much clearer:

act_on_subcommand(opts->subcommand);
todo_list = prepare_todo_list();
if (pick_commits(todo_list, opts) < 0)
   print "Error encountered while picking commits"

Now, callers can easily call-in to the cherry-picking machinery by constructing an arbitrary todo list along with some options.

Helped-by: Jonathan Nieder <jrnider@gmail.com>
Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
---
 builtin/revert.c |   46 ++++++++++++++++++++++++++++++----------------
 1 files changed, 30 insertions(+), 16 deletions(-)
Show changes to builtin/revert.c +30 −16
diff --git a/builtin/revert.c b/builtin/revert.c
index b02d3d2..46b1371 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -851,11 +851,9 @@ static void save_opts(struct replay_opts *opts)
 	strbuf_release(&buf);
 }
 
-static int pick_commits(struct replay_opts *opts)
+static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts)
 {
-	struct commit_list *todo_list = NULL;
 	struct strbuf buf = STRBUF_INIT;
-	unsigned char sha1[20];
 	struct commit_list *cur;
 	int res;
 
@@ -865,17 +863,6 @@ static int pick_commits(struct replay_opts *opts)
 				opts->record_origin || opts->edit));
 	read_and_refresh_cache(opts);
 
-	walk_revs_populate_todo(&todo_list, opts);
-	create_seq_dir();
-	if (get_sha1("HEAD", sha1)) {
-		if (opts->action == REVERT)
-			return error(_("Can't revert as initial commit"));
-		return error(_("Can't cherry-pick into empty head"));
-	} else
-		save_head(sha1_to_hex(sha1));
-	save_opts(opts);
-	save_todo(todo_list, opts);
-
 	for (cur = todo_list; cur; cur = cur->next) {
 		save_todo(cur, opts);
 		res = do_pick_commit(cur->item, opts);
@@ -892,6 +879,27 @@ static int pick_commits(struct replay_opts *opts)
 	return 0;
 }
 
+static int pick_revisions(struct replay_opts *opts)
+{
+	struct commit_list *todo_list = NULL;
+	unsigned char sha1[20];
+
+	read_and_refresh_cache(opts);
+
+	walk_revs_populate_todo(&todo_list, opts);
+	create_seq_dir();
+	if (get_sha1("HEAD", sha1)) {
+		if (opts->action == REVERT)
+			return error(_("Can't revert as initial commit"));
+		return error(_("Can't cherry-pick into empty head"));
+	} else
+		save_head(sha1_to_hex(sha1));
+	save_opts(opts);
+	save_todo(todo_list, opts);
+
+	return pick_commits(todo_list, opts);
+}
+
 int cmd_revert(int argc, const char **argv, const char *prefix)
 {
 	int res;
@@ -903,7 +911,13 @@ int cmd_revert(int argc, const char **argv, const char *prefix)
 	opts.action = REVERT;
 	git_config(git_default_config, NULL);
 	parse_args(argc, argv, &opts);
-	res = pick_commits(&opts);
+
+	/*
+	 * Decide what to do depending on the arguments; a fresh
+	 * cherry-pick should be handled differently from an existing
+	 * one that is being continued
+	 */
+	res = pick_revisions(&opts);
 	if (res < 0)
 		die(_("revert failed"));
 	return res;
@@ -918,7 +932,7 @@ int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
 	opts.action = CHERRY_PICK;
 	git_config(git_default_config, NULL);
 	parse_args(argc, argv, &opts);
-	res = pick_commits(&opts);
+	res = pick_revisions(&opts);
 	if (res < 0)
 		die(_("cherry-pick failed"));
 	return res;
-- 
1.7.4.rc1.7.g2cf08.dirty
Ramkumar Ramachandra· Jul 27, 2011, 03:19 UTC · re: Ramkumar Ramachandra · lore

[PATCH 14/18] revert: Introduce --reset to remove sequencer state

To explicitly remove the sequencer state for a fresh cherry-pick or revert invocation, introduce a new subcommand called "--reset" to remove the sequencer state.

Take the opportunity to publicly expose the sequencer paths, and a generic function called "remove_sequencer_state" that various git programs can use to remove the sequencer state in a uniform manner; "git reset" uses it later in this series. Introducing this public API is also in line with our long-term goal of eventually factoring out functions from revert.c into a generic commit sequencer.

Helped-by: Jonathan Nieder <jrnieder@gmail.com>
Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
---
 Documentation/git-cherry-pick.txt |    5 +++
 Documentation/git-revert.txt      |    5 +++
 Documentation/sequencer.txt       |    4 ++
 Makefile                          |    2 +
 builtin/revert.c                  |   65 +++++++++++++++++++++++++-----------
 sequencer.c                       |   19 +++++++++++
 sequencer.h                       |   20 +++++++++++
 t/t3510-cherry-pick-sequence.sh   |   16 ++++++++-
 8 files changed, 114 insertions(+), 22 deletions(-)
 create mode 100644 Documentation/sequencer.txt
 create mode 100644 sequencer.c
 create mode 100644 sequencer.h
Show changes to 8 files +114 −22

Documentation/git-cherry-pick.txt, Documentation/git-revert.txt, Documentation/sequencer.txt, Makefile, builtin/revert.c, sequencer.c, sequencer.h, t/t3510-cherry-pick-sequence.sh

diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
index 9d8fe0d..138a292 100644
--- a/Documentation/git-cherry-pick.txt
+++ b/Documentation/git-cherry-pick.txt
@@ -8,6 +8,7 @@ git-cherry-pick - Apply the changes introduced by some existing commits
 SYNOPSIS
 --------
 'git cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] [--ff] <commit>...
+'git cherry-pick' --reset
 
 DESCRIPTION
 -----------
@@ -109,6 +110,10 @@ effect to your index in a row.
 	Pass the merge strategy-specific option through to the
 	merge strategy.  See linkgit:git-merge[1] for details.
 
+SEQUENCER SUBCOMMANDS
+---------------------
+include::sequencer.txt[]
+
 EXAMPLES
 --------
 git cherry-pick master::
diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt
index 6a21b37..b6789be 100644
--- a/Documentation/git-revert.txt
+++ b/Documentation/git-revert.txt
@@ -8,6 +8,7 @@ git-revert - Revert some existing commits
 SYNOPSIS
 --------
 'git revert' [--edit | --no-edit] [-n] [-m parent-number] [-s] <commit>...
+'git revert' --reset
 
 DESCRIPTION
 -----------
@@ -90,6 +91,10 @@ effect to your index in a row.
 	Pass the merge strategy-specific option through to the
 	merge strategy.  See linkgit:git-merge[1] for details.
 
+SEQUENCER SUBCOMMANDS
+---------------------
+include::sequencer.txt[]
+
 EXAMPLES
 --------
 git revert HEAD~3::
diff --git a/Documentation/sequencer.txt b/Documentation/sequencer.txt
new file mode 100644
index 0000000..16ce88c
--- /dev/null
+++ b/Documentation/sequencer.txt
@@ -0,0 +1,4 @@
+--reset::
+	Forget about the current operation in progress.  Can be used
+	to clear the sequencer state after a failed cherry-pick or
+	revert.
diff --git a/Makefile b/Makefile
index 4ed7996..afd7673 100644
--- a/Makefile
+++ b/Makefile
@@ -554,6 +554,7 @@ LIB_H += rerere.h
 LIB_H += resolve-undo.h
 LIB_H += revision.h
 LIB_H += run-command.h
+LIB_H += sequencer.h
 LIB_H += sha1-array.h
 LIB_H += sha1-lookup.h
 LIB_H += sideband.h
@@ -658,6 +659,7 @@ LIB_OBJS += revision.o
 LIB_OBJS += run-command.o
 LIB_OBJS += server-info.o
 LIB_OBJS += setup.o
+LIB_OBJS += sequencer.o
 LIB_OBJS += sha1-array.o
 LIB_OBJS += sha1-lookup.o
 LIB_OBJS += sha1_file.o
diff --git a/builtin/revert.c b/builtin/revert.c
index 46b1371..3c792fa 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -14,6 +14,7 @@
 #include "merge-recursive.h"
 #include "refs.h"
 #include "dir.h"
+#include "sequencer.h"
 
 /*
  * This implements the builtins revert and cherry-pick.
@@ -28,18 +29,22 @@
 
 static const char * const revert_usage[] = {
 	"git revert [options] <commit-ish>",
+	"git revert <subcommand>",
 	NULL
 };
 
 static const char * const cherry_pick_usage[] = {
 	"git cherry-pick [options] <commit-ish>",
+	"git cherry-pick <subcommand>",
 	NULL
 };
 
 enum replay_action { REVERT, CHERRY_PICK };
+enum replay_subcommand { REPLAY_NONE, REPLAY_RESET };
 
 struct replay_opts {
 	enum replay_action action;
+	enum replay_subcommand subcommand;
 
 	/* Boolean options */
 	int edit;
@@ -62,11 +67,6 @@ struct replay_opts {
 #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
 #define MAYBE_UNUSED __attribute__((__unused__))
 
-#define SEQ_DIR         "sequencer"
-#define SEQ_HEAD_FILE   "sequencer/head"
-#define SEQ_TODO_FILE   "sequencer/todo"
-#define SEQ_OPTS_FILE   "sequencer/opts"
-
 static const char *action_name(const struct replay_opts *opts)
 {
 	return opts->action == REVERT ? "revert" : "cherry-pick";
@@ -114,7 +114,9 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
 	const char * const * usage_str = revert_or_cherry_pick_usage(opts);
 	const char *me = action_name(opts);
 	int noop;
+	int reset = 0;
 	struct option options[] = {
+		OPT_BOOLEAN(0, "reset", &reset, "forget the current operation"),
 		OPT_BOOLEAN('n', "no-commit", &opts->no_commit, "don't automatically commit"),
 		OPT_BOOLEAN('e', "edit", &opts->edit, "edit the commit message"),
 		{ OPTION_BOOLEAN, 'r', NULL, &noop, NULL, "no-op (backward compatibility)",
@@ -143,7 +145,27 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
 	opts->commit_argc = parse_options(argc, argv, NULL, options, usage_str,
 					PARSE_OPT_KEEP_ARGV0 |
 					PARSE_OPT_KEEP_UNKNOWN);
-	if (opts->commit_argc < 2)
+
+	/* Set the subcommand */
+	if (reset)
+		opts->subcommand = REPLAY_RESET;
+	else
+		opts->subcommand = REPLAY_NONE;
+
+	/* Check for incompatible command line arguments */
+	if (opts->subcommand == REPLAY_RESET) {
+		verify_opt_compatible(me, "--reset",
+				"--no-commit", opts->no_commit,
+				"--signoff", opts->signoff,
+				"--mainline", opts->mainline,
+				"--strategy", opts->strategy ? 1 : 0,
+				"--strategy-option", opts->xopts ? 1 : 0,
+				"-x", opts->record_origin,
+				"--ff", opts->allow_ff,
+				NULL);
+	}
+
+	else if (opts->commit_argc < 2)
 		usage_with_options(usage_str, options);
 
 	if (opts->allow_ff)
@@ -853,7 +875,6 @@ static void save_opts(struct replay_opts *opts)
 
 static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts)
 {
-	struct strbuf buf = STRBUF_INIT;
 	struct commit_list *cur;
 	int res;
 
@@ -874,8 +895,7 @@ static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts)
 	 * Sequence of picks finished successfully; cleanup by
 	 * removing the .git/sequencer directory
 	 */
-	strbuf_addf(&buf, "%s", git_path(SEQ_DIR));
-	remove_dir_recursively(&buf, 0);
+	remove_sequencer_state(1);
 	return 0;
 }
 
@@ -886,17 +906,22 @@ static int pick_revisions(struct replay_opts *opts)
 
 	read_and_refresh_cache(opts);
 
-	walk_revs_populate_todo(&todo_list, opts);
-	create_seq_dir();
-	if (get_sha1("HEAD", sha1)) {
-		if (opts->action == REVERT)
-			return error(_("Can't revert as initial commit"));
-		return error(_("Can't cherry-pick into empty head"));
-	} else
-		save_head(sha1_to_hex(sha1));
-	save_opts(opts);
-	save_todo(todo_list, opts);
-
+	if (opts->subcommand == REPLAY_RESET) {
+		remove_sequencer_state(1);
+		return 0;
+	} else {
+		/* Start a new cherry-pick/ revert sequence */
+		walk_revs_populate_todo(&todo_list, opts);
+		create_seq_dir();
+		if (get_sha1("HEAD", sha1)) {
+			if (opts->action == REVERT)
+				return error(_("Can't revert as initial commit"));
+			return error(_("Can't cherry-pick into empty head"));
+		} else
+			save_head(sha1_to_hex(sha1));
+		save_opts(opts);
+		save_todo(todo_list, opts);
+	}
 	return pick_commits(todo_list, opts);
 }
 
diff --git a/sequencer.c b/sequencer.c
new file mode 100644
index 0000000..bc2c046
--- /dev/null
+++ b/sequencer.c
@@ -0,0 +1,19 @@
+#include "cache.h"
+#include "sequencer.h"
+#include "strbuf.h"
+#include "dir.h"
+
+void remove_sequencer_state(int aggressive)
+{
+	struct strbuf seq_dir = STRBUF_INIT;
+	struct strbuf seq_old_dir = STRBUF_INIT;
+
+	strbuf_addf(&seq_dir, "%s", git_path(SEQ_DIR));
+	strbuf_addf(&seq_old_dir, "%s", git_path(SEQ_OLD_DIR));
+	remove_dir_recursively(&seq_old_dir, 0);
+	rename(git_path(SEQ_DIR), git_path(SEQ_OLD_DIR));
+	if (aggressive)
+		remove_dir_recursively(&seq_old_dir, 0);
+	strbuf_release(&seq_dir);
+	strbuf_release(&seq_old_dir);
+}
diff --git a/sequencer.h b/sequencer.h
new file mode 100644
index 0000000..905d295
--- /dev/null
+++ b/sequencer.h
@@ -0,0 +1,20 @@
+#ifndef SEQUENCER_H
+#define SEQUENCER_H
+
+#define SEQ_DIR		"sequencer"
+#define SEQ_OLD_DIR	"sequencer-old"
+#define SEQ_HEAD_FILE	"sequencer/head"
+#define SEQ_TODO_FILE	"sequencer/todo"
+#define SEQ_OPTS_FILE	"sequencer/opts"
+
+/*
+ * Removes SEQ_OLD_DIR and renames SEQ_DIR to SEQ_OLD_DIR, ignoring
+ * any errors.  Intended to be used by 'git reset'.
+ *
+ * With the aggressive flag, it additionally removes SEQ_OLD_DIR,
+ * ignoring any errors.  Inteded to be used by the sequencer's
+ * '--reset' subcommand.
+ */
+void remove_sequencer_state(int aggressive);
+
+#endif
diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh
index 79d868f..aea4f6c 100755
--- a/t/t3510-cherry-pick-sequence.sh
+++ b/t/t3510-cherry-pick-sequence.sh
@@ -37,7 +37,7 @@ test_expect_success 'cherry-pick persists data on failure' '
 	test_path_is_file .git/sequencer/head &&
 	test_path_is_file .git/sequencer/todo &&
 	test_path_is_file .git/sequencer/opts &&
-	rm -rf .git/sequencer
+	git cherry-pick --reset
 '
 
 test_expect_success 'cherry-pick persists opts correctly' '
@@ -62,7 +62,7 @@ test_expect_success 'cherry-pick persists opts correctly' '
 	EOF
 	git config --file=.git/sequencer/opts --get-all core.strategy-option >actual &&
 	test_cmp expect actual &&
-	rm -rf .git/sequencer
+	git cherry-pick --reset
 '
 
 test_expect_success 'cherry-pick cleans up sequencer state upon success' '
@@ -71,4 +71,16 @@ test_expect_success 'cherry-pick cleans up sequencer state upon success' '
 	test_path_is_missing .git/sequencer
 '
 
+test_expect_success '--reset does not complain when no cherry-pick is in progress' '
+	pristine_detach initial &&
+	git cherry-pick --reset
+'
+
+test_expect_success '--reset cleans up sequencer state' '
+	pristine_detach initial &&
+	test_must_fail git cherry-pick base..picked &&
+	git cherry-pick --reset &&
+	test_path_is_missing .git/sequencer
+'
+
 test_done
-- 
1.7.4.rc1.7.g2cf08.dirty
Jonathan Nieder· Jul 27, 2011, 05:11 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 14/18] revert: Introduce --reset to remove sequencer state

Ramkumar Ramachandra wrote:
Show 7 quoted lines
> --- /dev/null
> +++ b/Documentation/sequencer.txt
> @@ -0,0 +1,4 @@
> +--reset::
> +	Forget about the current operation in progress.  Can be used
> +	to clear the sequencer state after a failed cherry-pick or
> +	revert.

Probably worth mentioning that the index, HEAD, and worktree are left alone.

> +++ b/builtin/revert.c
[...]
> @@ -886,17 +906,22 @@ static int pick_revisions(struct replay_opts *opts)
[...]
Show 5 quoted lines
> +	if (opts->subcommand == REPLAY_RESET) {
> +		remove_sequencer_state(1);
> +		return 0;
> +	} else {
> +		/* Start a new cherry-pick/ revert sequence */
Can un-indent by dropping the "else":
	if (opts->subcommand == REPLAY_RESET) {
		...
		return 0;
	}
	/* Start a new cherry-pick or revert. */
	...
[...]
Show 9 quoted lines
> --- a/t/t3510-cherry-pick-sequence.sh
> +++ b/t/t3510-cherry-pick-sequence.sh
> @@ -37,7 +37,7 @@ test_expect_success 'cherry-pick persists data on failure' '
>  	test_path_is_file .git/sequencer/head &&
>  	test_path_is_file .git/sequencer/todo &&
>  	test_path_is_file .git/sequencer/opts &&
> -	rm -rf .git/sequencer
> +	git cherry-pick --reset
>  '

This is not about this patch, but ideally the cleanup would come at the beginning of the next test, so if one test fails it does not take down all the tests that come after it.

Ramkumar Ramachandra· Jul 27, 2011, 10:12 UTC · re: Jonathan Nieder · lore

Re: [PATCH 14/18] revert: Introduce --reset to remove sequencer state

Hi,
Jonathan Nieder writes:
Show 11 quoted lines
> Ramkumar Ramachandra wrote:
>> --- /dev/null
>> +++ b/Documentation/sequencer.txt
>> @@ -0,0 +1,4 @@
>> +--reset::
>> +     Forget about the current operation in progress.  Can be used
>> +     to clear the sequencer state after a failed cherry-pick or
>> +     revert.
>
> Probably worth mentioning that the index, HEAD, and worktree are left
> alone.
Fixed, thanks.
Show 11 quoted lines
>> +++ b/builtin/revert.c
> [...]
>> @@ -886,17 +906,22 @@ static int pick_revisions(struct replay_opts *opts)
> [...]
>> +     if (opts->subcommand == REPLAY_RESET) {
>> +             remove_sequencer_state(1);
>> +             return 0;
>> +     } else {
>> +             /* Start a new cherry-pick/ revert sequence */
>
> Can un-indent by dropping the "else":

Actually this was intentional; if we un-indent this now, there'll be a diff indenting it when '--reset' and '--continue' are introduced which turns out to be especially ugly :)

Show 13 quoted lines
>> --- a/t/t3510-cherry-pick-sequence.sh
>> +++ b/t/t3510-cherry-pick-sequence.sh
>> @@ -37,7 +37,7 @@ test_expect_success 'cherry-pick persists data on failure' '
>>       test_path_is_file .git/sequencer/head &&
>>       test_path_is_file .git/sequencer/todo &&
>>       test_path_is_file .git/sequencer/opts &&
>> -     rm -rf .git/sequencer
>> +     git cherry-pick --reset
>>  '
>
> This is not about this patch, but ideally the cleanup would come at
> the beginning of the next test, so if one test fails it does not take
> down all the tests that come after it.
Good point.  Fixed all, thanks.
-- Ram
Jonathan Nieder· Jul 27, 2011, 14:28 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 14/18] revert: Introduce --reset to remove sequencer state

Ramkumar Ramachandra wrote:
> Jonathan Nieder writes:
>> Ramkumar Ramachandra wrote:
Show 11 quoted lines
>>> +     if (opts->subcommand == REPLAY_RESET) {
>>> +             remove_sequencer_state(1);
>>> +             return 0;
>>> +     } else {
>>> +             /* Start a new cherry-pick/ revert sequence */
>>
>> Can un-indent by dropping the "else":
>
> Actually this was intentional; if we un-indent this now, there'll be a
> diff indenting it when '--reset' and '--continue' are introduced which
> turns out to be especially ugly :)

Why couldn't it look like this at the end, for example? (As always, this is just an example; I am not saying "please make it look like this".)

	if (opts->subcommand == REPLAY_RESET) {
		remove_sequencer_state(1);
		return 0;
	}
	if (opts->subcommand == REPLAY_CONTINUE) {
		... prepare todo list for continue ...
	} else {
		... prepare todo list for a new cherry-pick ...
	}
	pick the chosen commits
Show 5 quoted lines
>> This is not about this patch, but ideally the cleanup would come at
>> the beginning of the next test, so if one test fails it does not take
>> down all the tests that come after it.
>
> Good point.  Fixed all, thanks.
Thanks for looking into it.
Ramkumar Ramachandra· Jul 27, 2011, 14:35 UTC · re: Jonathan Nieder · lore

Re: [PATCH 14/18] revert: Introduce --reset to remove sequencer state

Hi,
Jonathan Nieder writes:
Show 31 quoted lines
> Ramkumar Ramachandra wrote:
>> Jonathan Nieder writes:
>>> Ramkumar Ramachandra wrote:
>
>>>> +     if (opts->subcommand == REPLAY_RESET) {
>>>> +             remove_sequencer_state(1);
>>>> +             return 0;
>>>> +     } else {
>>>> +             /* Start a new cherry-pick/ revert sequence */
>>>
>>> Can un-indent by dropping the "else":
>>
>> Actually this was intentional; if we un-indent this now, there'll be a
>> diff indenting it when '--reset' and '--continue' are introduced which
>> turns out to be especially ugly :)
>
> Why couldn't it look like this at the end, for example?  (As always,
> this is just an example; I am not saying "please make it look like
> this".)
>
>        if (opts->subcommand == REPLAY_RESET) {
>                remove_sequencer_state(1);
>                return 0;
>        }
>
>        if (opts->subcommand == REPLAY_CONTINUE) {
>                ... prepare todo list for continue ...
>        } else {
>                ... prepare todo list for a new cherry-pick ...
>        }
>        pick the chosen commits

Mainly style. It looks a little unnatural: when more subcommands come up, I'd like a nice switch-case to dispatch :)

-- Ram
Ramkumar Ramachandra· Jul 27, 2011, 03:19 UTC · re: Ramkumar Ramachandra · lore

[PATCH 15/18] reset: Make reset remove the sequencer state

Years of muscle memory have trained users to use "git reset --hard" to remove the branch state after any sort operation. Make it also remove the sequencer state to facilitate this established workflow:

$ git cherry-pick foo..bar ... conflict encountered ... $ git reset --hard # Oops, I didn't mean that $ git cherry-pick quux..bar ... cherry-pick succeeded ...

Guard against accidental removal of the sequencer state by providing one level of "undo". In the first "reset" invocation, ".git/sequencer" is moved to ".git/sequencer-old"; it is completely removed only in the second invocation.

Helped-by: Jonathan Nieder <jrnieder@gmail.com>
Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
---
 branch.c                 |    2 ++
 t/7106-reset-sequence.sh |   43 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 45 insertions(+), 0 deletions(-)
 create mode 100755 t/7106-reset-sequence.sh
Show changes to 2 files +45 −0

branch.c, t/7106-reset-sequence.sh

diff --git a/branch.c b/branch.c
index c0c865a..d06aec4 100644
--- a/branch.c
+++ b/branch.c
@@ -3,6 +3,7 @@
 #include "refs.h"
 #include "remote.h"
 #include "commit.h"
+#include "sequencer.h"
 
 struct tracking {
 	struct refspec spec;
@@ -228,4 +229,5 @@ void remove_branch_state(void)
 	unlink(git_path("MERGE_MSG"));
 	unlink(git_path("MERGE_MODE"));
 	unlink(git_path("SQUASH_MSG"));
+	remove_sequencer_state(0);
 }
diff --git a/t/7106-reset-sequence.sh b/t/7106-reset-sequence.sh
new file mode 100755
index 0000000..c61c62d
--- /dev/null
+++ b/t/7106-reset-sequence.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+test_description='Test interaction of reset --hard with sequencer
+
+  + anotherpick: rewrites foo to d
+  + picked: rewrites foo to c
+  + unrelatedpick: rewrites unrelated to reallyunrelated
+  + base: rewrites foo to b
+  + initial: writes foo as a, unrelated as unrelated
+'
+
+. ./test-lib.sh
+
+pristine_detach () {
+	git checkout -f "$1^0" &&
+	git read-tree -u --reset HEAD &&
+	git clean -d -f -f -q -x
+}
+
+test_expect_success setup '
+	echo unrelated >unrelated &&
+	git add unrelated &&
+	test_commit initial foo a &&
+	test_commit base foo b &&
+	test_commit unrelatedpick unrelated reallyunrelated &&
+	test_commit picked foo c &&
+	test_commit anotherpick foo d &&
+	git config advice.detachedhead false
+
+'
+
+test_expect_success 'reset --hard cleans up sequencer state, providing one-level undo' '
+	pristine_detach initial &&
+	test_must_fail git cherry-pick base..anotherpick &&
+	test_path_is_dir .git/sequencer &&
+	git reset --hard &&
+	test_path_is_missing .git/sequencer &&
+	test_path_is_dir .git/sequencer-old &&
+	git reset --hard &&
+	test_path_is_missing .git/sequencer-old
+'
+
+test_done
-- 
1.7.4.rc1.7.g2cf08.dirty
Jonathan Nieder· Jul 27, 2011, 05:16 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 15/18] reset: Make reset remove the sequencer state

Ramkumar Ramachandra wrote:
> [Subject: reset: Make reset remove the sequencer state]

The chosen strategy here also affects some other commands, most notably "git checkout".

[...]
> Guard against accidental removal of the sequencer state by providing
> one level of "undo".  In the first "reset" invocation,
> ".git/sequencer" is moved to ".git/sequencer-old"; it is completely
> removed only in the second invocation.

That doesn't help much if the user doesn't know about it, I fear. Any ideas about how to address that?

Especially, if I run
	git checkout HEAD^
and get the response
	note: removing pending cherry-pick state
	hint: use "git cherry-pick --revive" to return to the cherry-pick

then I probably would not be too annoyed. And on the other hand, if I run

	git reset --hard

to clear away an ugly conflict in the course of a long cherry-pick-many and suddenly "git cherry-pick --continue" stops working without explanation and the only way I know to finish the job I was working on is to start over, that would be an unpleasant experience.

Ramkumar Ramachandra· Jul 28, 2011, 15:42 UTC · re: Jonathan Nieder · lore

Re: [PATCH 15/18] reset: Make reset remove the sequencer state

Hi Jonathan,
Jonathan Nieder writes:
Show 5 quoted lines
> Ramkumar Ramachandra wrote:
>> [Subject: reset: Make reset remove the sequencer state]
>
> The chosen strategy here also affects some other commands, most notably
> "git checkout".

I'm curious: doesn't this also apply to "rebase -i" and other scripts that attempt to remove the branch state through "reset"?

-- Ram
Jonathan Nieder· Jul 28, 2011, 15:59 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 15/18] reset: Make reset remove the sequencer state

Ramkumar Ramachandra wrote:
> I'm curious: doesn't this also apply to "rebase -i" and other scripts
> that attempt to remove the branch state through "reset"?

It starts by detaching HEAD. So, just like after a conflicted "git merge", I think typically it will write

	error: you need to resolve your current index first

But that doesn't help when there are no unmerged index entries; good catch.

Maybe merge_working_tree() should ask the sequencer API whether there is something untoward in progress so "git checkout" without "-f" can know to bail out.

Ramkumar Ramachandra· Jul 27, 2011, 03:19 UTC · re: Ramkumar Ramachandra · lore

[PATCH 16/18] revert: Remove sequencer state when no commits are pending

When cherry-pick or revert is called on a list of commits, and a conflict encountered somewhere in the middle, the data in ".git/sequencer" is required to continue the operation. However, when a conflict is encountered in the very last commit, the user will have to "continue" after resolving the conflict and committing just so that the sequencer state is removed. This is how the current "rebase -i" script works as well.

$ git cherry-pick foo..bar ... conflict encountered while picking "bar" ... $ echo "resolved" >problematicfile $ git add problematicfile $ git commit $ git cherry-pick --continue # This would be a noop

Change this so that the sequencer state is cleared when a conflict is encountered in the last commit. Incidentally, this patch makes sure that some existing tests don't break when features like "--reset" and "--continue" are implemented later in the series.

Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
---
 builtin/revert.c                |   12 +++++++++++-
 t/t3510-cherry-pick-sequence.sh |   24 ++++++++++++++++++++++++
 2 files changed, 35 insertions(+), 1 deletions(-)
Show changes to 2 files +35 −1

builtin/revert.c, t/t3510-cherry-pick-sequence.sh

diff --git a/builtin/revert.c b/builtin/revert.c
index 3c792fa..09d479b 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -887,8 +887,18 @@ static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts)
 	for (cur = todo_list; cur; cur = cur->next) {
 		save_todo(cur, opts);
 		res = do_pick_commit(cur->item, opts);
-		if (res)
+		if (res) {
+			if (!cur->next)
+				/*
+				 * An error was encountered while
+				 * picking the last commit; the
+				 * sequencer state is useless now --
+				 * the user simply needs to resolve
+				 * the conflict and commit
+				 */
+				remove_sequencer_state(0);
 			return res;
+		}
 	}
 
 	/*
diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh
index aea4f6c..428b5bf 100755
--- a/t/t3510-cherry-pick-sequence.sh
+++ b/t/t3510-cherry-pick-sequence.sh
@@ -83,4 +83,28 @@ test_expect_success '--reset cleans up sequencer state' '
 	test_path_is_missing .git/sequencer
 '
 
+test_expect_success 'cherry-pick cleans up sequencer state when one commit is left' '
+	pristine_detach initial &&
+	test_must_fail git cherry-pick base..picked &&
+	test_path_is_missing .git/sequencer &&
+	echo "resolved" >foo &&
+	git add foo &&
+	git commit &&
+	{
+		git rev-list HEAD |
+		git diff-tree --root --stdin |
+		sed "s/$_x40/OBJID/g"
+	} >actual &&
+	cat >expect <<-\EOF &&
+	OBJID
+	:100644 100644 OBJID OBJID M	foo
+	OBJID
+	:100644 100644 OBJID OBJID M	unrelated
+	OBJID
+	:000000 100644 OBJID OBJID A	foo
+	:000000 100644 OBJID OBJID A	unrelated
+	EOF
+	test_cmp expect actual
+'
+
 test_done
-- 
1.7.4.rc1.7.g2cf08.dirty
Jonathan Nieder· Jul 27, 2011, 05:17 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 16/18] revert: Remove sequencer state when no commits are pending

Ramkumar Ramachandra wrote:
> Change this so that the sequencer state is cleared when a conflict is
> encountered in the last commit.

This defeats the point of keeping a ".git/sequencer/head" file. Why not remove the cherry-pick state when it is known no longer to be needed, namely after the conflict resolution is commited?

Ramkumar Ramachandra· Jul 27, 2011, 09:42 UTC · re: Jonathan Nieder · lore

Re: [PATCH 16/18] revert: Remove sequencer state when no commits are pending

Hi again,
Jonathan Nieder writes:
Show 7 quoted lines
> Ramkumar Ramachandra wrote:
>> Change this so that the sequencer state is cleared when a conflict is
>> encountered in the last commit.
>
> This defeats the point of keeping a ".git/sequencer/head" file.  Why
> not remove the cherry-pick state when it is known no longer to be needed,
> namely after the conflict resolution is commited?

Sounds nice in theory, but how do we do it? Remove the state at "git commit" time? I've already thought about the problem and presented my arguments here [1].

[1]: http://article.gmane.org/gmane.comp.version-control.git/177465
-- Ram
Jonathan Nieder· Jul 27, 2011, 14:10 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 16/18] revert: Remove sequencer state when no commits are pending

Ramkumar Ramachandra wrote:
Show 5 quoted lines
> Sounds nice in theory, but how do we do it?  Remove the state at "git
> commit" time?  I've already thought about the problem and presented my
> arguments here [1].
>
> [1]: http://article.gmane.org/gmane.comp.version-control.git/177465
For reference:
Show 6 quoted lines
> My sequencer state was blown away just because of a stray
> .git/remove-sequencer-state-after-commit from a previous operation!
> This is horrible.  One can then argue: the file should only ever abort
> *that* sequencer operation, and now we run into the problem of
> assigning a UID for each sequencer operation.  Unnatural and ugly.
> Conclusion: Making "git commit" remove the sequencer state is WRONG.

That is a compelling argument for not using a .git/remove-sequencer-state-after-commit file, but I don't see why one would want such a file anyway. Why can't "git commit" just call a function or use a command that examines .git/sequencer and looks at how many commits are left to pick?

Looking at it from the other side, "My sequencer state was blown away" is a bit dramatic. If it's a problem in "git commit", why isn't it a problem in "git reset", too? If I just commited after resolving a conflict from applying the last commit in the series, what cherry-pick sequence state is going to be useful to me now?

And how is this any less unnatural than making the 'abort cherry-pick' facility unusable when and only when cherry-picking the last commit in a sequence? I don't get it.

Ramkumar Ramachandra· Jul 27, 2011, 14:30 UTC · re: Jonathan Nieder · lore

Re: [PATCH 16/18] revert: Remove sequencer state when no commits are pending

Hi again,
Jonathan Nieder writes:
Show 15 quoted lines
> Ramkumar Ramachandra wrote:
> For reference:
>
>> My sequencer state was blown away just because of a stray
>> .git/remove-sequencer-state-after-commit from a previous operation!
>> This is horrible.  One can then argue: the file should only ever abort
>> *that* sequencer operation, and now we run into the problem of
>> assigning a UID for each sequencer operation.  Unnatural and ugly.
>> Conclusion: Making "git commit" remove the sequencer state is WRONG.
>
> That is a compelling argument for not using a
> .git/remove-sequencer-state-after-commit file, but I don't see why one
> would want such a file anyway.  Why can't "git commit" just call a
> function or use a command that examines .git/sequencer and looks at
> how many commits are left to pick?

Hm, yes. I'd definitely like to see a tighter coupling between "git commit" and the sequencer as it becomes more generalized. Would you advocate this specific change now? If so, I'll write an implementation right away. I was wondering whether you'd like such a tight coupling at this stage.

Show 5 quoted lines
> Looking at it from the other side, "My sequencer state was blown away"
> is a bit dramatic.  If it's a problem in "git commit", why isn't it a
> problem in "git reset", too?  If I just commited after resolving a
> conflict from applying the last commit in the series, what cherry-pick
> sequence state is going to be useful to me now?
Er, no.  I said "stray .git/remove-sequencer-state-after-commit" --
something that's left over from a long-forgotten sequencer operation*.
 Since "git reset" has already been established as a command that
removes the branch state, it's not as dramatic when "git reset"
removes the sequencer state; more so when "git commit" does it
accidentally.
> And how is this any less unnatural than making the 'abort cherry-pick'
> facility unusable when and only when cherry-picking the last commit in
> a sequence?  I don't get it.

I don't want to make it unusable! I was asking for suggestions (like .git/SEQUENCER_HEAD).

* I realize now that we can remove this file before starting a fresh
sequencer operation.
Thanks.
-- Ram
Jonathan Nieder· Jul 27, 2011, 15:48 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 16/18] revert: Remove sequencer state when no commits are pending

Ramkumar Ramachandra wrote:
Show 5 quoted lines
> Hm, yes.  I'd definitely like to see a tighter coupling between "git
> commit" and the sequencer as it becomes more generalized.
> Would you advocate this specific change now?  If so, I'll write an
> implementation right away.  I was wondering whether you'd like such a
> tight coupling at this stage.

If "tight coupling" means "builtin/commit.c calls a function declared in sequencer.h", then yes, I think it should be fine.

In an ideal world it would also be exposed as plumbing ("git cherry-pick --commited"?), for use by hypothetical porcelains that want to use "git commit-tree" et al to simulate what git commit does. But probably best to wait on that part until someone needs it and can tell what a good interface would look like.

Thanks for explaining. Jonathan

Ramkumar Ramachandra· Jul 27, 2011, 15:52 UTC · re: Jonathan Nieder · lore

Re: [PATCH 16/18] revert: Remove sequencer state when no commits are pending

Hi,
Jonathan Nieder writes:
Show 9 quoted lines
> Ramkumar Ramachandra wrote:
>> Hm, yes.  I'd definitely like to see a tighter coupling between "git
>> commit" and the sequencer as it becomes more generalized.
>> Would you advocate this specific change now?  If so, I'll write an
>> implementation right away.  I was wondering whether you'd like such a
>> tight coupling at this stage.
>
> If "tight coupling" means "builtin/commit.c calls a function declared
> in sequencer.h", then yes, I think it should be fine.
Okay.  I'll write something now.
Show 5 quoted lines
> In an ideal world it would also be exposed as plumbing ("git
> cherry-pick --commited"?), for use by hypothetical porcelains that
> want to use "git commit-tree" et al to simulate what git commit does.
> But probably best to wait on that part until someone needs it and can
> tell what a good interface would look like.
Right.  We'll refrain from venturing into this part for now.
-- Ram
Ramkumar Ramachandra· Jul 28, 2011, 16:16 UTC · re: Jonathan Nieder · lore

Re: [PATCH 16/18] revert: Remove sequencer state when no commits are pending

Hi again,
Jonathan Nieder writes:
> If "tight coupling" means "builtin/commit.c calls a function declared
> in sequencer.h", then yes, I think it should be fine.

My bad. I'll first have to figure out a way to expose the todo parsing functions (and dependent functions) without touching much else. This'll be a huge non-trivial patch, and my post midterm work has to include this anyway. So I think it's better to leave it as-it-is for now, and work on this coupling after this series is merged.

-- Ram
Jonathan Nieder· Jul 29, 2011, 19:16 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 16/18] revert: Remove sequencer state when no commits are pending

Ramkumar Ramachandra wrote:
> Jonathan Nieder writes:
Show 5 quoted lines
>> If "tight coupling" means "builtin/commit.c calls a function declared
>> in sequencer.h", then yes, I think it should be fine.
>
> My bad. I'll first have to figure out a way to expose the todo parsing
> functions (and dependent functions) without touching much else.

That's the wrong way to go. One right way would be to expose a single function, representing "here is what to do after successfully making a commit". Think of it as an invisible built-in post-commit hook (see githooks(5)).

Ramkumar Ramachandra· Jul 29, 2011, 19:56 UTC · re: Jonathan Nieder · lore

Re: [PATCH 16/18] revert: Remove sequencer state when no commits are pending

Hi Jonathan,
Jonathan Nieder writes:
Show 12 quoted lines
> Ramkumar Ramachandra wrote:
>> Jonathan Nieder writes:
>>> If "tight coupling" means "builtin/commit.c calls a function declared
>>> in sequencer.h", then yes, I think it should be fine.
>>
>> My bad. I'll first have to figure out a way to expose the todo parsing
>> functions (and dependent functions) without touching much else.
>
> That's the wrong way to go.  One right way would be to expose a single
> function, representing "here is what to do after successfully making a
> commit".  Think of it as an invisible built-in post-commit hook
> (see githooks(5)).

Okay, I don't follow. Wouldn't this function need to parse the todo sheet and figure out how many instructions are left? For that, the todo parsing functions can't be buried in builtin/revert.c, no? Hence I said "expose" them -- or move them to sequencer.c.

-- Ram
Jonathan Nieder· Jul 30, 2011, 13:10 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 16/18] revert: Remove sequencer state when no commits are pending

Ramkumar Ramachandra wrote:
> Okay, I don't follow.  Wouldn't this function need to parse the todo
> sheet and figure out how many instructions are left?  For that, the
> todo parsing functions can't be buried in builtin/revert.c, no?  Hence
> I said "expose" them -- or move them to sequencer.c.

Oh, that makes sense. Sorry for the nonsense. If I were in your shoes, I'd move everything below pick_commits to sequencer.c.

Ramkumar Ramachandra· Jul 30, 2011, 14:43 UTC · re: Jonathan Nieder · lore

Re: [PATCH 16/18] revert: Remove sequencer state when no commits are pending

Hi again,
Jonathan Nieder writes:
Show 8 quoted lines
> Ramkumar Ramachandra wrote:
>> Okay, I don't follow.  Wouldn't this function need to parse the todo
>> sheet and figure out how many instructions are left?  For that, the
>> todo parsing functions can't be buried in builtin/revert.c, no?  Hence
>> I said "expose" them -- or move them to sequencer.c.
>
> Oh, that makes sense.  Sorry for the nonsense.  If I were in your
> shoes, I'd move everything below pick_commits to sequencer.c.

:sigh: I thought it would be easy too, but there are several complications. I'll spare you the gory details of the series I'm preparing and tell you the main problem briefly: Notice how the API of read_populate_todo includes opts. Arguably, I did this with good reason: I didn't want `git cherry-pick --continue` to continue a pending `git revert` operation and viceversa. However, this means that option parsing and todo parsing are slightly tangled up now -- I could always pass a dummy opts structure with only the action filled in, but I think that's inelegant. So, I'm currently working to untangle them by changing the way a todo list is represented -- just a commit_list won't do. Once I change this, a simple `git cherry-pick --continue` will be able to continue any pending sequencer operation, but this is arguably a big patch. Once that is done, I can move dependent functions to sequencer.c and expose an API in sequencer.h for commit.c to use. Also, I don't want to move things to sequencer.c and change the API until I can imagine other git programs working with the generalized sequencer; I hope it'll be clear after a weekend of work.

Long story short: I'd like it if we could bear with this slight annoyance (removal of sequencer state when one commit is pending) and get the series merged, so that I can work on a series to improve the experience based on this series.

Thanks.
-- Ram
Jonathan Nieder· Jul 30, 2011, 14:48 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 16/18] revert: Remove sequencer state when no commits are pending

Ramkumar Ramachandra wrote:
> Long story short: I'd like it if we could bear with this slight
> annoyance (removal of sequencer state when one commit is pending) and
> get the series merged, so that I can work on a series to improve the
> experience based on this series.
Thanks for explaining.  It's not a regression, so fwiw
Acked-by: Jonathan Nieder <jrnieder@gmail.com>

If you are preparing a reroll, please do explain this a little in the log message.

Ramkumar Ramachandra· Jul 27, 2011, 03:19 UTC · re: Ramkumar Ramachandra · lore

[PATCH 17/18] revert: Don't implictly stomp pending sequencer operation

Protect the user from forgetting about a pending sequencer operation by immediately erroring out when an existing cherry-pick or revert operation is in progress like:

$ git cherry-pick foo ... conflict ... $ git cherry-pick moo error: .git/sequencer already exists hint: A cherry-pick or revert is in progress hint: Use --reset to forget about it fatal: cherry-pick failed

A naive version of this would break the following established ways of working:

$ git cherry-pick foo ... conflict ... $ git reset --hard # I actually meant "moo" when I said "foo" $ git cherry-pick moo

$ git cherry-pick foo ... conflict ... $ git commit # commit the resolution $ git cherry-pick moo # New operation

However, the previous patches "reset: Make reset remove the sequencer state" and "revert: Remove sequencer state when no commits are pending" make sure that this does not happen.

Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
---
 builtin/revert.c                |   21 +++++++++++++++++----
 t/t3510-cherry-pick-sequence.sh |   10 ++++++++++
 2 files changed, 27 insertions(+), 4 deletions(-)
Show changes to 2 files +27 −4

builtin/revert.c, t/t3510-cherry-pick-sequence.sh

diff --git a/builtin/revert.c b/builtin/revert.c
index 09d479b..11973e6 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -796,12 +796,15 @@ static void walk_revs_populate_todo(struct commit_list **todo_list,
 		next = commit_list_append(commit, next);
 }
 
-static void create_seq_dir(void)
+static int create_seq_dir(void)
 {
 	const char *seq_dir = git_path(SEQ_DIR);
 
-	if (!file_exists(seq_dir) && mkdir(seq_dir, 0777) < 0)
+	if (file_exists(seq_dir))
+		return error(_("%s already exists."), seq_dir);
+	else if (mkdir(seq_dir, 0777) < 0)
 		die_errno(_("Could not create sequencer directory '%s'."), seq_dir);
+	return 0;
 }
 
 static void save_head(const char *head)
@@ -912,6 +915,7 @@ static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts)
 static int pick_revisions(struct replay_opts *opts)
 {
 	struct commit_list *todo_list = NULL;
+	const char *me = action_name(opts);
 	unsigned char sha1[20];
 
 	read_and_refresh_cache(opts);
@@ -920,9 +924,18 @@ static int pick_revisions(struct replay_opts *opts)
 		remove_sequencer_state(1);
 		return 0;
 	} else {
-		/* Start a new cherry-pick/ revert sequence */
+		/*
+		 * Start a new cherry-pick/ revert sequence; but
+		 * first, make sure that an existing one isn't in
+		 * progress
+		 */
+
 		walk_revs_populate_todo(&todo_list, opts);
-		create_seq_dir();
+		if (create_seq_dir() < 0) {
+			advise(_("A cherry-pick or revert is in progress."));
+			advise(_("Use --reset to forget about it"));
+			return -1;
+		}
 		if (get_sha1("HEAD", sha1)) {
 			if (opts->action == REVERT)
 				return error(_("Can't revert as initial commit"));
diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh
index 428b5bf..44277f5 100755
--- a/t/t3510-cherry-pick-sequence.sh
+++ b/t/t3510-cherry-pick-sequence.sh
@@ -107,4 +107,14 @@ test_expect_success 'cherry-pick cleans up sequencer state when one commit is le
 	test_cmp expect actual
 '
 
+test_expect_success 'cherry-pick does not implicitly stomp an existing operation' '
+	pristine_detach initial &&
+	test_must_fail git cherry-pick base..anotherpick &&
+	test-chmtime -v +0 .git/sequencer >expect &&
+	test_must_fail git cherry-pick unrelatedpick &&
+	test-chmtime -v +0 .git/sequencer >actual &&
+	test_cmp expect actual &&
+	git cherry-pick --reset
+'
+
 test_done
-- 
1.7.4.rc1.7.g2cf08.dirty
Jonathan Nieder· Jul 27, 2011, 05:19 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 17/18] revert: Don't implictly stomp pending sequencer operation

Ramkumar Ramachandra wrote:
[...]
Show 5 quoted lines
> +		if (create_seq_dir() < 0) {
> +			advise(_("A cherry-pick or revert is in progress."));
> +			advise(_("Use --reset to forget about it"));
> +			return -1;
> +		}
The usual formula is:
	error(... description of error ...)
	if (advice_foo_bar [i.e., if the user is not tired of the advice already]) {
		advise(... how to recover from error ...);
		advise(... more lines ...);
	}
Ramkumar Ramachandra· Jul 27, 2011, 09:59 UTC · re: Jonathan Nieder · lore

Re: [PATCH 17/18] revert: Don't implictly stomp pending sequencer operation

Hi,
Jonathan Nieder writes:
Show 15 quoted lines
> Ramkumar Ramachandra wrote:
> [...]
>> +             if (create_seq_dir() < 0) {
>> +                     advise(_("A cherry-pick or revert is in progress."));
>> +                     advise(_("Use --reset to forget about it"));
>> +                     return -1;
>> +             }
>
> The usual formula is:
>
>        error(... description of error ...)
>        if (advice_foo_bar [i.e., if the user is not tired of the advice already]) {
>                advise(... how to recover from error ...);
>                advise(... more lines ...);
>        }
I think you're trying to say two things here:
1. Put the error() call in the caller.  Why is this a good idea?  The
error is very specific to the create_seq_dir functionality, and has
nothing to do with the caller.  The advice on the other hand, can be
quite caller-specific, which is why I put it in the caller in the
first place.
2. Guard the advice using a variable.  I have to invent a new
configuration variable; can't that wait*?
Thanks.
* Ideally, I'd do these things immediately if I didn't fear picking a
stupid variable name, and implementing it in a daft manner the first
few times.  More delays will result in me doing less post midterm work
(the more interesting bits).
-- Ram
Jonathan Nieder· Jul 27, 2011, 14:33 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 17/18] revert: Don't implictly stomp pending sequencer operation

Ramkumar Ramachandra wrote:
> Jonathan Nieder writes:
>> Ramkumar Ramachandra wrote:
Show 16 quoted lines
>>> +             if (create_seq_dir() < 0) {
>>> +                     advise(_("A cherry-pick or revert is in progress."));
>>> +                     advise(_("Use --reset to forget about it"));
>>> +                     return -1;
>>> +             }
>>
>> The usual formula is:
>>
>>        error(... description of error ...)
>>        if (advice_foo_bar [i.e., if the user is not tired of the advice already]) {
>>                advise(... how to recover from error ...);
>>                advise(... more lines ...);
>>        }
>
> I think you're trying to say two things here:
> 1. Put the error() call in the caller.

No, I didn't mean anything about the caller. Here the error is "a cherry-pick or revert is in progress". Before the advice mechanism existed, such a line would have been written with error(), not note(), right?

[...]
> 2. Guard the advice using a variable.  I have to invent a new
> configuration variable; can't that wait*?

Sure, if the advice is quiet (as it is in this case), I suppose there is no need to guard it until someone using it complains. :)

I mentioned advice configuration to emphasize that output from running git with all "hint:" lines stripped out is supposed to be just as sensible as with them, if I understand the purpose of the facility correctly.

Thanks for thinking it over.
Ramkumar Ramachandra· Jul 27, 2011, 17:06 UTC · re: Jonathan Nieder · lore

Re: [PATCH 17/18] revert: Don't implictly stomp pending sequencer operation

Hi,
Jonathan Nieder writes:
Show 7 quoted lines
> Sure, if the advice is quiet (as it is in this case), I suppose there
> is no need to guard it until someone using it complains. :)
>
> I mentioned advice configuration to emphasize that output from running
> git with all "hint:" lines stripped out is supposed to be just as
> sensible as with them, if I understand the purpose of the facility
> correctly.

Ah, I hadn't thought about this so deeply. Indeed a standalone "error: Could not create sequencer directory '.git/sequencer'." does not make much sense to the end user. Now fix so that the caller reports the error. Thanks.

-- Ram
Ramkumar Ramachandra· Jul 27, 2011, 03:19 UTC · re: Ramkumar Ramachandra · lore

[PATCH 18/18] revert: Introduce --continue to continue the operation

Introduce a new "git cherry-pick --continue" command which uses the information in ".git/sequencer" to continue a cherry-pick that stopped because of a conflict or other error. It works by dropping the first instruction from .git/sequencer/todo and performing the remaining cherry-picks listed there, with options (think "-s" and "-X") from the initial command listed in ".git/sequencer/opts".

So now you can do:

$ git cherry-pick -Xpatience foo..bar ... description conflict in commit moo ... $ git cherry-pick --continue error: 'cherry-pick' is not possible because you have unmerged files. fatal: failed to resume cherry-pick $ echo resolved >conflictingfile $ git add conflictingfile && git commit $ git cherry-pick --continue; # resumes with the commit after "moo"

During the "git commit" stage, CHERRY_PICK_HEAD will aid by providing the commit message from the conflicting "moo" commit. Note that the cherry-pick mechanism has no control at this stage, so the user is free to violate anything that was specified during the first cherry-pick invocation. For example, if "-x" was specified during the first cherry-pick invocation, the user is free to edit out the message during commit time. One glitch to note is that the "--signoff" option specified at cherry-pick invocation time is not reflected in the commit message provided by CHERRY_PICK_HEAD; the user must take care to add "--signoff" during the "git commit" invocation.

Helped-by: Jonathan Nieder <jrnieder@gmail.com>
Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com>
---
 Documentation/git-cherry-pick.txt |    1 +
 Documentation/git-revert.txt      |    1 +
 Documentation/sequencer.txt       |    5 ++
 builtin/revert.c                  |   67 ++++++++++++++++++++++---
 t/t3510-cherry-pick-sequence.sh   |   99 +++++++++++++++++++++++++++++++++++++
 5 files changed, 165 insertions(+), 8 deletions(-)
Show changes to 5 files +165 −8

Documentation/git-cherry-pick.txt, Documentation/git-revert.txt, Documentation/sequencer.txt, builtin/revert.c, t/t3510-cherry-pick-sequence.sh

diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
index 138a292..663186b 100644
--- a/Documentation/git-cherry-pick.txt
+++ b/Documentation/git-cherry-pick.txt
@@ -9,6 +9,7 @@ SYNOPSIS
 --------
 'git cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] [--ff] <commit>...
 'git cherry-pick' --reset
+'git cherry-pick' --continue
 
 DESCRIPTION
 -----------
diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt
index b6789be..923ae51 100644
--- a/Documentation/git-revert.txt
+++ b/Documentation/git-revert.txt
@@ -9,6 +9,7 @@ SYNOPSIS
 --------
 'git revert' [--edit | --no-edit] [-n] [-m parent-number] [-s] <commit>...
 'git revert' --reset
+'git revert' --continue
 
 DESCRIPTION
 -----------
diff --git a/Documentation/sequencer.txt b/Documentation/sequencer.txt
index 16ce88c..3e6df33 100644
--- a/Documentation/sequencer.txt
+++ b/Documentation/sequencer.txt
@@ -2,3 +2,8 @@
 	Forget about the current operation in progress.  Can be used
 	to clear the sequencer state after a failed cherry-pick or
 	revert.
+
+--continue::
+	Continue the operation in progress using the information in
+	'.git/sequencer'.  Can be used to continue after resolving
+	conflicts in a failed cherry-pick or revert.
diff --git a/builtin/revert.c b/builtin/revert.c
index 11973e6..4069abc 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -40,7 +40,7 @@ static const char * const cherry_pick_usage[] = {
 };
 
 enum replay_action { REVERT, CHERRY_PICK };
-enum replay_subcommand { REPLAY_NONE, REPLAY_RESET };
+enum replay_subcommand { REPLAY_NONE, REPLAY_RESET, REPLAY_CONTINUE };
 
 struct replay_opts {
 	enum replay_action action;
@@ -65,7 +65,6 @@ struct replay_opts {
 };
 
 #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
-#define MAYBE_UNUSED __attribute__((__unused__))
 
 static const char *action_name(const struct replay_opts *opts)
 {
@@ -109,14 +108,40 @@ static void verify_opt_compatible(const char *me, const char *base_opt, ...)
 	va_end(ap);
 }
 
+static void verify_opt_mutually_compatible(const char *me, ...)
+{
+       const char *opt1, *opt2;
+       va_list ap;
+       int set;
+
+       va_start(ap, me);
+       while ((opt1 = va_arg(ap, const char *))) {
+	       set = va_arg(ap, int);
+	       if (set)
+		       break;
+       }
+       if (!opt1)
+	       goto ok;
+       while ((opt2 = va_arg(ap, const char *))) {
+	       set = va_arg(ap, int);
+	       if (set)
+		       die(_("%s: %s cannot be used with %s"),
+			       me, opt1, opt2);
+       }
+ok:
+       va_end(ap);
+}
+
 static void parse_args(int argc, const char **argv, struct replay_opts *opts)
 {
 	const char * const * usage_str = revert_or_cherry_pick_usage(opts);
 	const char *me = action_name(opts);
 	int noop;
 	int reset = 0;
+	int contin = 0;
 	struct option options[] = {
 		OPT_BOOLEAN(0, "reset", &reset, "forget the current operation"),
+		OPT_BOOLEAN(0, "continue", &contin, "continue the current operation"),
 		OPT_BOOLEAN('n', "no-commit", &opts->no_commit, "don't automatically commit"),
 		OPT_BOOLEAN('e', "edit", &opts->edit, "edit the commit message"),
 		{ OPTION_BOOLEAN, 'r', NULL, &noop, NULL, "no-op (backward compatibility)",
@@ -146,15 +171,29 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
 					PARSE_OPT_KEEP_ARGV0 |
 					PARSE_OPT_KEEP_UNKNOWN);
 
+	/* Check for incompatible subcommands */
+	verify_opt_mutually_compatible(me,
+				"--reset", reset,
+				"--continue", contin,
+				NULL);
+
 	/* Set the subcommand */
 	if (reset)
 		opts->subcommand = REPLAY_RESET;
+	else if (contin)
+		opts->subcommand = REPLAY_CONTINUE;
 	else
 		opts->subcommand = REPLAY_NONE;
 
 	/* Check for incompatible command line arguments */
-	if (opts->subcommand == REPLAY_RESET) {
-		verify_opt_compatible(me, "--reset",
+	if (opts->subcommand != REPLAY_NONE) {
+		char *this_operation;
+		if (opts->subcommand == REPLAY_RESET)
+			this_operation = "--reset";
+		else
+			this_operation = "--continue";
+
+		verify_opt_compatible(me, this_operation,
 				"--no-commit", opts->no_commit,
 				"--signoff", opts->signoff,
 				"--mainline", opts->mainline,
@@ -702,8 +741,8 @@ static struct commit *parse_insn_line(char *start, struct replay_opts *opts)
 	return commit;
 }
 
-static void MAYBE_UNUSED read_populate_todo(struct commit_list **todo_list,
-					struct replay_opts *opts)
+static void read_populate_todo(struct commit_list **todo_list,
+			struct replay_opts *opts)
 {
 	const char *todo_file = git_path(SEQ_TODO_FILE);
 	struct strbuf buf = STRBUF_INIT;
@@ -772,7 +811,7 @@ static int populate_opts_cb(const char *key, const char *value, void *data)
 	return 0;
 }
 
-static void MAYBE_UNUSED read_populate_opts(struct replay_opts **opts_ptr)
+static void read_populate_opts(struct replay_opts **opts_ptr)
 {
 	const char *opts_file = git_path(SEQ_OPTS_FILE);
 
@@ -923,6 +962,15 @@ static int pick_revisions(struct replay_opts *opts)
 	if (opts->subcommand == REPLAY_RESET) {
 		remove_sequencer_state(1);
 		return 0;
+	} else if (opts->subcommand == REPLAY_CONTINUE) {
+		if (!file_exists(git_path(SEQ_TODO_FILE)))
+			goto error;
+		read_populate_opts(&opts);
+		read_populate_todo(&todo_list, opts);
+
+		/* Verify that the conflict has been resolved */
+		if (!index_differs_from("HEAD", 0))
+			todo_list = todo_list->next;
 	} else {
 		/*
 		 * Start a new cherry-pick/ revert sequence; but
@@ -933,7 +981,8 @@ static int pick_revisions(struct replay_opts *opts)
 		walk_revs_populate_todo(&todo_list, opts);
 		if (create_seq_dir() < 0) {
 			advise(_("A cherry-pick or revert is in progress."));
-			advise(_("Use --reset to forget about it"));
+			advise(_("Use --continue to continue the operation"));
+			advise(_("or --reset to forget about it"));
 			return -1;
 		}
 		if (get_sha1("HEAD", sha1)) {
@@ -946,6 +995,8 @@ static int pick_revisions(struct replay_opts *opts)
 		save_todo(todo_list, opts);
 	}
 	return pick_commits(todo_list, opts);
+error:
+	return error(_("No %s in progress"), me);
 }
 
 int cmd_revert(int argc, const char **argv, const char *prefix)
diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh
index 44277f5..0267714 100755
--- a/t/t3510-cherry-pick-sequence.sh
+++ b/t/t3510-cherry-pick-sequence.sh
@@ -117,4 +117,103 @@ test_expect_success 'cherry-pick does not implicitly stomp an existing operation
 	git cherry-pick --reset
 '
 
+test_expect_success '--continue complains when no cherry-pick is in progress' '
+	pristine_detach initial &&
+	test_must_fail git cherry-pick --continue
+'
+
+test_expect_success '--continue complains when there are unresolved conflicts' '
+	pristine_detach initial &&
+	test_must_fail git cherry-pick base..picked &&
+	test_must_fail git cherry-pick --continue &&
+	git cherry-pick --reset
+'
+
+test_expect_success '--continue continues after conflicts are resolved' '
+	pristine_detach initial &&
+	test_must_fail git cherry-pick base..anotherpick &&
+	echo "c" >foo &&
+	git add foo &&
+	git commit &&
+	git cherry-pick --continue &&
+	test_path_is_missing .git/sequencer &&
+	{
+		git rev-list HEAD |
+		git diff-tree --root --stdin |
+		sed "s/$_x40/OBJID/g"
+	} >actual &&
+	cat >expect <<-\EOF &&
+	OBJID
+	:100644 100644 OBJID OBJID M	foo
+	OBJID
+	:100644 100644 OBJID OBJID M	foo
+	OBJID
+	:100644 100644 OBJID OBJID M	unrelated
+	OBJID
+	:000000 100644 OBJID OBJID A	foo
+	:000000 100644 OBJID OBJID A	unrelated
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success '--continue respects opts' '
+	pristine_detach initial &&
+	test_must_fail git cherry-pick -x base..anotherpick &&
+	echo "c" >foo &&
+	git add foo &&
+	git commit &&
+	git cherry-pick --continue &&
+	test_path_is_missing .git/sequencer &&
+	git cat-file commit HEAD >anotherpick_msg &&
+	git cat-file commit HEAD~1 >picked_msg &&
+	git cat-file commit HEAD~2 >unrelatedpick_msg &&
+	git cat-file commit HEAD~3 >initial_msg &&
+	test_must_fail grep "cherry picked from" initial_msg &&
+	grep "cherry picked from" unrelatedpick_msg &&
+	grep "cherry picked from" picked_msg &&
+	grep "cherry picked from" anotherpick_msg
+'
+
+test_expect_success '--signoff is not automatically propogated to resolved conflict' '
+	pristine_detach initial &&
+	test_must_fail git cherry-pick --signoff base..anotherpick &&
+	echo "c" >foo &&
+	git add foo &&
+	git commit &&
+	git cherry-pick --continue &&
+	test_path_is_missing .git/sequencer &&
+	git cat-file commit HEAD >anotherpick_msg &&
+	git cat-file commit HEAD~1 >picked_msg &&
+	git cat-file commit HEAD~2 >unrelatedpick_msg &&
+	git cat-file commit HEAD~3 >initial_msg &&
+	test_must_fail grep "Signed-off-by:" initial_msg &&
+	grep "Signed-off-by:" unrelatedpick_msg &&
+	test_must_fail grep "Signed-off-by:" picked_msg &&
+	grep "Signed-off-by:" anotherpick_msg
+'
+
+test_expect_success 'malformed instruction sheet 1' '
+	pristine_detach initial &&
+	test_must_fail git cherry-pick base..picked &&
+	echo "resolved" >foo &&
+	git add foo &&
+	git commit &&
+	sed "s/pick /pick/" .git/sequencer/todo >new_sheet
+	cp new_sheet .git/sequencer/todo
+	test_must_fail git cherry-pick --continue &&
+	git cherry-pick --reset
+'
+
+test_expect_success 'malformed instruction sheet 2' '
+	pristine_detach initial &&
+	test_must_fail git cherry-pick base..picked &&
+	echo "resolved" >foo &&
+	git add foo &&
+	git commit &&
+	sed "s/pick/revert/" .git/sequencer/todo >new_sheet
+	cp new_sheet .git/sequencer/todo
+	test_must_fail git cherry-pick --continue &&
+	git cherry-pick --reset
+'
+
 test_done
-- 
1.7.4.rc1.7.g2cf08.dirty
Jonathan Nieder· Jul 27, 2011, 05:22 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 18/18] revert: Introduce --continue to continue the operation

Ramkumar Ramachandra wrote:
> during commit time.  One glitch to note is that the "--signoff" option
> specified at cherry-pick invocation time is not reflected in the
> commit message provided by CHERRY_PICK_HEAD; the user must take care
> to add "--signoff" during the "git commit" invocation.

Does this reflect the previous discussion of whether this is a glitch or an intentional behavior?

Ramkumar Ramachandra· Jul 27, 2011, 09:36 UTC · re: Jonathan Nieder · lore

Re: [PATCH 18/18] revert: Introduce --continue to continue the operation

Hi Jonathan,
Jonathan Nieder writes:
Show 8 quoted lines
> Ramkumar Ramachandra wrote:
>> during commit time.  One glitch to note is that the "--signoff" option
>> specified at cherry-pick invocation time is not reflected in the
>> commit message provided by CHERRY_PICK_HEAD; the user must take care
>> to add "--signoff" during the "git commit" invocation.
>
> Does this reflect the previous discussion of whether this is a glitch
> or an intentional behavior?

The previous discussion thread for your reference [1] -- I thought we agreed that it was a glitch. Neither of us can find any traces of it being intentional, and the commit that introduced this option doesn't say this: v1.5.6-rc0~83^2 (Allow cherry-pick (and revert) to add signoff line, 2008-04-26).

[1]: http://thread.gmane.org/gmane.comp.version-control.git/176855/
-- Ram
Jonathan Nieder· Jul 27, 2011, 15:42 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 18/18] revert: Introduce --continue to continue the operation

Ramkumar Ramachandra wrote:
>> Ramkumar Ramachandra wrote:
>>> during commit time.  One glitch to note is that the "--signoff" option
>>> specified at cherry-pick invocation time is not reflected in the
>>> commit message provided by CHERRY_PICK_HEAD
[...]
> The previous discussion thread for your reference [1] -- I thought we
> agreed that it was a glitch.

Ok. I thought it had come up before and that there was not wide agreement but probably I imagined it. How about the patch below (needs commit message, tests)?

Anyway, this is simple program logic that would be easy to change, not a short-lived fault like the term "glitch" usually refers to.

> Neither of us can find any traces of it
> being intentional, and the commit that introduced this option doesn't
> say this: v1.5.6-rc0~83^2 (Allow cherry-pick (and revert) to add
> signoff line, 2008-04-26).

At the time, -x was not propagated to "git commit", either. I guess it was not considered notable that -s worked the same way, as in "Of course 'git commit -c $original_commit' is going to discard MERGE_MSG."

Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
---
 builtin/commit.c |   62 +---------------------------------------------
 builtin/revert.c |    5 ++-
 commit.c         |   71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 commit.h         |    3 ++
 4 files changed, 79 insertions(+), 62 deletions(-)
Show changes to diff +79 −62
diff --git c/builtin/commit.c i/builtin/commit.c
index e1af9b19..55ae8294 100644
--- c/builtin/commit.c
+++ i/builtin/commit.c
@@ -524,8 +524,6 @@ static int is_a_merge(const unsigned char *sha1)
 	return !!(commit->parents && commit->parents->next);
 }
 
-static const char sign_off_header[] = "Signed-off-by: ";
-
 static void determine_author_info(struct strbuf *author_ident)
 {
 	char *name, *email, *date;
@@ -574,47 +572,6 @@ static void determine_author_info(struct strbuf *author_ident)
 					      IDENT_ERROR_ON_NO_NAME));
 }
 
-static int ends_rfc2822_footer(struct strbuf *sb)
-{
-	int ch;
-	int hit = 0;
-	int i, j, k;
-	int len = sb->len;
-	int first = 1;
-	const char *buf = sb->buf;
-
-	for (i = len - 1; i > 0; i--) {
-		if (hit && buf[i] == '\n')
-			break;
-		hit = (buf[i] == '\n');
-	}
-
-	while (i < len - 1 && buf[i] == '\n')
-		i++;
-
-	for (; i < len; i = k) {
-		for (k = i; k < len && buf[k] != '\n'; k++)
-			; /* do nothing */
-		k++;
-
-		if ((buf[k] == ' ' || buf[k] == '\t') && !first)
-			continue;
-
-		first = 0;
-
-		for (j = 0; i + j < len; j++) {
-			ch = buf[i + j];
-			if (ch == ':')
-				break;
-			if (isalnum(ch) ||
-			    (ch == '-'))
-				continue;
-			return 0;
-		}
-	}
-	return 1;
-}
-
 static char *cut_ident_timestamp_part(char *string)
 {
 	char *ket = strrchr(string, '>');
@@ -734,23 +691,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 	if (clean_message_contents)
 		stripspace(&sb, 0);
 
-	if (signoff) {
-		struct strbuf sob = STRBUF_INIT;
-		int i;
-
-		strbuf_addstr(&sob, sign_off_header);
-		strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"),
-					     getenv("GIT_COMMITTER_EMAIL")));
-		strbuf_addch(&sob, '\n');
-		for (i = sb.len - 1; i > 0 && sb.buf[i - 1] != '\n'; i--)
-			; /* do nothing */
-		if (prefixcmp(sb.buf + i, sob.buf)) {
-			if (!i || !ends_rfc2822_footer(&sb))
-				strbuf_addch(&sb, '\n');
-			strbuf_addbuf(&sb, &sob);
-		}
-		strbuf_release(&sob);
-	}
+	if (signoff)
+		add_signoff(&sb);
 
 	if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)
 		die_errno(_("could not write commit template"));
diff --git c/builtin/revert.c i/builtin/revert.c
index 1f27c633..79ed9883 100644
--- c/builtin/revert.c
+++ i/builtin/revert.c
@@ -369,8 +369,6 @@ static int run_git_commit(const char *defmsg)
 
 	args[i++] = "commit";
 	args[i++] = "-n";
-	if (signoff)
-		args[i++] = "-s";
 	if (!edit) {
 		args[i++] = "-F";
 		args[i++] = defmsg;
@@ -485,6 +483,9 @@ static int do_pick_commit(void)
 			write_cherry_pick_head();
 	}
 
+	if (signoff)
+		add_signoff(&msgbuf);
+
 	if (!strategy || !strcmp(strategy, "recursive") || action == REVERT) {
 		res = do_recursive_merge(base, next, base_label, next_label,
 					 head, &msgbuf);
diff --git c/commit.c i/commit.c
index ac337c7d..5022db6e 100644
--- c/commit.c
+++ i/commit.c
@@ -10,6 +10,7 @@
 int save_commit_buffer = 1;
 
 const char *commit_type = "commit";
+const char sign_off_header[] = "Signed-off-by: ";
 
 static struct commit *check_commit(struct object *obj,
 				   const unsigned char *sha1,
@@ -348,6 +349,76 @@ int find_commit_subject(const char *commit_buffer, const char **subject)
 	return eol - p;
 }
 
+static int ends_rfc2822_footer(struct strbuf *sb)
+{
+	int ch;
+	int hit = 0;
+	int i, j, k;
+	int len = sb->len;
+	int first = 1;
+	const char *buf = sb->buf;
+
+	for (i = len - 1; i > 0; i--) {
+		if (hit && buf[i] == '\n')
+			break;
+		hit = (buf[i] == '\n');
+	}
+
+	while (i < len - 1 && buf[i] == '\n')
+		i++;
+
+	for (; i < len; i = k) {
+		for (k = i; k < len && buf[k] != '\n'; k++)
+			; /* do nothing */
+		k++;
+
+		if ((buf[k] == ' ' || buf[k] == '\t') && !first)
+			continue;
+
+		first = 0;
+
+		for (j = 0; i + j < len; j++) {
+			ch = buf[i + j];
+			if (ch == ':')
+				break;
+			if (isalnum(ch) ||
+			    (ch == '-'))
+				continue;
+			return 0;
+		}
+	}
+	return 1;
+}
+
+void add_signoff(struct strbuf *sb)
+{
+	struct strbuf sob = STRBUF_INIT;
+	const char *p;
+
+	strbuf_addstr(&sob, sign_off_header);
+	strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"),
+				     getenv("GIT_COMMITTER_EMAIL")));
+	strbuf_addch(&sob, '\n');
+
+	/* final line */
+	p = memrchr(sb->buf, '\n', sb->len ? sb->len - 1 : 0);
+	if (!p)
+		p = sb->buf;
+	else
+		p++;
+
+	/*
+	 * If the line does not already contain our sign-off,
+	 * add it.
+	 */
+	if (prefixcmp(p, sob.buf)) {
+		if (p == sb->buf || !ends_rfc2822_footer(sb))
+			strbuf_addch(sb, '\n');
+		strbuf_addbuf(sb, &sob);
+	}
+	strbuf_release(&sob);
+}
+
 struct commit_list *commit_list_insert(struct commit *item, struct commit_list **list_p)
 {
 	struct commit_list *new_list = xmalloc(sizeof(struct commit_list));
diff --git c/commit.h i/commit.h
index a2d571b9..3d198afb 100644
--- c/commit.h
+++ i/commit.h
@@ -23,6 +23,7 @@ struct commit {
 
 extern int save_commit_buffer;
 extern const char *commit_type;
+extern const char sign_off_header[];
 
 /* While we can decorate any object with a name, it's only used for commits.. */
 extern struct decoration name_decoration;
@@ -44,6 +45,8 @@ int parse_commit(struct commit *item);
 /* Find beginning and length of commit subject. */
 int find_commit_subject(const char *commit_buffer, const char **subject);
 
+void add_signoff(struct strbuf *sb);
+
 struct commit_list *commit_list_insert(struct commit *item,
 					struct commit_list **list);
 unsigned commit_list_count(const struct commit_list *l);
Ramkumar Ramachandra· Jul 27, 2011, 15:56 UTC · re: Jonathan Nieder · lore

Re: [PATCH 18/18] revert: Introduce --continue to continue the operation

Hi Jonathan,
Jonathan Nieder writes:
Show 7 quoted lines
> Ramkumar Ramachandra wrote:
>> The previous discussion thread for your reference [1] -- I thought we
>> agreed that it was a glitch.
>
> Ok.  I thought it had come up before and that there was not wide
> agreement but probably I imagined it.  How about the patch below
> (needs commit message, tests)?

Looks good! Thanks for working on this. We can include this near the beginning of the series I think.

-- Ram
Junio C Hamano· Jul 27, 2011, 22:52 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 18/18] revert: Introduce --continue to continue the operation

Ramkumar Ramachandra <artagnon@gmail.com> writes:
Show 12 quoted lines
> @@ -109,14 +108,40 @@ static void verify_opt_compatible(const char *me, const char *base_opt, ...)
>  	va_end(ap);
>  }
>  
> +static void verify_opt_mutually_compatible(const char *me, ...)
> +{
> +       const char *opt1, *opt2;
> +       va_list ap;
> +       int set;
> +
> +       va_start(ap, me);
> +       while ((opt1 = va_arg(ap, const char *))) {

(style) Many lines indented with SP thoughout the patch.

Show 5 quoted lines
> +	       set = va_arg(ap, int);
> +	       if (set)
> +		       break;
> +       }
> +       if (!opt1)
Ramkumar Ramachandra· Jul 28, 2011, 13:16 UTC · re: Junio C Hamano · lore

Re: [PATCH 18/18] revert: Introduce --continue to continue the operation

Hi,
Junio C Hamano writes:
> (style)
> Many lines indented with SP thoughout the patch.

Fixed. I really should use the kernel's checkpatch.pl before sending out patches.

-- Ram
Junio C Hamano· Jul 27, 2011, 22:59 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 00/18] GSoC update: Sequencer for inclusion v3

As I already said in the earlier "What's cooking" message, this series is getting very closer, and the difference between the last round and this seems to be just "allow explicit file to be given to the config writer" (with help from Peff) which is very good, and "remove 'Please, do this'".

Both of which are good. I offhand do not recall if there were other issues raised in the previous review round.

Ramkumar Ramachandra· Jul 28, 2011, 16:26 UTC · re: Junio C Hamano · lore

Re: [PATCH 00/18] GSoC update: Sequencer for inclusion v3

Hi Junio,
Junio C Hamano writes:
> As I already said in the earlier "What's cooking" message, this series is
> getting very closer, and the difference between the last round and this
> seems to be just "allow explicit file to be given to the config writer"
> (with help from Peff) which is very good, and "remove 'Please, do this'".
Right, and some minor commit message improvements.
> Both of which are good. I offhand do not recall if there were other issues
> raised in the previous review round.
No other issues were raised.  I've taken care of all the issues that
were raised by you and Jonathan this iteration too, with a couple of
exceptions:
1. "revert: Remove sequencer state when no commits are pending" isn't
very elegant.  I've decided that the right way to fix this is to
increase the coupling between "git commit" and the sequencer -- I
think this can wait.
2. Jonathan suggested some improvements to "git checkout" behavior in
"reset: Make reset remove the sequencer state".  I think this can wait
too.

Do you think the series is alright otherwise? I'll post another iteration of the sequencer momentarily.

Thanks.
-- Ram
Ramkumar Ramachandra· Jul 28, 2011, 16:32 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 00/18] GSoC update: Sequencer for inclusion v3

Hi again,
Ramkumar Ramachandra writes:
> Do you think the series is alright otherwise?  I'll post another
> iteration of the sequencer momentarily.

Um, one more thing. I've also decided to let Jonathan's commit signoff-factoring patch [1] wait until the next series.

[1]: https://github.com/artagnon/git/commit/0494a
Thanks.
-- Ram
Jonathan Nieder· Jul 28, 2011, 16:39 UTC · re: Ramkumar Ramachandra · lore

Re: [PATCH 00/18] GSoC update: Sequencer for inclusion v3

Ramkumar Ramachandra wrote:
> Um, one more thing.  I've also decided to let Jonathan's commit
> signoff-factoring patch [1] wait until the next series.
>
> [1]: https://github.com/artagnon/git/commit/0494a

Sure, I don't think it belongs in the series at all fwiw --- it can migrate to master independently. It was mostly a way to deter a non-sequitor in the commit message. :)

← back to recent threads