From 19bff7ab833be6005e87babf4378467a413f66ba Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 01/92] sequencer: lib'ify sequencer_pick_revisions() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The function sequencer_pick_revisions() has only two callers, cmd_revert() and cmd_cherry_pick(), both of which check the return value and react appropriately upon errors. So this is a safe conversion to make sequencer_pick_revisions() callable from new callers that want it not to die, without changing the external behaviour of anything existing. Signed-off-by: Johannes Schindelin --- sequencer.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/sequencer.c b/sequencer.c index 3804fa931d..76b1c52f75 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1063,10 +1063,11 @@ int sequencer_pick_revisions(struct replay_opts *opts) if (!get_sha1(name, sha1)) { if (!lookup_commit_reference_gently(sha1, 1)) { enum object_type type = sha1_object_info(sha1, NULL); - die(_("%s: can't cherry-pick a %s"), name, typename(type)); + return error(_("%s: can't cherry-pick a %s"), + name, typename(type)); } } else - die(_("%s: bad revision"), name); + return error(_("%s: bad revision"), name); } /* @@ -1082,10 +1083,10 @@ int sequencer_pick_revisions(struct replay_opts *opts) !opts->revs->cmdline.rev->flags) { struct commit *cmit; if (prepare_revision_walk(opts->revs)) - die(_("revision walk setup failed")); + return error(_("revision walk setup failed")); cmit = get_revision(opts->revs); if (!cmit || get_revision(opts->revs)) - die("BUG: expected exactly one commit from walk"); + return error("BUG: expected exactly one commit from walk"); return single_pick(cmit, opts); } From 129670a507f24d86ab5cde39d6e1dacf88d6a3a1 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 23 Aug 2016 08:54:00 +0200 Subject: [PATCH 02/92] sequencer: do not die() in do_pick_commit() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The eventual caller of do_pick_commit() is sequencer_pick_revisions(), which already relays a reported error from its helper functions (including this one), and both of its two callers know how to react to a negative return correctly. So this makes do_pick_commit() callable from new callers that want it not to die, without changing the external behaviour of anything existing. Signed-off-by: Johannes Schindelin --- sequencer.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/sequencer.c b/sequencer.c index 76b1c52f75..baf6b40e7a 100644 --- a/sequencer.c +++ b/sequencer.c @@ -585,12 +585,14 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) * However, if the merge did not even start, then we don't want to * write it at all. */ - if (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1)) - update_ref(NULL, "CHERRY_PICK_HEAD", commit->object.oid.hash, NULL, - REF_NODEREF, UPDATE_REFS_DIE_ON_ERR); - if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1)) - update_ref(NULL, "REVERT_HEAD", commit->object.oid.hash, NULL, - REF_NODEREF, UPDATE_REFS_DIE_ON_ERR); + if (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1) && + update_ref(NULL, "CHERRY_PICK_HEAD", commit->object.oid.hash, NULL, + REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) + res = -1; + if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1) && + update_ref(NULL, "REVERT_HEAD", commit->object.oid.hash, NULL, + REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) + res = -1; if (res) { error(opts->action == REPLAY_REVERT From 43ed203fda0a9ce33947cd84affed00cdb2ca858 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 03/92] sequencer: lib'ify write_message() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The only caller of write_message(), do_pick_commit() already checks the return value and passes it on to its callers, so its caller must be already prepared to handle error returns, and with this step, we make it notice an error return from this function. So this is a safe conversion to make write_message() callable from new callers that want it not to die, without changing the external behaviour of anything existing. Signed-off-by: Johannes Schindelin --- sequencer.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/sequencer.c b/sequencer.c index baf6b40e7a..ec85fe77b4 100644 --- a/sequencer.c +++ b/sequencer.c @@ -180,17 +180,20 @@ static void print_advice(int show_hint, struct replay_opts *opts) } } -static void write_message(struct strbuf *msgbuf, const char *filename) +static int write_message(struct strbuf *msgbuf, const char *filename) { static struct lock_file msg_file; - int msg_fd = hold_lock_file_for_update(&msg_file, filename, - LOCK_DIE_ON_ERROR); + int msg_fd = hold_lock_file_for_update(&msg_file, filename, 0); + if (msg_fd < 0) + return error_errno(_("Could not lock '%s'"), filename); if (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0) - die_errno(_("Could not write to %s"), filename); + return error_errno(_("Could not write to %s"), filename); strbuf_release(msgbuf); if (commit_lock_file(&msg_file) < 0) - die(_("Error wrapping up %s."), filename); + return error(_("Error wrapping up %s."), filename); + + return 0; } static struct tree *empty_tree(void) @@ -564,16 +567,16 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) head, &msgbuf, opts); if (res < 0) return res; - write_message(&msgbuf, git_path_merge_msg()); + res |= write_message(&msgbuf, git_path_merge_msg()); } else { struct commit_list *common = NULL; struct commit_list *remotes = NULL; - write_message(&msgbuf, git_path_merge_msg()); + res = write_message(&msgbuf, git_path_merge_msg()); commit_list_insert(base, &common); commit_list_insert(next, &remotes); - res = try_merge_command(opts->strategy, opts->xopts_nr, opts->xopts, + 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); From dc997549fb0f5c05f9292aa02dcda74e0c04d445 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 04/92] sequencer: lib'ify do_recursive_merge() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The only caller of do_recursive_merge(), do_pick_commit() already checks the return value and passes it on to its callers, so its caller must be already prepared to handle error returns, and with this step, we make it notice an error return from this function. So this is a safe conversion to make do_recursive_merge() callable from new callers that want it not to die, without changing the external behaviour of anything existing. Signed-off-by: Johannes Schindelin --- sequencer.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sequencer.c b/sequencer.c index ec85fe77b4..eb700913a1 100644 --- a/sequencer.c +++ b/sequencer.c @@ -303,7 +303,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next, if (active_cache_changed && write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) /* TRANSLATORS: %s will be "revert" or "cherry-pick" */ - die(_("%s: Unable to write new index file"), action_name(opts)); + return error(_("%s: Unable to write new index file"), + action_name(opts)); rollback_lock_file(&index_lock); if (opts->signoff) From 033cc614c82cd8b703d682a9ef69e20bf28d47a2 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 05/92] sequencer: lib'ify do_pick_commit() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The only two callers of do_pick_commit(), pick_commits() and single_pick() already check the return value and pass it on to their callers, so their callers must be already prepared to handle error returns, and with this step, we make it notice an error return from this function. So this is a safe conversion to make do_pick_commit() callable from new callers that want it not to die, without changing the external behaviour of anything existing. While at it, remove the superfluous space. Signed-off-by: Johannes Schindelin --- sequencer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sequencer.c b/sequencer.c index eb700913a1..96b9ae1978 100644 --- a/sequencer.c +++ b/sequencer.c @@ -464,7 +464,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) * to work on. */ if (write_cache_as_tree(head, 0, NULL)) - die (_("Your index file is unmerged.")); + return error(_("Your index file is unmerged.")); } else { unborn = get_sha1("HEAD", head); if (unborn) From 29ca1810c2318a7ab54117e2d76955a130e7db24 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 06/92] sequencer: lib'ify walk_revs_populate_todo() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The function sequencer_pick_revisions() is the only caller of walk_revs_populate_todo(), and it already returns errors appropriately, so its caller must be already prepared to handle error returns, and with this step, we make it notice an error return from this function. So this is a safe conversion to make walk_revs_populate_todo() callable from new callers that want it not to die, without changing the external behaviour of anything existing. Signed-off-by: Johannes Schindelin --- sequencer.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sequencer.c b/sequencer.c index 96b9ae1978..ab599e04df 100644 --- a/sequencer.c +++ b/sequencer.c @@ -809,17 +809,19 @@ static void read_populate_opts(struct replay_opts **opts_ptr) die(_("Malformed options sheet: %s"), git_path_opts_file()); } -static void walk_revs_populate_todo(struct commit_list **todo_list, +static int walk_revs_populate_todo(struct commit_list **todo_list, struct replay_opts *opts) { struct commit *commit; struct commit_list **next; - prepare_revs(opts); + if (prepare_revs(opts)) + return -1; next = todo_list; while ((commit = get_revision(opts->revs))) next = commit_list_append(commit, next); + return 0; } static int create_seq_dir(void) @@ -1102,8 +1104,8 @@ int sequencer_pick_revisions(struct replay_opts *opts) * progress */ - walk_revs_populate_todo(&todo_list, opts); - if (create_seq_dir() < 0) + if (walk_revs_populate_todo(&todo_list, opts) || + create_seq_dir() < 0) return -1; if (get_sha1("HEAD", sha1) && (opts->action == REPLAY_REVERT)) return error(_("Can't revert as initial commit")); From b009135221ad18ae35082b14848547a25bf5b703 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 07/92] sequencer: lib'ify prepare_revs() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The only caller of prepare_revs(), walk_revs_populate_todo() was just taught to return errors, after verifying that its callers are prepared to handle error returns, and with this step, we make it notice an error return from this function. So this is a safe conversion to make prepare_revs() callable from new callers that want it not to die, without changing the external behaviour of anything existing. Signed-off-by: Johannes Schindelin --- sequencer.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sequencer.c b/sequencer.c index ab599e04df..7fd0f99650 100644 --- a/sequencer.c +++ b/sequencer.c @@ -623,7 +623,7 @@ leave: return res; } -static void prepare_revs(struct replay_opts *opts) +static int prepare_revs(struct replay_opts *opts) { /* * picking (but not reverting) ranges (but not individual revisions) @@ -633,10 +633,11 @@ static void prepare_revs(struct replay_opts *opts) opts->revs->reverse ^= 1; if (prepare_revision_walk(opts->revs)) - die(_("revision walk setup failed")); + return error(_("revision walk setup failed")); if (!opts->revs->commits) - die(_("empty commit set passed")); + return error(_("empty commit set passed")); + return 0; } static void read_and_refresh_cache(struct replay_opts *opts) From 1fa21d077bc6aefb6bb2a5d6d0ec822555345100 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 08/92] sequencer: lib'ify read_and_refresh_cache() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). There are two call sites of read_and_refresh_cache(), one of which is pick_commits(), whose callers were already prepared to do the right thing given an "error" return from it by an earlier patch, so the conversion is safe. The other one, sequencer_pick_revisions() was also prepared to relay an error return back to its caller in all remaining cases in an earlier patch. Signed-off-by: Johannes Schindelin --- sequencer.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/sequencer.c b/sequencer.c index 7fd0f99650..631b75dabe 100644 --- a/sequencer.c +++ b/sequencer.c @@ -640,18 +640,21 @@ static int prepare_revs(struct replay_opts *opts) return 0; } -static void read_and_refresh_cache(struct replay_opts *opts) +static int 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"), action_name(opts)); + return error(_("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 && index_fd >= 0) { if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) - die(_("git %s: failed to refresh the index"), action_name(opts)); + return error(_("git %s: failed to refresh the index"), + action_name(opts)); } rollback_lock_file(&index_lock); + return 0; } static int format_todo(struct strbuf *buf, struct commit_list *todo_list, @@ -981,7 +984,8 @@ static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts) if (opts->allow_ff) assert(!(opts->signoff || opts->no_commit || opts->record_origin || opts->edit)); - read_and_refresh_cache(opts); + if (read_and_refresh_cache(opts)) + return -1; for (cur = todo_list; cur; cur = cur->next) { save_todo(cur, opts); @@ -1045,7 +1049,8 @@ int sequencer_pick_revisions(struct replay_opts *opts) if (opts->subcommand == REPLAY_NONE) assert(opts->revs); - read_and_refresh_cache(opts); + if (read_and_refresh_cache(opts)) + return -1; /* * Decide what to do depending on the arguments; a fresh From dfefb63933d8d08915b3545b5857c6952bed7337 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 09/92] sequencer: lib'ify read_populate_todo() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The only caller of read_populate_todo(), sequencer_continue() can already return errors, so its caller must be already prepared to handle error returns, and with this step, we make it notice an error return from this function. So this is a safe conversion to make read_populate_todo() callable from new callers that want it not to die, without changing the external behaviour of anything existing. Signed-off-by: Johannes Schindelin --- sequencer.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/sequencer.c b/sequencer.c index 631b75dabe..c73cdfdac1 100644 --- a/sequencer.c +++ b/sequencer.c @@ -748,7 +748,7 @@ static int parse_insn_buffer(char *buf, struct commit_list **todo_list, return 0; } -static void read_populate_todo(struct commit_list **todo_list, +static int read_populate_todo(struct commit_list **todo_list, struct replay_opts *opts) { struct strbuf buf = STRBUF_INIT; @@ -756,18 +756,21 @@ static void read_populate_todo(struct commit_list **todo_list, fd = open(git_path_todo_file(), O_RDONLY); if (fd < 0) - die_errno(_("Could not open %s"), git_path_todo_file()); + return error_errno(_("Could not open %s"), + git_path_todo_file()); if (strbuf_read(&buf, fd, 0) < 0) { close(fd); strbuf_release(&buf); - die(_("Could not read %s."), git_path_todo_file()); + return error(_("Could not read %s."), git_path_todo_file()); } close(fd); res = parse_insn_buffer(buf.buf, todo_list, opts); strbuf_release(&buf); if (res) - die(_("Unusable instruction sheet: %s"), git_path_todo_file()); + return error(_("Unusable instruction sheet: %s"), + git_path_todo_file()); + return 0; } static int populate_opts_cb(const char *key, const char *value, void *data) @@ -1019,7 +1022,8 @@ static int sequencer_continue(struct replay_opts *opts) if (!file_exists(git_path_todo_file())) return continue_single_pick(); read_populate_opts(&opts); - read_populate_todo(&todo_list, opts); + if (read_populate_todo(&todo_list, opts)) + return -1; /* Verify that the conflict has been resolved */ if (file_exists(git_path_cherry_pick_head()) || From 70b059f1f6781a8425c37d04ebfaf629279a7052 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 10/92] sequencer: lib'ify read_populate_opts() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The only caller of read_populate_opts(), sequencer_continue() can already return errors, so its caller must be already prepared to handle error returns, and with this step, we make it notice an error return from this function. So this is a safe conversion to make read_populate_opts() callable from new callers that want it not to die, without changing the external behaviour of anything existing. Note that the function git_config_from_file(), called from read_populate_opts(), can currently still die() (in git_parse_source(), because the do_config_from_file() function sets die_on_error = 1). We do not try to fix that here, as it would have larger ramifications on the config code, and we also assume that we write the opts file programmatically, hence any parse errors would be bugs. Signed-off-by: Johannes Schindelin --- sequencer.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/sequencer.c b/sequencer.c index c73cdfdac1..1614efb8db 100644 --- a/sequencer.c +++ b/sequencer.c @@ -808,12 +808,20 @@ static int populate_opts_cb(const char *key, const char *value, void *data) return 0; } -static void read_populate_opts(struct replay_opts **opts_ptr) +static int read_populate_opts(struct replay_opts **opts) { if (!file_exists(git_path_opts_file())) - return; - if (git_config_from_file(populate_opts_cb, git_path_opts_file(), *opts_ptr) < 0) - die(_("Malformed options sheet: %s"), git_path_opts_file()); + return 0; + /* + * The function git_parse_source(), called from git_config_from_file(), + * may die() in case of a syntactically incorrect file. We do not care + * about this case, though, because we wrote that file ourselves, so we + * are pretty certain that it is syntactically correct. + */ + if (git_config_from_file(populate_opts_cb, git_path_opts_file(), *opts) < 0) + return error(_("Malformed options sheet: %s"), + git_path_opts_file()); + return 0; } static int walk_revs_populate_todo(struct commit_list **todo_list, @@ -1021,8 +1029,8 @@ static int sequencer_continue(struct replay_opts *opts) if (!file_exists(git_path_todo_file())) return continue_single_pick(); - read_populate_opts(&opts); - if (read_populate_todo(&todo_list, opts)) + if (read_populate_opts(&opts) || + read_populate_todo(&todo_list, opts)) return -1; /* Verify that the conflict has been resolved */ From 156029cda0d5097901790a7cf3499910aa0456bb Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 11/92] sequencer: lib'ify create_seq_dir() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The only caller of create_seq_dir(), sequencer_pick_revisions() can already return errors, so its caller must be already prepared to handle error returns, and with this step, we make it notice an error return from this function. So this is a safe conversion to make create_seq_dir() callable from new callers that want it not to die, without changing the external behaviour of anything existing. Signed-off-by: Johannes Schindelin --- sequencer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sequencer.c b/sequencer.c index 1614efb8db..eb9c473328 100644 --- a/sequencer.c +++ b/sequencer.c @@ -847,8 +847,8 @@ static int create_seq_dir(void) return -1; } else if (mkdir(git_path_seq_dir(), 0777) < 0) - die_errno(_("Could not create sequencer directory %s"), - git_path_seq_dir()); + return error_errno(_("Could not create sequencer directory %s"), + git_path_seq_dir()); return 0; } From b901700159fc2991c9b733f033198380c497c296 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 12/92] sequencer: lib'ify save_head() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The only caller of save_head(), sequencer_pick_revisions() can already return errors, so its caller must be already prepared to handle error returns, and with this step, we make it notice an error return from this function. So this is a safe conversion to make save_head() callable from new callers that want it not to die, without changing the external behaviour of anything existing. Signed-off-by: Johannes Schindelin --- sequencer.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/sequencer.c b/sequencer.c index eb9c473328..7a1561e6a0 100644 --- a/sequencer.c +++ b/sequencer.c @@ -852,18 +852,28 @@ static int create_seq_dir(void) return 0; } -static void save_head(const char *head) +static int save_head(const char *head) { static struct lock_file head_lock; struct strbuf buf = STRBUF_INIT; int fd; - fd = hold_lock_file_for_update(&head_lock, git_path_head_file(), LOCK_DIE_ON_ERROR); + fd = hold_lock_file_for_update(&head_lock, git_path_head_file(), 0); + if (fd < 0) { + rollback_lock_file(&head_lock); + return error_errno(_("Could not lock HEAD")); + } strbuf_addf(&buf, "%s\n", head); - if (write_in_full(fd, buf.buf, buf.len) < 0) - die_errno(_("Could not write to %s"), git_path_head_file()); - if (commit_lock_file(&head_lock) < 0) - die(_("Error wrapping up %s."), git_path_head_file()); + if (write_in_full(fd, buf.buf, buf.len) < 0) { + rollback_lock_file(&head_lock); + return error_errno(_("Could not write to %s"), + git_path_head_file()); + } + if (commit_lock_file(&head_lock) < 0) { + rollback_lock_file(&head_lock); + return error(_("Error wrapping up %s."), git_path_head_file()); + } + return 0; } static int reset_for_rollback(const unsigned char *sha1) @@ -1127,7 +1137,8 @@ int sequencer_pick_revisions(struct replay_opts *opts) return -1; if (get_sha1("HEAD", sha1) && (opts->action == REPLAY_REVERT)) return error(_("Can't revert as initial commit")); - save_head(sha1_to_hex(sha1)); + if (save_head(sha1_to_hex(sha1))) + return -1; save_opts(opts); return pick_commits(todo_list, opts); } From e13f37f7b77883e8a4b8ff2a7ac9dbdb41723f79 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 13/92] sequencer: lib'ify save_todo() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The only caller of save_todo(), pick_commits() can already return errors, so its caller must be already prepared to handle error returns, and with this step, we make it notice an error return from this function. So this is a safe conversion to make save_todo() callable from new callers that want it not to die, without changing the external behaviour of anything existing. Signed-off-by: Johannes Schindelin --- sequencer.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/sequencer.c b/sequencer.c index 7a1561e6a0..32c53bb2b7 100644 --- a/sequencer.c +++ b/sequencer.c @@ -943,24 +943,31 @@ fail: return -1; } -static void save_todo(struct commit_list *todo_list, struct replay_opts *opts) +static int save_todo(struct commit_list *todo_list, struct replay_opts *opts) { static struct lock_file todo_lock; struct strbuf buf = STRBUF_INIT; int fd; - fd = hold_lock_file_for_update(&todo_lock, git_path_todo_file(), LOCK_DIE_ON_ERROR); - if (format_todo(&buf, todo_list, opts) < 0) - die(_("Could not format %s."), git_path_todo_file()); + fd = hold_lock_file_for_update(&todo_lock, git_path_todo_file(), 0); + if (fd < 0) + return error_errno(_("Could not lock '%s'"), + git_path_todo_file()); + if (format_todo(&buf, todo_list, opts) < 0) { + strbuf_release(&buf); + return error(_("Could not format %s."), git_path_todo_file()); + } if (write_in_full(fd, buf.buf, buf.len) < 0) { strbuf_release(&buf); - die_errno(_("Could not write to %s"), git_path_todo_file()); + return error_errno(_("Could not write to %s"), + git_path_todo_file()); } if (commit_lock_file(&todo_lock) < 0) { strbuf_release(&buf); - die(_("Error wrapping up %s."), git_path_todo_file()); + return error(_("Error wrapping up %s."), git_path_todo_file()); } strbuf_release(&buf); + return 0; } static void save_opts(struct replay_opts *opts) @@ -1009,7 +1016,8 @@ static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts) return -1; for (cur = todo_list; cur; cur = cur->next) { - save_todo(cur, opts); + if (save_todo(cur, opts)) + return -1; res = do_pick_commit(cur->item, opts); if (res) return res; From 805dd861a48d2162b8aaace74529dc491f93ac8d Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 14/92] sequencer: lib'ify save_opts() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The only caller of save_opts(), sequencer_pick_revisions() can already return errors, so its caller must be already prepared to handle error returns, and with this step, we make it notice an error return from this function. So this is a safe conversion to make save_opts() callable from new callers that want it not to die, without changing the external behaviour of anything existing. Signed-off-by: Johannes Schindelin --- sequencer.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/sequencer.c b/sequencer.c index 32c53bb2b7..021ddf36d0 100644 --- a/sequencer.c +++ b/sequencer.c @@ -970,37 +970,39 @@ static int save_todo(struct commit_list *todo_list, struct replay_opts *opts) return 0; } -static void save_opts(struct replay_opts *opts) +static int save_opts(struct replay_opts *opts) { const char *opts_file = git_path_opts_file(); + int res = 0; if (opts->no_commit) - git_config_set_in_file(opts_file, "options.no-commit", "true"); + res |= git_config_set_in_file_gently(opts_file, "options.no-commit", "true"); if (opts->edit) - git_config_set_in_file(opts_file, "options.edit", "true"); + res |= git_config_set_in_file_gently(opts_file, "options.edit", "true"); if (opts->signoff) - git_config_set_in_file(opts_file, "options.signoff", "true"); + res |= git_config_set_in_file_gently(opts_file, "options.signoff", "true"); if (opts->record_origin) - git_config_set_in_file(opts_file, "options.record-origin", "true"); + res |= git_config_set_in_file_gently(opts_file, "options.record-origin", "true"); if (opts->allow_ff) - git_config_set_in_file(opts_file, "options.allow-ff", "true"); + res |= git_config_set_in_file_gently(opts_file, "options.allow-ff", "true"); if (opts->mainline) { struct strbuf buf = STRBUF_INIT; strbuf_addf(&buf, "%d", opts->mainline); - git_config_set_in_file(opts_file, "options.mainline", buf.buf); + res |= git_config_set_in_file_gently(opts_file, "options.mainline", buf.buf); strbuf_release(&buf); } if (opts->strategy) - git_config_set_in_file(opts_file, "options.strategy", opts->strategy); + res |= git_config_set_in_file_gently(opts_file, "options.strategy", opts->strategy); if (opts->gpg_sign) - git_config_set_in_file(opts_file, "options.gpg-sign", opts->gpg_sign); + res |= git_config_set_in_file_gently(opts_file, "options.gpg-sign", opts->gpg_sign); if (opts->xopts) { int i; for (i = 0; i < opts->xopts_nr; i++) - git_config_set_multivar_in_file(opts_file, + res |= git_config_set_multivar_in_file_gently(opts_file, "options.strategy-option", opts->xopts[i], "^$", 0); } + return res; } static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts) @@ -1147,7 +1149,8 @@ int sequencer_pick_revisions(struct replay_opts *opts) return error(_("Can't revert as initial commit")); if (save_head(sha1_to_hex(sha1))) return -1; - save_opts(opts); + if (save_opts(opts)) + return -1; return pick_commits(todo_list, opts); } From 444cf0456b20128c1ff19f5ca490fd4dfc53c842 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 15/92] sequencer: lib'ify fast_forward_to() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The only caller of fast_forward_to(), do_pick_commit() already checks the return value and passes it on to its callers, so its caller must be already prepared to handle error returns, and with this step, we make it notice an error return from this function. So this is a safe conversion to make fast_forward_to() callable from new callers that want it not to die, without changing the external behaviour of anything existing. Signed-off-by: Johannes Schindelin --- sequencer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sequencer.c b/sequencer.c index 021ddf36d0..d92a632f29 100644 --- a/sequencer.c +++ b/sequencer.c @@ -226,7 +226,7 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from, read_cache(); if (checkout_fast_forward(from, to, 1)) - exit(128); /* the callee should have complained already */ + return -1; /* the callee should have complained already */ strbuf_addf(&sb, _("%s: fast-forward"), action_name(opts)); From 90b6e7c1119a9e21ecd7c3270e5e8b4f793ccd4e Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 15:32:18 +0100 Subject: [PATCH 16/92] sequencer: lib'ify checkout_fast_forward() Instead of dying there, let the caller high up in the callchain notice the error and handle it (by dying, still). The only callers of checkout_fast_forward(), cmd_merge(), pull_into_void(), cmd_pull() and sequencer's fast_forward_to(), already check the return value and handle it appropriately. With this step, we make it notice an error return from this function. So this is a safe conversion to make checkout_fast_forward() callable from new callers that want it not to die, without changing the external behaviour of anything existing. Signed-off-by: Johannes Schindelin --- merge.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/merge.c b/merge.c index 5db7d56b90..23866c9165 100644 --- a/merge.c +++ b/merge.c @@ -57,7 +57,8 @@ int checkout_fast_forward(const unsigned char *head, refresh_cache(REFRESH_QUIET); - hold_locked_index(lock_file, 1); + if (hold_locked_index(lock_file, 0) < 0) + return -1; memset(&trees, 0, sizeof(trees)); memset(&opts, 0, sizeof(opts)); @@ -90,7 +91,9 @@ int checkout_fast_forward(const unsigned char *head, } if (unpack_trees(nr_trees, t, &opts)) return -1; - if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) - die(_("unable to write new index file")); + if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) { + rollback_lock_file(lock_file); + return error(_("unable to write new index file")); + } return 0; } From 7f78793a4543de19431a343d2eff4ad3cc3746e4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 30 Aug 2016 11:04:21 +0200 Subject: [PATCH 17/92] sequencer: ensure to release the lock when we could not read the index A future caller of read_and_refresh_cache() may want to do more than just print some helpful advice in case of failure. Suggested by Junio Hamano. Signed-off-by: Johannes Schindelin --- sequencer.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sequencer.c b/sequencer.c index d92a632f29..eec8a60d6b 100644 --- a/sequencer.c +++ b/sequencer.c @@ -644,14 +644,18 @@ static int 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) + if (read_index_preload(&the_index, NULL) < 0) { + rollback_lock_file(&index_lock); return error(_("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 && index_fd >= 0) { - if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) + if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) { + rollback_lock_file(&index_lock); return error(_("git %s: failed to refresh the index"), action_name(opts)); + } } rollback_lock_file(&index_lock); return 0; From 34c74297c5955a15ad35be6bfe171b8bee9c2f5d Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 8 Feb 2016 17:16:19 +0100 Subject: [PATCH 18/92] sequencer: use static initializers for replay_opts This change is not completely faithful: instead of initializing all fields to 0, we choose to initialize command and subcommand to -1 (instead of defaulting to REPLAY_REVERT and REPLAY_NONE, respectively). Practically, it makes no difference at all, but future-proofs the code to require explicit assignments for both fields. Signed-off-by: Johannes Schindelin --- builtin/revert.c | 6 ++---- sequencer.h | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/builtin/revert.c b/builtin/revert.c index 4e693808b1..7365559c97 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -178,10 +178,9 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) int cmd_revert(int argc, const char **argv, const char *prefix) { - struct replay_opts opts; + struct replay_opts opts = REPLAY_OPTS_INIT; int res; - memset(&opts, 0, sizeof(opts)); if (isatty(0)) opts.edit = 1; opts.action = REPLAY_REVERT; @@ -195,10 +194,9 @@ int cmd_revert(int argc, const char **argv, const char *prefix) int cmd_cherry_pick(int argc, const char **argv, const char *prefix) { - struct replay_opts opts; + struct replay_opts opts = REPLAY_OPTS_INIT; int res; - memset(&opts, 0, sizeof(opts)); opts.action = REPLAY_PICK; git_config(git_default_config, NULL); parse_args(argc, argv, &opts); diff --git a/sequencer.h b/sequencer.h index 5ed5cb1d97..db425ad1a6 100644 --- a/sequencer.h +++ b/sequencer.h @@ -47,6 +47,7 @@ struct replay_opts { /* Only used by REPLAY_NONE */ struct rev_info *revs; }; +#define REPLAY_OPTS_INIT { -1, -1 } int sequencer_pick_revisions(struct replay_opts *opts); From af300ae8defca2de571a94df4f4ac5f3b23e409a Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 9 Feb 2016 14:09:05 +0100 Subject: [PATCH 19/92] sequencer: use memoized sequencer directory path Signed-off-by: Johannes Schindelin --- builtin/commit.c | 2 +- sequencer.c | 11 ++++++----- sequencer.h | 5 +---- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/builtin/commit.c b/builtin/commit.c index 7a1ade0d27..0fdf520903 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -173,7 +173,7 @@ static void determine_whence(struct wt_status *s) whence = FROM_MERGE; else if (file_exists(git_path_cherry_pick_head())) { whence = FROM_CHERRY_PICK; - if (file_exists(git_path(SEQ_DIR))) + if (file_exists(git_path_seq_dir())) sequencer_in_use = 1; } else diff --git a/sequencer.c b/sequencer.c index eec8a60d6b..cb16cbda35 100644 --- a/sequencer.c +++ b/sequencer.c @@ -21,10 +21,11 @@ const char sign_off_header[] = "Signed-off-by: "; static const char cherry_picked_prefix[] = "(cherry picked from commit "; -static GIT_PATH_FUNC(git_path_todo_file, SEQ_TODO_FILE) -static GIT_PATH_FUNC(git_path_opts_file, SEQ_OPTS_FILE) -static GIT_PATH_FUNC(git_path_seq_dir, SEQ_DIR) -static GIT_PATH_FUNC(git_path_head_file, SEQ_HEAD_FILE) +GIT_PATH_FUNC(git_path_seq_dir, "sequencer") + +static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo") +static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts") +static GIT_PATH_FUNC(git_path_head_file, "sequencer/head") static int is_rfc2822_line(const char *buf, int len) { @@ -112,7 +113,7 @@ static void remove_sequencer_state(void) { struct strbuf seq_dir = STRBUF_INIT; - strbuf_addstr(&seq_dir, git_path(SEQ_DIR)); + strbuf_addstr(&seq_dir, git_path_seq_dir()); remove_dir_recursively(&seq_dir, 0); strbuf_release(&seq_dir); } diff --git a/sequencer.h b/sequencer.h index db425ad1a6..dd4d33a572 100644 --- a/sequencer.h +++ b/sequencer.h @@ -1,10 +1,7 @@ #ifndef SEQUENCER_H #define SEQUENCER_H -#define SEQ_DIR "sequencer" -#define SEQ_HEAD_FILE "sequencer/head" -#define SEQ_TODO_FILE "sequencer/todo" -#define SEQ_OPTS_FILE "sequencer/opts" +const char *git_path_seq_dir(void); #define APPEND_SIGNOFF_DEDUP (1u << 0) From ff26a84edf27775e7dad4dcdc1a8610ed2c4fc12 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 9 Feb 2016 14:23:55 +0100 Subject: [PATCH 20/92] sequencer: avoid unnecessary indirection We really do not need the *pointer to a* pointer to the options in the read_populate_opts() function. Signed-off-by: Johannes Schindelin --- sequencer.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sequencer.c b/sequencer.c index cb16cbda35..c2fbf6f89c 100644 --- a/sequencer.c +++ b/sequencer.c @@ -813,7 +813,7 @@ static int populate_opts_cb(const char *key, const char *value, void *data) return 0; } -static int read_populate_opts(struct replay_opts **opts) +static int read_populate_opts(struct replay_opts *opts) { if (!file_exists(git_path_opts_file())) return 0; @@ -823,7 +823,7 @@ static int read_populate_opts(struct replay_opts **opts) * about this case, though, because we wrote that file ourselves, so we * are pretty certain that it is syntactically correct. */ - if (git_config_from_file(populate_opts_cb, git_path_opts_file(), *opts) < 0) + if (git_config_from_file(populate_opts_cb, git_path_opts_file(), opts) < 0) return error(_("Malformed options sheet: %s"), git_path_opts_file()); return 0; @@ -1054,7 +1054,7 @@ static int sequencer_continue(struct replay_opts *opts) if (!file_exists(git_path_todo_file())) return continue_single_pick(); - if (read_populate_opts(&opts) || + if (read_populate_opts(opts) || read_populate_todo(&todo_list, opts)) return -1; From 5ae174ce9add68aca30d0eb39d79f83eebfe8a7c Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 9 Feb 2016 16:06:35 +0100 Subject: [PATCH 21/92] sequencer: future-proof remove_sequencer_state() In a couple of commits, we will teach the sequencer to handle the nitty gritty of the interactive rebase, which keeps its state in a different directory. Signed-off-by: Johannes Schindelin --- sequencer.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/sequencer.c b/sequencer.c index c2fbf6f89c..8d272fbdca 100644 --- a/sequencer.c +++ b/sequencer.c @@ -27,6 +27,11 @@ static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo") static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts") static GIT_PATH_FUNC(git_path_head_file, "sequencer/head") +static const char *get_dir(const struct replay_opts *opts) +{ + return git_path_seq_dir(); +} + static int is_rfc2822_line(const char *buf, int len) { int i; @@ -109,13 +114,13 @@ static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob, return 1; } -static void remove_sequencer_state(void) +static void remove_sequencer_state(const struct replay_opts *opts) { - struct strbuf seq_dir = STRBUF_INIT; + struct strbuf dir = STRBUF_INIT; - strbuf_addstr(&seq_dir, git_path_seq_dir()); - remove_dir_recursively(&seq_dir, 0); - strbuf_release(&seq_dir); + strbuf_addf(&dir, "%s", get_dir(opts)); + remove_dir_recursively(&dir, 0); + strbuf_release(&dir); } static const char *action_name(const struct replay_opts *opts) @@ -940,7 +945,7 @@ static int sequencer_rollback(struct replay_opts *opts) } if (reset_for_rollback(sha1)) goto fail; - remove_sequencer_state(); + remove_sequencer_state(opts); strbuf_release(&buf); return 0; fail: @@ -1034,7 +1039,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 */ - remove_sequencer_state(); + remove_sequencer_state(opts); return 0; } @@ -1095,7 +1100,7 @@ int sequencer_pick_revisions(struct replay_opts *opts) * one that is being continued */ if (opts->subcommand == REPLAY_REMOVE_STATE) { - remove_sequencer_state(); + remove_sequencer_state(opts); return 0; } if (opts->subcommand == REPLAY_ROLLBACK) From 16690eeb3ac64747339a3f545ad942b6e1068602 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 9 Jun 2016 09:43:42 +0200 Subject: [PATCH 22/92] sequencer: allow the sequencer to take custody of malloc()ed data The sequencer is our attempt to lib-ify cherry-pick. Yet it behaves like a one-shot command when it reads its configuration: memory is allocated and released only when the command exits. This is kind of okay for git-cherry-pick, which *is* a one-shot command. All the work to make the sequencer its work horse was done to allow using the functionality as a library function, though, including proper clean-up after use. This patch introduces an API to pass the responsibility of releasing certain memory to the sequencer. Example: const char *label = sequencer_entrust(opts, xstrfmt("From: %s", email)); The entrusted memory will remain valid until sequencer_remove_state() is called, or the program exits, whichever comes first. Signed-off-by: Johannes Schindelin --- sequencer.c | 13 +++++++++++++ sequencer.h | 10 ++++++++++ 2 files changed, 23 insertions(+) diff --git a/sequencer.c b/sequencer.c index 8d272fbdca..8d56a05277 100644 --- a/sequencer.c +++ b/sequencer.c @@ -114,9 +114,22 @@ static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob, return 1; } +void *sequencer_entrust(struct replay_opts *opts, void *to_free) +{ + ALLOC_GROW(opts->owned, opts->owned_nr + 1, opts->owned_alloc); + opts->owned[opts->owned_nr++] = to_free; + + return to_free; +} + static void remove_sequencer_state(const struct replay_opts *opts) { struct strbuf dir = STRBUF_INIT; + int i; + + for (i = 0; i < opts->owned_nr; i++) + free(opts->owned[i]); + free(opts->owned); strbuf_addf(&dir, "%s", get_dir(opts)); remove_dir_recursively(&dir, 0); diff --git a/sequencer.h b/sequencer.h index dd4d33a572..04892a9c20 100644 --- a/sequencer.h +++ b/sequencer.h @@ -43,9 +43,19 @@ struct replay_opts { /* Only used by REPLAY_NONE */ struct rev_info *revs; + + /* malloc()ed data entrusted to the sequencer */ + void **owned; + int owned_nr, owned_alloc; }; #define REPLAY_OPTS_INIT { -1, -1 } +/* + * Make it the duty of sequencer_remove_state() to release the memory; + * For ease of use, return the same pointer. + */ +void *sequencer_entrust(struct replay_opts *opts, void *to_free); + int sequencer_pick_revisions(struct replay_opts *opts); extern const char sign_off_header[]; From eae16a322fa5c5ea71771ce1c711c94070ba5ea7 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 9 Jun 2016 09:59:52 +0200 Subject: [PATCH 23/92] sequencer: release memory that was allocated when reading options The sequencer reads options from disk and stores them in its struct for use during sequencer's operations. With this patch, the memory is released afterwards, plugging a memory leak. Signed-off-by: Johannes Schindelin --- sequencer.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/sequencer.c b/sequencer.c index 8d56a05277..3ca231f8f3 100644 --- a/sequencer.c +++ b/sequencer.c @@ -131,6 +131,8 @@ static void remove_sequencer_state(const struct replay_opts *opts) free(opts->owned[i]); free(opts->owned); + free(opts->xopts); + strbuf_addf(&dir, "%s", get_dir(opts)); remove_dir_recursively(&dir, 0); strbuf_release(&dir); @@ -815,13 +817,18 @@ static int populate_opts_cb(const char *key, const char *value, void *data) opts->allow_ff = git_config_bool_or_int(key, value, &error_flag); else if (!strcmp(key, "options.mainline")) opts->mainline = git_config_int(key, value); - else if (!strcmp(key, "options.strategy")) + else if (!strcmp(key, "options.strategy")) { git_config_string(&opts->strategy, key, value); - else if (!strcmp(key, "options.gpg-sign")) + sequencer_entrust(opts, (char *) opts->strategy); + } + else if (!strcmp(key, "options.gpg-sign")) { git_config_string(&opts->gpg_sign, key, value); + sequencer_entrust(opts, (char *) opts->gpg_sign); + } else if (!strcmp(key, "options.strategy-option")) { ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc); - opts->xopts[opts->xopts_nr++] = xstrdup(value); + opts->xopts[opts->xopts_nr++] = + sequencer_entrust(opts, xstrdup(value)); } else return error(_("Invalid key: %s"), key); From f15270ca2704598faf99f05e4790da3ae1401f69 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 13 Feb 2016 14:31:00 +0100 Subject: [PATCH 24/92] sequencer: future-proof read_populate_todo() Over the next commits, we will work on improving the sequencer to the point where it can process the edit script of an interactive rebase. To that end, we will need to teach the sequencer to read interactive rebase's todo file. In preparation, we consolidate all places where that todo file is needed to call a function that we will later extend. Signed-off-by: Johannes Schindelin --- sequencer.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/sequencer.c b/sequencer.c index 3ca231f8f3..da6de229e7 100644 --- a/sequencer.c +++ b/sequencer.c @@ -32,6 +32,11 @@ static const char *get_dir(const struct replay_opts *opts) return git_path_seq_dir(); } +static const char *get_todo_path(const struct replay_opts *opts) +{ + return git_path_todo_file(); +} + static int is_rfc2822_line(const char *buf, int len) { int i; @@ -776,25 +781,24 @@ static int parse_insn_buffer(char *buf, struct commit_list **todo_list, static int read_populate_todo(struct commit_list **todo_list, struct replay_opts *opts) { + const char *todo_file = get_todo_path(opts); struct strbuf buf = STRBUF_INIT; int fd, res; - fd = open(git_path_todo_file(), O_RDONLY); + fd = open(todo_file, O_RDONLY); if (fd < 0) - return error_errno(_("Could not open %s"), - git_path_todo_file()); + return error_errno(_("Could not open %s"), todo_file); if (strbuf_read(&buf, fd, 0) < 0) { close(fd); strbuf_release(&buf); - return error(_("Could not read %s."), git_path_todo_file()); + return error(_("Could not read %s."), todo_file); } close(fd); res = parse_insn_buffer(buf.buf, todo_list, opts); strbuf_release(&buf); if (res) - return error(_("Unusable instruction sheet: %s"), - git_path_todo_file()); + return error(_("Unusable instruction sheet: %s"), todo_file); return 0; } @@ -1077,7 +1081,7 @@ static int sequencer_continue(struct replay_opts *opts) { struct commit_list *todo_list = NULL; - if (!file_exists(git_path_todo_file())) + if (!file_exists(get_todo_path(opts))) return continue_single_pick(); if (read_populate_opts(opts) || read_populate_todo(&todo_list, opts)) From 1511309e74cc476456d64e549c6c4d9929b4911c Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 23 Feb 2016 19:21:31 +0100 Subject: [PATCH 25/92] sequencer: completely revamp the "todo" script parsing When we came up with the "sequencer" idea, we really wanted to have kind of a plumbing equivalent of the interactive rebase. Hence the choice of words: the "todo" script, a "pick", etc. However, when it came time to implement the entire shebang, somehow this idea got lost and the sequencer was used as working horse for cherry-pick and revert instead. So as not to interfere with the interactive rebase, it even uses a separate directory to store its state. Furthermore, it also is stupidly strict about the "todo" script it accepts: while it parses commands in a way that was *designed* to be similar to the interactive rebase, it then goes on to *error out* if the commands disagree with the overall action (cherry-pick or revert). Finally, the sequencer code chose to deviate from the interactive rebase code insofar that it *reformats* the "todo" script instead of just writing the part of the parsed script that were not yet processed. This is not only unnecessary churn, but might well lose information that is valuable to the user (i.e. comments after the commands). Let's just bite the bullet and rewrite the entire parser; the code now becomes not only more elegant: it allows us to go on and teach the sequencer how to parse *true* "todo" scripts as used by the interactive rebase itself. In a way, the sequencer is about to grow up to do its older brother's job. Better. In particular, we choose to maintain the list of commands in an array instead of a linked list: this is flexible enough to allow us later on to even implement rebase -i's reordering of fixup!/squash! commits very easily (and with a very nice speed bonus, at least on Windows). While at it, do not stop at the first problem, but list *all* of the problems. This will help the user when the sequencer will do `rebase -i`'s work by allowing to address all issues in one go rather than going back and forth until the todo list is valid. Signed-off-by: Johannes Schindelin --- sequencer.c | 275 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 159 insertions(+), 116 deletions(-) diff --git a/sequencer.c b/sequencer.c index da6de229e7..b971ad04fe 100644 --- a/sequencer.c +++ b/sequencer.c @@ -473,7 +473,26 @@ static int allow_empty(struct replay_opts *opts, struct commit *commit) return 1; } -static int do_pick_commit(struct commit *commit, struct replay_opts *opts) +enum todo_command { + TODO_PICK = 0, + TODO_REVERT +}; + +static const char *todo_command_strings[] = { + "pick", + "revert" +}; + +static const char *command_to_string(const enum todo_command command) +{ + if (command < ARRAY_SIZE(todo_command_strings)) + return todo_command_strings[command]; + die("Unknown command: %d", command); +} + + +static int do_pick_commit(enum todo_command command, struct commit *commit, + struct replay_opts *opts) { unsigned char head[20]; struct commit *base, *next, *parent; @@ -535,7 +554,8 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) /* TRANSLATORS: The first %s will be "revert" or "cherry-pick", the second %s a SHA1 */ return error(_("%s: cannot parse parent commit %s"), - action_name(opts), oid_to_hex(&parent->object.oid)); + command_to_string(command), + oid_to_hex(&parent->object.oid)); if (get_message(commit, &msg) != 0) return error(_("Cannot get commit message for %s"), @@ -548,7 +568,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) * reverse of it if we are revert. */ - if (opts->action == REPLAY_REVERT) { + if (command == TODO_REVERT) { base = commit; base_label = msg.label; next = parent; @@ -589,7 +609,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) } } - if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REPLAY_REVERT) { + if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) { res = do_recursive_merge(base, next, base_label, next_label, head, &msgbuf, opts); if (res < 0) @@ -615,17 +635,17 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) * However, if the merge did not even start, then we don't want to * write it at all. */ - if (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1) && + if (command == TODO_PICK && !opts->no_commit && (res == 0 || res == 1) && update_ref(NULL, "CHERRY_PICK_HEAD", commit->object.oid.hash, NULL, REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) res = -1; - if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1) && + if (command == TODO_REVERT && ((opts->no_commit && res == 0) || res == 1) && update_ref(NULL, "REVERT_HEAD", commit->object.oid.hash, NULL, REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) res = -1; if (res) { - error(opts->action == REPLAY_REVERT + error(command == TODO_REVERT ? _("could not revert %s... %s") : _("could not apply %s... %s"), find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV), @@ -687,116 +707,121 @@ static int read_and_refresh_cache(struct replay_opts *opts) return 0; } -static int format_todo(struct strbuf *buf, struct commit_list *todo_list, - struct replay_opts *opts) -{ - struct commit_list *cur = NULL; - const char *sha1_abbrev = NULL; - const char *action_str = opts->action == REPLAY_REVERT ? "revert" : "pick"; - const char *subject; - int subject_len; +struct todo_item { + enum todo_command command; + struct commit *commit; + size_t offset_in_buf; +}; - for (cur = todo_list; cur; cur = cur->next) { - const char *commit_buffer = get_commit_buffer(cur->item, NULL); - sha1_abbrev = find_unique_abbrev(cur->item->object.oid.hash, DEFAULT_ABBREV); - subject_len = find_commit_subject(commit_buffer, &subject); - strbuf_addf(buf, "%s %s %.*s\n", action_str, sha1_abbrev, - subject_len, subject); - unuse_commit_buffer(cur->item, commit_buffer); - } - return 0; +struct todo_list { + struct strbuf buf; + struct todo_item *items; + int nr, alloc, current; +}; + +#define TODO_LIST_INIT { STRBUF_INIT } + +static void todo_list_release(struct todo_list *todo_list) +{ + strbuf_release(&todo_list->buf); + free(todo_list->items); + todo_list->items = NULL; + todo_list->nr = todo_list->alloc = 0; } -static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts *opts) +struct todo_item *append_new_todo(struct todo_list *todo_list) +{ + ALLOC_GROW(todo_list->items, todo_list->nr + 1, todo_list->alloc); + return todo_list->items + todo_list->nr++; +} + +static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) { unsigned char commit_sha1[20]; - enum replay_action action; char *end_of_object_name; - int saved, status, padding; + int i, saved, status, padding; - if (starts_with(bol, "pick")) { - action = REPLAY_PICK; - bol += strlen("pick"); - } else if (starts_with(bol, "revert")) { - action = REPLAY_REVERT; - bol += strlen("revert"); - } else - return NULL; + for (i = 0; i < ARRAY_SIZE(todo_command_strings); i++) + if (skip_prefix(bol, todo_command_strings[i], &bol)) { + item->command = i; + break; + } + if (i >= ARRAY_SIZE(todo_command_strings)) + return -1; /* Eat up extra spaces/ tabs before object name */ padding = strspn(bol, " \t"); if (!padding) - return NULL; + return -1; bol += padding; - end_of_object_name = bol + strcspn(bol, " \t\n"); + end_of_object_name = (char *) bol + strcspn(bol, " \t\n"); saved = *end_of_object_name; *end_of_object_name = '\0'; status = get_sha1(bol, commit_sha1); *end_of_object_name = saved; - /* - * Verify that the action matches up with the one in - * opts; we don't support arbitrary instructions - */ - if (action != opts->action) { - if (action == REPLAY_REVERT) - error((opts->action == REPLAY_REVERT) - ? _("Cannot revert during another revert.") - : _("Cannot revert during a cherry-pick.")); - else - error((opts->action == REPLAY_REVERT) - ? _("Cannot cherry-pick during a revert.") - : _("Cannot cherry-pick during another cherry-pick.")); - return NULL; - } - if (status < 0) - return NULL; + return -1; - return lookup_commit_reference(commit_sha1); + item->commit = lookup_commit_reference(commit_sha1); + return !item->commit; } -static int parse_insn_buffer(char *buf, struct commit_list **todo_list, - struct replay_opts *opts) +static int parse_insn_buffer(char *buf, struct todo_list *todo_list) { - struct commit_list **next = todo_list; - struct commit *commit; + struct todo_item *item; char *p = buf; - int i; + int i, res = 0; for (i = 1; *p; i++) { char *eol = strchrnul(p, '\n'); - commit = parse_insn_line(p, eol, opts); - if (!commit) - return error(_("Could not parse line %d."), i); - next = commit_list_append(commit, next); + + item = append_new_todo(todo_list); + item->offset_in_buf = p - todo_list->buf.buf; + if (parse_insn_line(item, p, eol)) { + res |= error(_("Invalid line %d: %.*s"), + i, (int)(eol - p), p); + item->command = -1; + } p = *eol ? eol + 1 : eol; } - if (!*todo_list) + if (!todo_list->nr) return error(_("No commits parsed.")); - return 0; + return res; } -static int read_populate_todo(struct commit_list **todo_list, +static int read_populate_todo(struct todo_list *todo_list, struct replay_opts *opts) { const char *todo_file = get_todo_path(opts); - struct strbuf buf = STRBUF_INIT; int fd, res; + strbuf_reset(&todo_list->buf); fd = open(todo_file, O_RDONLY); if (fd < 0) return error_errno(_("Could not open %s"), todo_file); - if (strbuf_read(&buf, fd, 0) < 0) { + if (strbuf_read(&todo_list->buf, fd, 0) < 0) { close(fd); - strbuf_release(&buf); return error(_("Could not read %s."), todo_file); } close(fd); - res = parse_insn_buffer(buf.buf, todo_list, opts); - strbuf_release(&buf); + res = parse_insn_buffer(todo_list->buf.buf, todo_list); + if (!res) { + enum todo_command valid = + opts->action == REPLAY_PICK ? TODO_PICK : TODO_REVERT; + int i; + + for (i = 0; i < todo_list->nr; i++) + if (valid == todo_list->items[i].command) + continue; + else if (valid == TODO_PICK) + return error(_("Cannot cherry-pick during a revert.")); + else + return error(_("Cannot revert during a cherry-pick.")); + } + if (res) return error(_("Unusable instruction sheet: %s"), todo_file); return 0; @@ -858,18 +883,33 @@ static int read_populate_opts(struct replay_opts *opts) return 0; } -static int walk_revs_populate_todo(struct commit_list **todo_list, +static int walk_revs_populate_todo(struct todo_list *todo_list, struct replay_opts *opts) { + enum todo_command command = opts->action == REPLAY_PICK ? + TODO_PICK : TODO_REVERT; + const char *command_string = todo_command_strings[command]; struct commit *commit; - struct commit_list **next; if (prepare_revs(opts)) return -1; - next = todo_list; - while ((commit = get_revision(opts->revs))) - next = commit_list_append(commit, next); + while ((commit = get_revision(opts->revs))) { + struct todo_item *item = append_new_todo(todo_list); + const char *commit_buffer = get_commit_buffer(commit, NULL); + const char *subject; + int subject_len; + + item->command = command; + item->commit = commit; + item->offset_in_buf = todo_list->buf.len; + subject_len = find_commit_subject(commit_buffer, &subject); + strbuf_addf(&todo_list->buf, "%s %s %.*s\n", command_string, + find_unique_abbrev(commit->object.oid.hash, + DEFAULT_ABBREV), + subject_len, subject); + unuse_commit_buffer(commit, commit_buffer); + } return 0; } @@ -977,30 +1017,22 @@ fail: return -1; } -static int save_todo(struct commit_list *todo_list, struct replay_opts *opts) +static int save_todo(struct todo_list *todo_list, struct replay_opts *opts) { static struct lock_file todo_lock; - struct strbuf buf = STRBUF_INIT; - int fd; + const char *todo_path = get_todo_path(opts); + int next = todo_list->current, offset, fd; - fd = hold_lock_file_for_update(&todo_lock, git_path_todo_file(), 0); + fd = hold_lock_file_for_update(&todo_lock, todo_path, 0); if (fd < 0) - return error_errno(_("Could not lock '%s'"), - git_path_todo_file()); - if (format_todo(&buf, todo_list, opts) < 0) { - strbuf_release(&buf); - return error(_("Could not format %s."), git_path_todo_file()); - } - if (write_in_full(fd, buf.buf, buf.len) < 0) { - strbuf_release(&buf); - return error_errno(_("Could not write to %s"), - git_path_todo_file()); - } - if (commit_lock_file(&todo_lock) < 0) { - strbuf_release(&buf); - return error(_("Error wrapping up %s."), git_path_todo_file()); - } - strbuf_release(&buf); + return error_errno(_("Could not lock '%s'"), todo_path); + offset = next < todo_list->nr ? + todo_list->items[next].offset_in_buf : todo_list->buf.len; + if (write_in_full(fd, todo_list->buf.buf + offset, + todo_list->buf.len - offset) < 0) + return error_errno(_("Could not write to '%s'"), todo_path); + if (commit_lock_file(&todo_lock) < 0) + return error(_("Error wrapping up %s."), todo_path); return 0; } @@ -1039,9 +1071,8 @@ static int save_opts(struct replay_opts *opts) return res; } -static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts) +static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) { - struct commit_list *cur; int res; setenv(GIT_REFLOG_ACTION, action_name(opts), 0); @@ -1051,10 +1082,12 @@ static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts) if (read_and_refresh_cache(opts)) return -1; - for (cur = todo_list; cur; cur = cur->next) { - if (save_todo(cur, opts)) + while (todo_list->current < todo_list->nr) { + struct todo_item *item = todo_list->items + todo_list->current; + if (save_todo(todo_list, opts)) return -1; - res = do_pick_commit(cur->item, opts); + res = do_pick_commit(item->command, item->commit, opts); + todo_list->current++; if (res) return res; } @@ -1079,38 +1112,46 @@ static int continue_single_pick(void) static int sequencer_continue(struct replay_opts *opts) { - struct commit_list *todo_list = NULL; + struct todo_list todo_list = TODO_LIST_INIT; + int res; if (!file_exists(get_todo_path(opts))) return continue_single_pick(); - if (read_populate_opts(opts) || - read_populate_todo(&todo_list, opts)) + if (read_populate_opts(opts)) return -1; + if ((res = read_populate_todo(&todo_list, opts))) + goto release_todo_list; /* Verify that the conflict has been resolved */ if (file_exists(git_path_cherry_pick_head()) || file_exists(git_path_revert_head())) { - int ret = continue_single_pick(); - if (ret) - return ret; + res = continue_single_pick(); + if (res) + goto release_todo_list; } - if (index_differs_from("HEAD", 0)) - return error_dirty_index(opts); - todo_list = todo_list->next; - return pick_commits(todo_list, opts); + if (index_differs_from("HEAD", 0)) { + res = error_dirty_index(opts); + goto release_todo_list; + } + todo_list.current++; + res = pick_commits(&todo_list, opts); +release_todo_list: + todo_list_release(&todo_list); + return res; } static int single_pick(struct commit *cmit, struct replay_opts *opts) { setenv(GIT_REFLOG_ACTION, action_name(opts), 0); - return do_pick_commit(cmit, opts); + return do_pick_commit(opts->action == REPLAY_PICK ? + TODO_PICK : TODO_REVERT, cmit, opts); } int sequencer_pick_revisions(struct replay_opts *opts) { - struct commit_list *todo_list = NULL; + struct todo_list todo_list = TODO_LIST_INIT; unsigned char sha1[20]; - int i; + int i, res; if (opts->subcommand == REPLAY_NONE) assert(opts->revs); @@ -1185,7 +1226,9 @@ int sequencer_pick_revisions(struct replay_opts *opts) return -1; if (save_opts(opts)) return -1; - return pick_commits(todo_list, opts); + res = pick_commits(&todo_list, opts); + todo_list_release(&todo_list); + return res; } void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag) From a68819aba7ff82f79a17794fa1b1b4cf58f611c6 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 24 Feb 2016 14:57:22 +0100 Subject: [PATCH 26/92] sequencer: avoid completely different messages for different actions Signed-off-by: Johannes Schindelin --- sequencer.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/sequencer.c b/sequencer.c index b971ad04fe..7ba932bf0e 100644 --- a/sequencer.c +++ b/sequencer.c @@ -232,11 +232,8 @@ static int error_dirty_index(struct replay_opts *opts) if (read_cache_unmerged()) return error_resolve_conflict(action_name(opts)); - /* Different translation strings for cherry-pick and revert */ - if (opts->action == REPLAY_PICK) - error(_("Your local changes would be overwritten by cherry-pick.")); - else - error(_("Your local changes would be overwritten by revert.")); + error(_("Your local changes would be overwritten by %s."), + action_name(opts)); if (advice_commit_before_merge) advise(_("Commit your changes or stash them to proceed.")); From 7e5d54cf8f77dcc97f4a94a28acfe06b7919a6e3 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 17:39:02 +0200 Subject: [PATCH 27/92] sequencer: get rid of the subcommand field The subcommands are used exactly once, at the very beginning of sequencer_pick_revisions(), to determine what to do. This is an unnecessary level of indirection: we can simply call the correct function to begin with. So let's do that. While at it, ensure that the subcommands return an error code so that they do not have to die() all over the place (bad practice for library functions...). Signed-off-by: Johannes Schindelin --- builtin/revert.c | 36 ++++++++++++++++-------------------- sequencer.c | 35 +++++++++++------------------------ sequencer.h | 13 ++++--------- 3 files changed, 31 insertions(+), 53 deletions(-) diff --git a/builtin/revert.c b/builtin/revert.c index 7365559c97..c9ae4dc13a 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -71,7 +71,7 @@ static void verify_opt_compatible(const char *me, const char *base_opt, ...) die(_("%s: %s cannot be used with %s"), me, this_opt, base_opt); } -static void parse_args(int argc, const char **argv, struct replay_opts *opts) +static int run_sequencer(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); @@ -115,25 +115,15 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) if (opts->keep_redundant_commits) opts->allow_empty = 1; - /* Set the subcommand */ - if (cmd == 'q') - opts->subcommand = REPLAY_REMOVE_STATE; - else if (cmd == 'c') - opts->subcommand = REPLAY_CONTINUE; - else if (cmd == 'a') - opts->subcommand = REPLAY_ROLLBACK; - else - opts->subcommand = REPLAY_NONE; - /* Check for incompatible command line arguments */ - if (opts->subcommand != REPLAY_NONE) { + if (cmd) { char *this_operation; - if (opts->subcommand == REPLAY_REMOVE_STATE) + if (cmd == 'q') this_operation = "--quit"; - else if (opts->subcommand == REPLAY_CONTINUE) + else if (cmd == 'c') this_operation = "--continue"; else { - assert(opts->subcommand == REPLAY_ROLLBACK); + assert(cmd == 'a'); this_operation = "--abort"; } @@ -156,7 +146,7 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) "--edit", opts->edit, NULL); - if (opts->subcommand != REPLAY_NONE) { + if (cmd) { opts->revs = NULL; } else { struct setup_revision_opt s_r_opt; @@ -174,6 +164,14 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) if (argc > 1) usage_with_options(usage_str, options); + + if (cmd == 'q') + return sequencer_remove_state(opts); + if (cmd == 'c') + return sequencer_continue(opts); + if (cmd == 'a') + return sequencer_rollback(opts); + return sequencer_pick_revisions(opts); } int cmd_revert(int argc, const char **argv, const char *prefix) @@ -185,8 +183,7 @@ int cmd_revert(int argc, const char **argv, const char *prefix) opts.edit = 1; opts.action = REPLAY_REVERT; git_config(git_default_config, NULL); - parse_args(argc, argv, &opts); - res = sequencer_pick_revisions(&opts); + res = run_sequencer(argc, argv, &opts); if (res < 0) die(_("revert failed")); return res; @@ -199,8 +196,7 @@ int cmd_cherry_pick(int argc, const char **argv, const char *prefix) opts.action = REPLAY_PICK; git_config(git_default_config, NULL); - parse_args(argc, argv, &opts); - res = sequencer_pick_revisions(&opts); + res = run_sequencer(argc, argv, &opts); if (res < 0) die(_("cherry-pick failed")); return res; diff --git a/sequencer.c b/sequencer.c index 7ba932bf0e..053e78ce8f 100644 --- a/sequencer.c +++ b/sequencer.c @@ -127,7 +127,7 @@ void *sequencer_entrust(struct replay_opts *opts, void *to_free) return to_free; } -static void remove_sequencer_state(const struct replay_opts *opts) +int sequencer_remove_state(struct replay_opts *opts) { struct strbuf dir = STRBUF_INIT; int i; @@ -141,6 +141,8 @@ static void remove_sequencer_state(const struct replay_opts *opts) strbuf_addf(&dir, "%s", get_dir(opts)); remove_dir_recursively(&dir, 0); strbuf_release(&dir); + + return 0; } static const char *action_name(const struct replay_opts *opts) @@ -971,7 +973,7 @@ static int rollback_single_pick(void) return reset_for_rollback(head_sha1); } -static int sequencer_rollback(struct replay_opts *opts) +int sequencer_rollback(struct replay_opts *opts) { FILE *f; unsigned char sha1[20]; @@ -1006,9 +1008,8 @@ static int sequencer_rollback(struct replay_opts *opts) } if (reset_for_rollback(sha1)) goto fail; - remove_sequencer_state(opts); strbuf_release(&buf); - return 0; + return sequencer_remove_state(opts); fail: strbuf_release(&buf); return -1; @@ -1093,8 +1094,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) * Sequence of picks finished successfully; cleanup by * removing the .git/sequencer directory */ - remove_sequencer_state(opts); - return 0; + return sequencer_remove_state(opts); } static int continue_single_pick(void) @@ -1107,11 +1107,14 @@ static int continue_single_pick(void) return run_command_v_opt(argv, RUN_GIT_CMD); } -static int sequencer_continue(struct replay_opts *opts) +int sequencer_continue(struct replay_opts *opts) { struct todo_list todo_list = TODO_LIST_INIT; int res; + if (read_and_refresh_cache(opts)) + return -1; + if (!file_exists(get_todo_path(opts))) return continue_single_pick(); if (read_populate_opts(opts)) @@ -1150,26 +1153,10 @@ int sequencer_pick_revisions(struct replay_opts *opts) unsigned char sha1[20]; int i, res; - if (opts->subcommand == REPLAY_NONE) - assert(opts->revs); - + assert(opts->revs); if (read_and_refresh_cache(opts)) return -1; - /* - * Decide what to do depending on the arguments; a fresh - * cherry-pick should be handled differently from an existing - * one that is being continued - */ - if (opts->subcommand == REPLAY_REMOVE_STATE) { - remove_sequencer_state(opts); - return 0; - } - if (opts->subcommand == REPLAY_ROLLBACK) - return sequencer_rollback(opts); - if (opts->subcommand == REPLAY_CONTINUE) - return sequencer_continue(opts); - for (i = 0; i < opts->revs->pending.nr; i++) { unsigned char sha1[20]; const char *name = opts->revs->pending.objects[i].name; diff --git a/sequencer.h b/sequencer.h index 04892a9c20..0b3950d8fd 100644 --- a/sequencer.h +++ b/sequencer.h @@ -10,16 +10,8 @@ enum replay_action { REPLAY_PICK }; -enum replay_subcommand { - REPLAY_NONE, - REPLAY_REMOVE_STATE, - REPLAY_CONTINUE, - REPLAY_ROLLBACK -}; - struct replay_opts { enum replay_action action; - enum replay_subcommand subcommand; /* Boolean options */ int edit; @@ -48,7 +40,7 @@ struct replay_opts { void **owned; int owned_nr, owned_alloc; }; -#define REPLAY_OPTS_INIT { -1, -1 } +#define REPLAY_OPTS_INIT { -1 } /* * Make it the duty of sequencer_remove_state() to release the memory; @@ -57,6 +49,9 @@ struct replay_opts { void *sequencer_entrust(struct replay_opts *opts, void *to_free); int sequencer_pick_revisions(struct replay_opts *opts); +int sequencer_continue(struct replay_opts *opts); +int sequencer_rollback(struct replay_opts *opts); +int sequencer_remove_state(struct replay_opts *opts); extern const char sign_off_header[]; From 557f38161921493d67aa5da864065c66e8387749 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 4 Mar 2016 16:21:16 +0100 Subject: [PATCH 28/92] sequencer: refactor the code to obtain a short commit name Not only does this DRY up the code (providing a better documentation what the code is about, as well as allowing to change the behavior in a single place), it also makes it substantially shorter to use the same functionality in functions to be introduced when we teach the sequencer to process interactive-rebase's git-rebase-todo file. Signed-off-by: Johannes Schindelin --- sequencer.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/sequencer.c b/sequencer.c index 053e78ce8f..0c8dec4992 100644 --- a/sequencer.c +++ b/sequencer.c @@ -157,13 +157,18 @@ struct commit_message { const char *message; }; +static const char *short_commit_name(struct commit *commit) +{ + return find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV); +} + static int get_message(struct commit *commit, struct commit_message *out) { const char *abbrev, *subject; int subject_len; out->message = logmsg_reencode(commit, NULL, get_commit_output_encoding()); - abbrev = find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV); + abbrev = short_commit_name(commit); subject_len = find_commit_subject(out->message, &subject); @@ -647,8 +652,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, error(command == TODO_REVERT ? _("could not revert %s... %s") : _("could not apply %s... %s"), - find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV), - msg.subject); + short_commit_name(commit), msg.subject); print_advice(res == 1, opts); rerere(opts->allow_rerere_auto); goto leave; @@ -904,9 +908,7 @@ static int walk_revs_populate_todo(struct todo_list *todo_list, item->offset_in_buf = todo_list->buf.len; subject_len = find_commit_subject(commit_buffer, &subject); strbuf_addf(&todo_list->buf, "%s %s %.*s\n", command_string, - find_unique_abbrev(commit->object.oid.hash, - DEFAULT_ABBREV), - subject_len, subject); + short_commit_name(commit), subject_len, subject); unuse_commit_buffer(commit, commit_buffer); } return 0; From 83f6ebe4471e6815325b36e556dac693c4953d73 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 17:31:24 +0100 Subject: [PATCH 29/92] sequencer: remember the onelines when parsing the todo file The `git-rebase-todo` file contains a list of commands. Most of those commands have the form The is displayed primarily for the user's convenience, as rebase -i really interprets only the part. However, there are *some* places in interactive rebase where the is used to display messages, e.g. for reporting at which commit we stopped. So let's just remember it when parsing the todo file; we keep a copy of the entire todo file anyway (to write out the new `done` and `git-rebase-todo` file just before processing each command), so all we need to do is remember the begin offsets and lengths. As we will have to parse and remember the command-line for `exec` commands later, we do not call the field "oneline" but rather "arg" (and will reuse that for exec's command-line). Signed-off-by: Johannes Schindelin --- sequencer.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sequencer.c b/sequencer.c index 0c8dec4992..ca1961c0f0 100644 --- a/sequencer.c +++ b/sequencer.c @@ -713,6 +713,8 @@ static int read_and_refresh_cache(struct replay_opts *opts) struct todo_item { enum todo_command command; struct commit *commit; + const char *arg; + int arg_len; size_t offset_in_buf; }; @@ -764,6 +766,9 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) status = get_sha1(bol, commit_sha1); *end_of_object_name = saved; + item->arg = end_of_object_name + strspn(end_of_object_name, " \t"); + item->arg_len = (int)(eol - item->arg); + if (status < 0) return -1; @@ -905,6 +910,8 @@ static int walk_revs_populate_todo(struct todo_list *todo_list, item->command = command; item->commit = commit; + item->arg = NULL; + item->arg_len = 0; item->offset_in_buf = todo_list->buf.len; subject_len = find_commit_subject(commit_buffer, &subject); strbuf_addf(&todo_list->buf, "%s %s %.*s\n", command_string, From 5da62303958925a2097bc37f2afd682335dd50c4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 29 Feb 2016 17:17:47 +0100 Subject: [PATCH 30/92] sequencer: prepare for rebase -i's commit functionality In interactive rebases, we commit a little bit differently than the sequencer did so far: we heed the "author-script", the "message" and the "amend" files in the .git/rebase-merge/ subdirectory. Likewise, we may want to edit the commit message *even* when providing a file containing the suggested commit message. Therefore we change the code to not even provide a default message when we do not want any, and to call the editor explicitly. As interactive rebase's GPG settings are configured differently from how cherry-pick (and therefore sequencer) handles them, we will leave support for that to the next commit. Signed-off-by: Johannes Schindelin --- sequencer.c | 97 +++++++++++++++++++++++++++++++++++++++++++++++------ sequencer.h | 3 ++ 2 files changed, 89 insertions(+), 11 deletions(-) diff --git a/sequencer.c b/sequencer.c index ca1961c0f0..6c35fe80b9 100644 --- a/sequencer.c +++ b/sequencer.c @@ -27,6 +27,19 @@ static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo") static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts") static GIT_PATH_FUNC(git_path_head_file, "sequencer/head") +/* + * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and + * GIT_AUTHOR_DATE that will be used for the commit that is currently + * being rebased. + */ +static GIT_PATH_FUNC(rebase_path_author_script, "rebase-merge/author-script") + +/* We will introduce the 'interactive rebase' mode later */ +static inline int is_rebase_i(const struct replay_opts *opts) +{ + return 0; +} + static const char *get_dir(const struct replay_opts *opts) { return git_path_seq_dir(); @@ -377,20 +390,76 @@ static int is_index_unchanged(void) return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.oid.hash); } +static char **read_author_script(void) +{ + struct strbuf script = STRBUF_INIT; + int i, count = 0; + char *p, *p2, **env; + size_t env_size; + + if (strbuf_read_file(&script, rebase_path_author_script(), 256) <= 0) + return NULL; + + for (p = script.buf; *p; p++) + if (skip_prefix(p, "'\\\\''", (const char **)&p2)) + strbuf_splice(&script, p - script.buf, p2 - p, "'", 1); + else if (*p == '\'') + strbuf_splice(&script, p-- - script.buf, 1, "", 0); + else if (*p == '\n') { + *p = '\0'; + count++; + } + + env_size = (count + 1) * sizeof(*env); + strbuf_grow(&script, env_size); + memmove(script.buf + env_size, script.buf, script.len); + p = script.buf + env_size; + env = (char **)strbuf_detach(&script, NULL); + + for (i = 0; i < count; i++) { + env[i] = p; + p += strlen(p) + 1; + } + env[count] = NULL; + + return env; +} + /* * If we are cherry-pick, and if the merge did not result in * hand-editing, we will hit this commit and inherit the original * author date and name. + * * 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. + * + * An exception is when sequencer_commit() is called during an + * interactive rebase: in that case, we will want to retain the + * author metadata. */ -static int run_git_commit(const char *defmsg, struct replay_opts *opts, +int sequencer_commit(const char *defmsg, struct replay_opts *opts, int allow_empty) { + char **env = NULL; struct argv_array array; int rc; const char *value; + if (is_rebase_i(opts)) { + env = read_author_script(); + if (!env) + return error("You have staged changes in your working " + "tree. If these changes are meant to be\n" + "squashed into the previous commit, run:\n\n" + " git commit --amend $gpg_sign_opt_quoted\n\n" + "If they are meant to go into a new commit, " + "run:\n\n" + " git commit $gpg_sign_opt_quoted\n\n" + "In both cases, once you're done, continue " + "with:\n\n" + " git rebase --continue\n"); + } + argv_array_init(&array); argv_array_push(&array, "commit"); argv_array_push(&array, "-n"); @@ -399,14 +468,13 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts, argv_array_pushf(&array, "-S%s", opts->gpg_sign); if (opts->signoff) argv_array_push(&array, "-s"); - if (!opts->edit) { - argv_array_push(&array, "-F"); - argv_array_push(&array, defmsg); - if (!opts->signoff && - !opts->record_origin && - git_config_get_value("commit.cleanup", &value)) - argv_array_push(&array, "--cleanup=verbatim"); - } + if (defmsg) + argv_array_pushl(&array, "-F", defmsg, NULL); + if (opts->edit) + argv_array_push(&array, "-e"); + else if (!opts->signoff && !opts->record_origin && + git_config_get_value("commit.cleanup", &value)) + argv_array_push(&array, "--cleanup=verbatim"); if (allow_empty) argv_array_push(&array, "--allow-empty"); @@ -414,8 +482,11 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts, if (opts->allow_empty_message) argv_array_push(&array, "--allow-empty-message"); - rc = run_command_v_opt(array.argv, RUN_GIT_CMD); + rc = run_command_v_opt_cd_env(array.argv, RUN_GIT_CMD, NULL, + (const char *const *)env); argv_array_clear(&array); + free(env); + return rc; } @@ -664,7 +735,8 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, goto leave; } if (!opts->no_commit) - res = run_git_commit(git_path_merge_msg(), opts, allow); + res = sequencer_commit(opts->edit ? NULL : git_path_merge_msg(), + opts, allow); leave: free_message(commit, &msg); @@ -877,6 +949,9 @@ static int populate_opts_cb(const char *key, const char *value, void *data) static int read_populate_opts(struct replay_opts *opts) { + if (is_rebase_i(opts)) + return 0; + if (!file_exists(git_path_opts_file())) return 0; /* diff --git a/sequencer.h b/sequencer.h index 0b3950d8fd..16deb6c85d 100644 --- a/sequencer.h +++ b/sequencer.h @@ -53,6 +53,9 @@ int sequencer_continue(struct replay_opts *opts); int sequencer_rollback(struct replay_opts *opts); int sequencer_remove_state(struct replay_opts *opts); +int sequencer_commit(const char *defmsg, struct replay_opts *opts, + int allow_empty); + extern const char sign_off_header[]; void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag); From a1a4446a715f09cbb72cf9b78fdd51487e95f267 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 19 May 2016 09:00:44 +0200 Subject: [PATCH 31/92] sequencer: introduce a helper to read files written by scripts As we are slowly teaching the sequencer to perform the hard work for the interactive rebase, we need to read files that were written by shell scripts. These files typically contain a single line and are invariably ended by a line feed (and possibly a carriage return before that). Let's use a helper to read such files and to remove the line ending. Signed-off-by: Johannes Schindelin --- sequencer.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/sequencer.c b/sequencer.c index 6c35fe80b9..086cd0b4be 100644 --- a/sequencer.c +++ b/sequencer.c @@ -242,6 +242,37 @@ static int write_message(struct strbuf *msgbuf, const char *filename) return 0; } +/* + * Reads a file that was presumably written by a shell script, i.e. + * with an end-of-line marker that needs to be stripped. + * + * Returns 1 if the file was read, 0 if it could not be read or does not exist. + */ +static int read_oneliner(struct strbuf *buf, + const char *path, int skip_if_empty) +{ + int orig_len = buf->len; + + if (!file_exists(path)) + return 0; + + if (strbuf_read_file(buf, path, 0) < 0) { + warning_errno("could not read '%s'", path); + return 0; + } + + if (buf->len > orig_len && buf->buf[buf->len - 1] == '\n') { + if (--buf->len > orig_len && buf->buf[buf->len - 1] == '\r') + --buf->len; + buf->buf[buf->len] = '\0'; + } + + if (skip_if_empty && buf->len == orig_len) + return 0; + + return 1; +} + static struct tree *empty_tree(void) { return lookup_tree(EMPTY_TREE_SHA1_BIN); From f9641b2d9f6f71b2f2c6211cefdade3a4331d985 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 18 May 2016 14:49:39 +0200 Subject: [PATCH 32/92] sequencer: prepare for rebase -i's GPG settings The rebase command sports a `--gpg-sign` option that is heeded by the interactive rebase. This patch teaches the sequencer that trick, as part of the bigger effort to make the sequencer the work horse of the interactive rebase. Signed-off-by: Johannes Schindelin --- sequencer.c | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/sequencer.c b/sequencer.c index 086cd0b4be..bf02565b4c 100644 --- a/sequencer.c +++ b/sequencer.c @@ -15,6 +15,7 @@ #include "merge-recursive.h" #include "refs.h" #include "argv-array.h" +#include "quote.h" #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@ -33,6 +34,11 @@ static GIT_PATH_FUNC(git_path_head_file, "sequencer/head") * being rebased. */ static GIT_PATH_FUNC(rebase_path_author_script, "rebase-merge/author-script") +/* + * The following files are written by git-rebase just after parsing the + * command-line (and are only consumed, not modified, by the sequencer). + */ +static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt") /* We will introduce the 'interactive rebase' mode later */ static inline int is_rebase_i(const struct replay_opts *opts) @@ -132,6 +138,16 @@ static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob, return 1; } +static const char *gpg_sign_opt_quoted(struct replay_opts *opts) +{ + static struct strbuf buf = STRBUF_INIT; + + strbuf_reset(&buf); + if (opts->gpg_sign) + sq_quotef(&buf, "-S%s", opts->gpg_sign); + return buf.buf; +} + void *sequencer_entrust(struct replay_opts *opts, void *to_free) { ALLOC_GROW(opts->owned, opts->owned_nr + 1, opts->owned_alloc); @@ -478,17 +494,20 @@ int sequencer_commit(const char *defmsg, struct replay_opts *opts, if (is_rebase_i(opts)) { env = read_author_script(); - if (!env) + if (!env) { + const char *gpg_opt = gpg_sign_opt_quoted(opts); + return error("You have staged changes in your working " "tree. If these changes are meant to be\n" "squashed into the previous commit, run:\n\n" - " git commit --amend $gpg_sign_opt_quoted\n\n" + " git commit --amend %s\n\n" "If they are meant to go into a new commit, " "run:\n\n" - " git commit $gpg_sign_opt_quoted\n\n" + " git commit %s\n\n" "In both cases, once you're done, continue " "with:\n\n" - " git rebase --continue\n"); + " git rebase --continue\n", gpg_opt, gpg_opt); + } } argv_array_init(&array); @@ -980,8 +999,21 @@ static int populate_opts_cb(const char *key, const char *value, void *data) static int read_populate_opts(struct replay_opts *opts) { - if (is_rebase_i(opts)) + if (is_rebase_i(opts)) { + struct strbuf buf = STRBUF_INIT; + + if (read_oneliner(&buf, rebase_path_gpg_sign_opt(), 1)) { + if (!starts_with(buf.buf, "-S")) + strbuf_reset(&buf); + else { + opts->gpg_sign = buf.buf + 2; + sequencer_entrust(opts, + strbuf_detach(&buf, NULL)); + } + } + return 0; + } if (!file_exists(git_path_opts_file())) return 0; From 4344341ebaa6e41a30656d6489ba377c53a24773 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 4 Mar 2016 16:13:26 +0100 Subject: [PATCH 33/92] sequencer: allow editing the commit message on a case-by-case basis In the upcoming commits, we will implement more and more of rebase -i's functionality. One particular feature of the commands to come is that some of them allow editing the commit message while others don't, i.e. we cannot define in the replay_opts whether the commit message should be edited or not. Let's add a new parameter to the sequencer_commit() function. Previously, it was the duty of the caller to ensure that the opts->edit setting indicates whether to let the user edit the commit message or not, indicating that it is an "all or nothing" setting, i.e. that the sequencer wants to let the user edit *all* commit message, or none at all. In the upcoming rebase -i mode, it will depend on the particular command that is currently executed, though. Signed-off-by: Johannes Schindelin --- sequencer.c | 6 +++--- sequencer.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sequencer.c b/sequencer.c index bf02565b4c..6e9732c5a5 100644 --- a/sequencer.c +++ b/sequencer.c @@ -485,7 +485,7 @@ static char **read_author_script(void) * author metadata. */ int sequencer_commit(const char *defmsg, struct replay_opts *opts, - int allow_empty) + int allow_empty, int edit) { char **env = NULL; struct argv_array array; @@ -520,7 +520,7 @@ int sequencer_commit(const char *defmsg, struct replay_opts *opts, argv_array_push(&array, "-s"); if (defmsg) argv_array_pushl(&array, "-F", defmsg, NULL); - if (opts->edit) + if (edit) argv_array_push(&array, "-e"); else if (!opts->signoff && !opts->record_origin && git_config_get_value("commit.cleanup", &value)) @@ -786,7 +786,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } if (!opts->no_commit) res = sequencer_commit(opts->edit ? NULL : git_path_merge_msg(), - opts, allow); + opts, allow, opts->edit); leave: free_message(commit, &msg); diff --git a/sequencer.h b/sequencer.h index 16deb6c85d..7f5222f6c2 100644 --- a/sequencer.h +++ b/sequencer.h @@ -54,7 +54,7 @@ int sequencer_rollback(struct replay_opts *opts); int sequencer_remove_state(struct replay_opts *opts); int sequencer_commit(const char *defmsg, struct replay_opts *opts, - int allow_empty); + int allow_empty, int edit); extern const char sign_off_header[]; From 3eabdeb33580b7646fbe7f924f423f0476ca349b Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 4 Mar 2016 15:01:48 +0100 Subject: [PATCH 34/92] sequencer: support amending commits This teaches the sequencer_commit() function to take an argument that will allow us to implement "todo" commands that need to amend the commit messages ("fixup", "squash" and "reword"). Signed-off-by: Johannes Schindelin --- sequencer.c | 6 ++++-- sequencer.h | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/sequencer.c b/sequencer.c index 6e9732c5a5..60b522e3f0 100644 --- a/sequencer.c +++ b/sequencer.c @@ -485,7 +485,7 @@ static char **read_author_script(void) * author metadata. */ int sequencer_commit(const char *defmsg, struct replay_opts *opts, - int allow_empty, int edit) + int allow_empty, int edit, int amend) { char **env = NULL; struct argv_array array; @@ -514,6 +514,8 @@ int sequencer_commit(const char *defmsg, struct replay_opts *opts, argv_array_push(&array, "commit"); argv_array_push(&array, "-n"); + if (amend) + argv_array_push(&array, "--amend"); if (opts->gpg_sign) argv_array_pushf(&array, "-S%s", opts->gpg_sign); if (opts->signoff) @@ -786,7 +788,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } if (!opts->no_commit) res = sequencer_commit(opts->edit ? NULL : git_path_merge_msg(), - opts, allow, opts->edit); + opts, allow, opts->edit, 0); leave: free_message(commit, &msg); diff --git a/sequencer.h b/sequencer.h index 7f5222f6c2..c45f5c4d62 100644 --- a/sequencer.h +++ b/sequencer.h @@ -54,7 +54,7 @@ int sequencer_rollback(struct replay_opts *opts); int sequencer_remove_state(struct replay_opts *opts); int sequencer_commit(const char *defmsg, struct replay_opts *opts, - int allow_empty, int edit); + int allow_empty, int edit, int amend); extern const char sign_off_header[]; From 52027e4caa9f9d6ad876d32c1b196719d6d883d4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 4 Mar 2016 15:01:48 +0100 Subject: [PATCH 35/92] sequencer: support cleaning up commit messages The sequencer_commit() function already knows how to amend commits, and with this new option, it can also clean up commit messages (i.e. strip out commented lines). This is needed to implement rebase -i's 'fixup' and 'squash' commands as sequencer commands. Signed-off-by: Johannes Schindelin --- sequencer.c | 10 +++++++--- sequencer.h | 3 ++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/sequencer.c b/sequencer.c index 60b522e3f0..75772b808a 100644 --- a/sequencer.c +++ b/sequencer.c @@ -485,7 +485,8 @@ static char **read_author_script(void) * author metadata. */ int sequencer_commit(const char *defmsg, struct replay_opts *opts, - int allow_empty, int edit, int amend) + int allow_empty, int edit, int amend, + int cleanup_commit_message) { char **env = NULL; struct argv_array array; @@ -522,9 +523,12 @@ int sequencer_commit(const char *defmsg, struct replay_opts *opts, argv_array_push(&array, "-s"); if (defmsg) argv_array_pushl(&array, "-F", defmsg, NULL); + if (cleanup_commit_message) + argv_array_push(&array, "--cleanup=strip"); if (edit) argv_array_push(&array, "-e"); - else if (!opts->signoff && !opts->record_origin && + else if (!cleanup_commit_message && + !opts->signoff && !opts->record_origin && git_config_get_value("commit.cleanup", &value)) argv_array_push(&array, "--cleanup=verbatim"); @@ -788,7 +792,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } if (!opts->no_commit) res = sequencer_commit(opts->edit ? NULL : git_path_merge_msg(), - opts, allow, opts->edit, 0); + opts, allow, opts->edit, 0, 0); leave: free_message(commit, &msg); diff --git a/sequencer.h b/sequencer.h index c45f5c4d62..688fff16dd 100644 --- a/sequencer.h +++ b/sequencer.h @@ -54,7 +54,8 @@ int sequencer_rollback(struct replay_opts *opts); int sequencer_remove_state(struct replay_opts *opts); int sequencer_commit(const char *defmsg, struct replay_opts *opts, - int allow_empty, int edit, int amend); + int allow_empty, int edit, int amend, + int cleanup_commit_message); extern const char sign_off_header[]; From 3038780bf0b5ba8bbec97ad4f7616a4d177f89f0 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 9 Apr 2016 14:48:50 +0200 Subject: [PATCH 36/92] sequencer: remember do_recursive_merge()'s return value The return value of do_recursive_merge() may be positive (indicating merge conflicts), so let's OR later error conditions so as not to overwrite them with 0. This is not yet a problem, but preparing for the patches to come: we will teach the sequencer to do rebase -i's job. Signed-off-by: Johannes Schindelin --- sequencer.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/sequencer.c b/sequencer.c index 75772b808a..7953a054f8 100644 --- a/sequencer.c +++ b/sequencer.c @@ -630,7 +630,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, const char *base_label, *next_label; struct commit_message msg = { NULL, NULL, NULL, NULL }; struct strbuf msgbuf = STRBUF_INIT; - int res, unborn = 0, allow; + int res = 0, unborn = 0, allow; if (opts->no_commit) { /* @@ -741,7 +741,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) { - res = do_recursive_merge(base, next, base_label, next_label, + res |= do_recursive_merge(base, next, base_label, next_label, head, &msgbuf, opts); if (res < 0) return res; @@ -750,7 +750,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, struct commit_list *common = NULL; struct commit_list *remotes = NULL; - res = write_message(&msgbuf, git_path_merge_msg()); + res |= write_message(&msgbuf, git_path_merge_msg()); commit_list_insert(base, &common); commit_list_insert(next, &remotes); @@ -787,11 +787,12 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, allow = allow_empty(opts, commit); if (allow < 0) { - res = allow; + res |= allow; goto leave; } if (!opts->no_commit) - res = sequencer_commit(opts->edit ? NULL : git_path_merge_msg(), + res |= sequencer_commit(opts->edit ? + NULL : git_path_merge_msg(), opts, allow, opts->edit, 0, 0); leave: From 0104aab98c06e89c5290f636577efbd51b4bd443 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 15 Apr 2016 17:41:32 +0200 Subject: [PATCH 37/92] sequencer: left-trim lines read from the script Interactive rebase's scripts may be indented; we need to handle this case, too, now that we prepare the sequencer to process interactive rebases. Signed-off-by: Johannes Schindelin --- sequencer.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sequencer.c b/sequencer.c index 7953a054f8..5e5d11388c 100644 --- a/sequencer.c +++ b/sequencer.c @@ -875,6 +875,9 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) char *end_of_object_name; int i, saved, status, padding; + /* left-trim */ + bol += strspn(bol, " \t"); + for (i = 0; i < ARRAY_SIZE(todo_command_strings); i++) if (skip_prefix(bol, todo_command_strings[i], &bol)) { item->command = i; From 10a0f77c2c183f81f3d9a6a5ce9ddf967b77b378 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 23 Aug 2016 15:06:53 +0200 Subject: [PATCH 38/92] sequencer: refactor write_message() The write_message() function safely writes an strbuf to a file. Sometimes it is inconvenient to require an strbuf, though: the text to be written may not be stored in a strbuf, or the strbuf should not be released after writing. Let's refactor "safely writing string to a file" into write_with_lock_file(), and make write_message() use it. The new function makes it easy to create new convenience function write_file_gently(); as some of the upcoming callers of this new function would want to append a newline character, add a flag for it in write_file_gently(), and thus in write_with_lock_file(). Signed-off-by: Johannes Schindelin --- sequencer.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/sequencer.c b/sequencer.c index 5e5d11388c..aa949d45cf 100644 --- a/sequencer.c +++ b/sequencer.c @@ -242,22 +242,37 @@ static void print_advice(int show_hint, struct replay_opts *opts) } } -static int write_message(struct strbuf *msgbuf, const char *filename) +static int write_with_lock_file(const char *filename, + const void *buf, size_t len, int append_eol) { static struct lock_file msg_file; int msg_fd = hold_lock_file_for_update(&msg_file, filename, 0); if (msg_fd < 0) return error_errno(_("Could not lock '%s'"), filename); - if (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0) - return error_errno(_("Could not write to %s"), filename); - strbuf_release(msgbuf); + if (write_in_full(msg_fd, buf, len) < 0) + return error_errno(_("Could not write to '%s'"), filename); + if (append_eol && write(msg_fd, "\n", 1) < 0) + return error_errno(_("Could not write eol to '%s"), filename); if (commit_lock_file(&msg_file) < 0) return error(_("Error wrapping up %s."), filename); return 0; } +static int write_message(struct strbuf *msgbuf, const char *filename) +{ + int res = write_with_lock_file(filename, msgbuf->buf, msgbuf->len, 0); + strbuf_release(msgbuf); + return res; +} + +static int write_file_gently(const char *filename, + const char *text, int append_eol) +{ + return write_with_lock_file(filename, text, strlen(text), append_eol); +} + /* * Reads a file that was presumably written by a shell script, i.e. * with an end-of-line marker that needs to be stripped. From 0d89943b0929b37d90ebb9974aeff004839de646 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 23 Feb 2016 19:21:31 +0100 Subject: [PATCH 39/92] sequencer: remove overzealous assumption in rebase -i mode The sequencer was introduced to make the cherry-pick and revert functionality available as library function, with the original idea being to extend the sequencer to also implement the rebase -i functionality. The test to ensure that all of the commands in the script are identical to the overall operation does not mesh well with that. Therefore let's disable the test in rebase -i mode. While at it, error out early if the "instruction sheet" (i.e. the todo script) could not be parsed. Signed-off-by: Johannes Schindelin --- sequencer.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sequencer.c b/sequencer.c index aa949d45cf..514424567e 100644 --- a/sequencer.c +++ b/sequencer.c @@ -963,7 +963,10 @@ static int read_populate_todo(struct todo_list *todo_list, close(fd); res = parse_insn_buffer(todo_list->buf.buf, todo_list); - if (!res) { + if (res) + return error(_("Unusable instruction sheet: %s"), todo_file); + + if (!is_rebase_i(opts)) { enum todo_command valid = opts->action == REPLAY_PICK ? TODO_PICK : TODO_REVERT; int i; @@ -977,8 +980,6 @@ static int read_populate_todo(struct todo_list *todo_list, return error(_("Cannot revert during a cherry-pick.")); } - if (res) - return error(_("Unusable instruction sheet: %s"), todo_file); return 0; } From e8cff2f0a34c9d696c6356597aa8ab85d18d8f1a Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 9 Sep 2016 16:20:26 +0200 Subject: [PATCH 40/92] sequencer: mark action_name() for translation The definition of this function goes back all the way to 043a449 (sequencer: factor code out of revert builtin, 2012-01-11), long before a serious effort was made to translate all the error messages. It is slightly out of the context of the current patch series (whose purpose it is to re-implement the performance critical parts of the interactive rebase in C) to make the error messages in the sequencer translatable, but what the heck. We'll just do it while we're looking at this part of the code. Signed-off-by: Johannes Schindelin --- sequencer.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sequencer.c b/sequencer.c index 514424567e..1e7f29eaed 100644 --- a/sequencer.c +++ b/sequencer.c @@ -176,7 +176,7 @@ int sequencer_remove_state(struct replay_opts *opts) static const char *action_name(const struct replay_opts *opts) { - return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick"; + return opts->action == REPLAY_REVERT ? N_("revert") : N_("cherry-pick"); } struct commit_message { @@ -312,10 +312,10 @@ static struct tree *empty_tree(void) static int error_dirty_index(struct replay_opts *opts) { if (read_cache_unmerged()) - return error_resolve_conflict(action_name(opts)); + return error_resolve_conflict(_(action_name(opts))); error(_("Your local changes would be overwritten by %s."), - action_name(opts)); + _(action_name(opts))); if (advice_commit_before_merge) advise(_("Commit your changes or stash them to proceed.")); @@ -333,7 +333,7 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from, if (checkout_fast_forward(from, to, 1)) return -1; /* the callee should have complained already */ - strbuf_addf(&sb, _("%s: fast-forward"), action_name(opts)); + strbuf_addf(&sb, _("%s: fast-forward"), _(action_name(opts))); transaction = ref_transaction_begin(&err); if (!transaction || @@ -409,7 +409,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next, write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) /* TRANSLATORS: %s will be "revert" or "cherry-pick" */ return error(_("%s: Unable to write new index file"), - action_name(opts)); + _(action_name(opts))); rollback_lock_file(&index_lock); if (opts->signoff) @@ -840,14 +840,14 @@ static int read_and_refresh_cache(struct replay_opts *opts) if (read_index_preload(&the_index, NULL) < 0) { rollback_lock_file(&index_lock); return error(_("git %s: failed to read the index"), - action_name(opts)); + _(action_name(opts))); } refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL); if (the_index.cache_changed && index_fd >= 0) { if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) { rollback_lock_file(&index_lock); return error(_("git %s: failed to refresh the index"), - action_name(opts)); + _(action_name(opts))); } } rollback_lock_file(&index_lock); From 4dd4cbca2ca5edca65b5fe3594d4542523b47e3a Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 9 Sep 2016 16:46:21 +0200 Subject: [PATCH 41/92] sequencer: quote filenames in error messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes the code consistent by fixing quite a couple of error messages. Suggested by Jakub Narębski. Signed-off-by: Johannes Schindelin --- sequencer.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/sequencer.c b/sequencer.c index 1e7f29eaed..465e018913 100644 --- a/sequencer.c +++ b/sequencer.c @@ -255,7 +255,7 @@ static int write_with_lock_file(const char *filename, if (append_eol && write(msg_fd, "\n", 1) < 0) return error_errno(_("Could not write eol to '%s"), filename); if (commit_lock_file(&msg_file) < 0) - return error(_("Error wrapping up %s."), filename); + return error(_("Error wrapping up '%s'."), filename); return 0; } @@ -955,16 +955,16 @@ static int read_populate_todo(struct todo_list *todo_list, strbuf_reset(&todo_list->buf); fd = open(todo_file, O_RDONLY); if (fd < 0) - return error_errno(_("Could not open %s"), todo_file); + return error_errno(_("Could not open '%s'"), todo_file); if (strbuf_read(&todo_list->buf, fd, 0) < 0) { close(fd); - return error(_("Could not read %s."), todo_file); + return error(_("Could not read '%s'."), todo_file); } close(fd); res = parse_insn_buffer(todo_list->buf.buf, todo_list); if (res) - return error(_("Unusable instruction sheet: %s"), todo_file); + return error(_("Unusable instruction sheet: '%s'"), todo_file); if (!is_rebase_i(opts)) { enum todo_command valid = @@ -1050,7 +1050,7 @@ static int read_populate_opts(struct replay_opts *opts) * are pretty certain that it is syntactically correct. */ if (git_config_from_file(populate_opts_cb, git_path_opts_file(), opts) < 0) - return error(_("Malformed options sheet: %s"), + return error(_("Malformed options sheet: '%s'"), git_path_opts_file()); return 0; } @@ -1093,7 +1093,7 @@ static int create_seq_dir(void) return -1; } else if (mkdir(git_path_seq_dir(), 0777) < 0) - return error_errno(_("Could not create sequencer directory %s"), + return error_errno(_("Could not create sequencer directory '%s'"), git_path_seq_dir()); return 0; } @@ -1112,12 +1112,12 @@ static int save_head(const char *head) strbuf_addf(&buf, "%s\n", head); if (write_in_full(fd, buf.buf, buf.len) < 0) { rollback_lock_file(&head_lock); - return error_errno(_("Could not write to %s"), + return error_errno(_("Could not write to '%s'"), git_path_head_file()); } if (commit_lock_file(&head_lock) < 0) { rollback_lock_file(&head_lock); - return error(_("Error wrapping up %s."), git_path_head_file()); + return error(_("Error wrapping up '%s'."), git_path_head_file()); } return 0; } @@ -1162,9 +1162,9 @@ int sequencer_rollback(struct replay_opts *opts) return rollback_single_pick(); } if (!f) - return error_errno(_("cannot open %s"), git_path_head_file()); + return error_errno(_("cannot open '%s'"), git_path_head_file()); if (strbuf_getline_lf(&buf, f)) { - error(_("cannot read %s: %s"), git_path_head_file(), + error(_("cannot read '%s': %s"), git_path_head_file(), ferror(f) ? strerror(errno) : _("unexpected end of file")); fclose(f); goto fail; @@ -1203,7 +1203,7 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts) todo_list->buf.len - offset) < 0) return error_errno(_("Could not write to '%s'"), todo_path); if (commit_lock_file(&todo_lock) < 0) - return error(_("Error wrapping up %s."), todo_path); + return error(_("Error wrapping up '%s'."), todo_path); return 0; } From d6e93bc6e6750bbe8cf60479556dc97899487596 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 9 Sep 2016 16:47:40 +0200 Subject: [PATCH 42/92] sequencer: remove bogus hint for translators When translating error messages, we need to be careful *not* to translate the todo commands such as "pick", "reword", etc because they are commands, and Git would not understand translated versions of those commands. Therefore, translating the commands in the error messages would simply be misleading. Signed-off-by: Johannes Schindelin --- sequencer.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/sequencer.c b/sequencer.c index 465e018913..cdff0f1954 100644 --- a/sequencer.c +++ b/sequencer.c @@ -697,8 +697,6 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, return fast_forward_to(commit->object.oid.hash, head, unborn, opts); 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"), command_to_string(command), oid_to_hex(&parent->object.oid)); From 60d59900b97deaa6d89cb4003f6cd45033f7bcc5 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 10 Mar 2016 13:19:54 +0100 Subject: [PATCH 43/92] pull: drop confusing prefix parameter of die_on_unclean_work_tree() In cmd_pull(), when verifying that there are no changes preventing a rebasing pull, we diligently pass the prefix parameter to the die_on_unclean_work_tree() function which in turn diligently passes it to the has_unstaged_changes() and has_uncommitted_changes() functions. The casual reader might now be curious (as this developer was) whether that means that calling `git pull --rebase` in a subdirectory will ignore unstaged changes in other parts of the working directory. And be puzzled that `git pull --rebase` (correctly) complains about those changes outside of the current directory. The puzzle is easily resolved: while we take pains to pass around the prefix and even pass it to init_revisions(), the fact that no paths are passed to init_revisions() ensures that the prefix is simply ignored. That, combined with the fact that we will *always* want a *full* working directory check before running a rebasing pull, is reason enough to simply do away with the actual prefix parameter and to pass NULL instead, as if we were running this from the top-level working directory anyway. Signed-off-by: Johannes Schindelin --- builtin/pull.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/builtin/pull.c b/builtin/pull.c index 398aae16c0..d4bd6350f0 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -328,12 +328,12 @@ static int git_pull_config(const char *var, const char *value, void *cb) /** * Returns 1 if there are unstaged changes, 0 otherwise. */ -static int has_unstaged_changes(const char *prefix) +static int has_unstaged_changes(void) { struct rev_info rev_info; int result; - init_revisions(&rev_info, prefix); + init_revisions(&rev_info, NULL); DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES); DIFF_OPT_SET(&rev_info.diffopt, QUICK); diff_setup_done(&rev_info.diffopt); @@ -344,7 +344,7 @@ static int has_unstaged_changes(const char *prefix) /** * Returns 1 if there are uncommitted changes, 0 otherwise. */ -static int has_uncommitted_changes(const char *prefix) +static int has_uncommitted_changes(void) { struct rev_info rev_info; int result; @@ -352,7 +352,7 @@ static int has_uncommitted_changes(const char *prefix) if (is_cache_unborn()) return 0; - init_revisions(&rev_info, prefix); + init_revisions(&rev_info, NULL); DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES); DIFF_OPT_SET(&rev_info.diffopt, QUICK); add_head_to_pending(&rev_info); @@ -365,7 +365,7 @@ static int has_uncommitted_changes(const char *prefix) * If the work tree has unstaged or uncommitted changes, dies with the * appropriate message. */ -static void die_on_unclean_work_tree(const char *prefix) +static void die_on_unclean_work_tree(void) { struct lock_file *lock_file = xcalloc(1, sizeof(*lock_file)); int do_die = 0; @@ -375,12 +375,12 @@ static void die_on_unclean_work_tree(const char *prefix) update_index_if_able(&the_index, lock_file); rollback_lock_file(lock_file); - if (has_unstaged_changes(prefix)) { + if (has_unstaged_changes()) { error(_("Cannot pull with rebase: You have unstaged changes.")); do_die = 1; } - if (has_uncommitted_changes(prefix)) { + if (has_uncommitted_changes()) { if (do_die) error(_("Additionally, your index contains uncommitted changes.")); else @@ -875,7 +875,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix) die(_("Updating an unborn branch with changes added to the index.")); if (!autostash) - die_on_unclean_work_tree(prefix); + die_on_unclean_work_tree(); if (get_rebase_fork_point(rebase_fork_point, repo, *refspecs)) hashclr(rebase_fork_point); From 0a38f0c1ce3b62b96b7f2436083289f8e2219440 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 10 Mar 2016 14:57:54 +0100 Subject: [PATCH 44/92] pull: make code more similar to the shell script again When converting the pull command to a builtin, the require_clean_work_tree() function was renamed and the pull-specific parts hard-coded. This makes it impossible to reuse the code, so let's modify the code to make it more similar to the original shell script again. Signed-off-by: Johannes Schindelin --- builtin/pull.c | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/builtin/pull.c b/builtin/pull.c index d4bd6350f0..a3ed0541d2 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -365,10 +365,11 @@ static int has_uncommitted_changes(void) * If the work tree has unstaged or uncommitted changes, dies with the * appropriate message. */ -static void die_on_unclean_work_tree(void) +static int require_clean_work_tree(const char *action, const char *hint, + int gently) { struct lock_file *lock_file = xcalloc(1, sizeof(*lock_file)); - int do_die = 0; + int err = 0; hold_locked_index(lock_file, 0); refresh_cache(REFRESH_QUIET); @@ -376,20 +377,27 @@ static void die_on_unclean_work_tree(void) rollback_lock_file(lock_file); if (has_unstaged_changes()) { - error(_("Cannot pull with rebase: You have unstaged changes.")); - do_die = 1; + error(_("Cannot %s: You have unstaged changes."), _(action)); + err = 1; } if (has_uncommitted_changes()) { - if (do_die) + if (err) error(_("Additionally, your index contains uncommitted changes.")); else - error(_("Cannot pull with rebase: Your index contains uncommitted changes.")); - do_die = 1; + error(_("Cannot %s: Your index contains uncommitted changes."), + _(action)); + err = 1; } - if (do_die) - exit(1); + if (err) { + if (hint) + error("%s", hint); + if (!gently) + exit(err); + } + + return err; } /** @@ -875,7 +883,8 @@ int cmd_pull(int argc, const char **argv, const char *prefix) die(_("Updating an unborn branch with changes added to the index.")); if (!autostash) - die_on_unclean_work_tree(); + require_clean_work_tree(N_("pull with rebase"), + "Please commit or stash them.", 0); if (get_rebase_fork_point(rebase_fork_point, repo, *refspecs)) hashclr(rebase_fork_point); From 28717d297a01fedaab9f78e6a5a73d145267ac6a Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 25 Feb 2016 13:46:53 +0100 Subject: [PATCH 45/92] Make the require_clean_work_tree() function truly reusable It is remarkable that libgit.a did not sport this function yet... Let's move it into a more prominent (and into an actually reusable) spot: wt-status.[ch]. Signed-off-by: Johannes Schindelin --- builtin/pull.c | 76 +------------------------------------------------- wt-status.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++++ wt-status.h | 3 ++ 3 files changed, 79 insertions(+), 75 deletions(-) diff --git a/builtin/pull.c b/builtin/pull.c index a3ed0541d2..14ef8b5820 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -17,6 +17,7 @@ #include "revision.h" #include "tempfile.h" #include "lockfile.h" +#include "wt-status.h" enum rebase_type { REBASE_INVALID = -1, @@ -325,81 +326,6 @@ static int git_pull_config(const char *var, const char *value, void *cb) return git_default_config(var, value, cb); } -/** - * Returns 1 if there are unstaged changes, 0 otherwise. - */ -static int has_unstaged_changes(void) -{ - struct rev_info rev_info; - int result; - - init_revisions(&rev_info, NULL); - DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES); - DIFF_OPT_SET(&rev_info.diffopt, QUICK); - diff_setup_done(&rev_info.diffopt); - result = run_diff_files(&rev_info, 0); - return diff_result_code(&rev_info.diffopt, result); -} - -/** - * Returns 1 if there are uncommitted changes, 0 otherwise. - */ -static int has_uncommitted_changes(void) -{ - struct rev_info rev_info; - int result; - - if (is_cache_unborn()) - return 0; - - init_revisions(&rev_info, NULL); - DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES); - DIFF_OPT_SET(&rev_info.diffopt, QUICK); - add_head_to_pending(&rev_info); - diff_setup_done(&rev_info.diffopt); - result = run_diff_index(&rev_info, 1); - return diff_result_code(&rev_info.diffopt, result); -} - -/** - * If the work tree has unstaged or uncommitted changes, dies with the - * appropriate message. - */ -static int require_clean_work_tree(const char *action, const char *hint, - int gently) -{ - struct lock_file *lock_file = xcalloc(1, sizeof(*lock_file)); - int err = 0; - - hold_locked_index(lock_file, 0); - refresh_cache(REFRESH_QUIET); - update_index_if_able(&the_index, lock_file); - rollback_lock_file(lock_file); - - if (has_unstaged_changes()) { - error(_("Cannot %s: You have unstaged changes."), _(action)); - err = 1; - } - - if (has_uncommitted_changes()) { - if (err) - error(_("Additionally, your index contains uncommitted changes.")); - else - error(_("Cannot %s: Your index contains uncommitted changes."), - _(action)); - err = 1; - } - - if (err) { - if (hint) - error("%s", hint); - if (!gently) - exit(err); - } - - return err; -} - /** * Appends merge candidates from FETCH_HEAD that are not marked not-for-merge * into merge_heads. diff --git a/wt-status.c b/wt-status.c index 539aac15a3..9ab9adc415 100644 --- a/wt-status.c +++ b/wt-status.c @@ -16,6 +16,7 @@ #include "strbuf.h" #include "utf8.h" #include "worktree.h" +#include "lockfile.h" static const char cut_line[] = "------------------------ >8 ------------------------\n"; @@ -2209,3 +2210,77 @@ void wt_status_print(struct wt_status *s) break; } } + +/** + * Returns 1 if there are unstaged changes, 0 otherwise. + */ +static int has_unstaged_changes(void) +{ + struct rev_info rev_info; + int result; + + init_revisions(&rev_info, NULL); + DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES); + DIFF_OPT_SET(&rev_info.diffopt, QUICK); + diff_setup_done(&rev_info.diffopt); + result = run_diff_files(&rev_info, 0); + return diff_result_code(&rev_info.diffopt, result); +} + +/** + * Returns 1 if there are uncommitted changes, 0 otherwise. + */ +static int has_uncommitted_changes(void) +{ + struct rev_info rev_info; + int result; + + if (is_cache_unborn()) + return 0; + + init_revisions(&rev_info, NULL); + DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES); + DIFF_OPT_SET(&rev_info.diffopt, QUICK); + add_head_to_pending(&rev_info); + diff_setup_done(&rev_info.diffopt); + result = run_diff_index(&rev_info, 1); + return diff_result_code(&rev_info.diffopt, result); +} + +/** + * If the work tree has unstaged or uncommitted changes, dies with the + * appropriate message. + */ +int require_clean_work_tree(const char *action, const char *hint, int gently) +{ + struct lock_file *lock_file = xcalloc(1, sizeof(*lock_file)); + int err = 0; + + hold_locked_index(lock_file, 0); + refresh_cache(REFRESH_QUIET); + update_index_if_able(&the_index, lock_file); + rollback_lock_file(lock_file); + + if (has_unstaged_changes()) { + error(_("Cannot %s: You have unstaged changes."), _(action)); + err = 1; + } + + if (has_uncommitted_changes()) { + if (err) + error(_("Additionally, your index contains uncommitted changes.")); + else + error(_("Cannot %s: Your index contains uncommitted changes."), + _(action)); + err = 1; + } + + if (err) { + if (hint) + error("%s", hint); + if (!gently) + exit(err); + } + + return err; +} diff --git a/wt-status.h b/wt-status.h index e401837707..03ecf53de0 100644 --- a/wt-status.h +++ b/wt-status.h @@ -128,4 +128,7 @@ void status_printf_ln(struct wt_status *s, const char *color, const char *fmt, . __attribute__((format (printf, 3, 4))) void status_printf(struct wt_status *s, const char *color, const char *fmt, ...); +/* The following function expect that the caller took care of reading the index. */ +int require_clean_work_tree(const char *action, const char *hint, int gently); + #endif /* STATUS_H */ From c9519d9cf28dead2c32d9c54bfae9b423536b577 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 10 Mar 2016 15:03:03 +0100 Subject: [PATCH 46/92] Export also the has_un{staged,committed}_changed() functions They will be used in the upcoming rebase helper. Signed-off-by: Johannes Schindelin --- wt-status.c | 4 ++-- wt-status.h | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/wt-status.c b/wt-status.c index 9ab9adc415..f1120e6f62 100644 --- a/wt-status.c +++ b/wt-status.c @@ -2214,7 +2214,7 @@ void wt_status_print(struct wt_status *s) /** * Returns 1 if there are unstaged changes, 0 otherwise. */ -static int has_unstaged_changes(void) +int has_unstaged_changes(void) { struct rev_info rev_info; int result; @@ -2230,7 +2230,7 @@ static int has_unstaged_changes(void) /** * Returns 1 if there are uncommitted changes, 0 otherwise. */ -static int has_uncommitted_changes(void) +int has_uncommitted_changes(void) { struct rev_info rev_info; int result; diff --git a/wt-status.h b/wt-status.h index 03ecf53de0..68e367a235 100644 --- a/wt-status.h +++ b/wt-status.h @@ -128,7 +128,9 @@ void status_printf_ln(struct wt_status *s, const char *color, const char *fmt, . __attribute__((format (printf, 3, 4))) void status_printf(struct wt_status *s, const char *color, const char *fmt, ...); -/* The following function expect that the caller took care of reading the index. */ +/* The following functions expect that the caller took care of reading the index. */ +int has_unstaged_changes(void); +int has_uncommitted_changes(void); int require_clean_work_tree(const char *action, const char *hint, int gently); #endif /* STATUS_H */ From b4d0c1f8fc3272dc7f694e045ad54e395c839300 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 9 Apr 2016 15:04:01 +0200 Subject: [PATCH 47/92] wt-status: teach has_{unstaged,uncommitted}_changes() about submodules Sometimes we are *actually* interested in those changes... For example when an interactive rebase wants to continue with a staged submodule update. Signed-off-by: Johannes Schindelin --- builtin/pull.c | 2 +- wt-status.c | 16 +++++++++------- wt-status.h | 7 ++++--- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/builtin/pull.c b/builtin/pull.c index 14ef8b5820..c639167586 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -810,7 +810,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix) if (!autostash) require_clean_work_tree(N_("pull with rebase"), - "Please commit or stash them.", 0); + "Please commit or stash them.", 1, 0); if (get_rebase_fork_point(rebase_fork_point, repo, *refspecs)) hashclr(rebase_fork_point); diff --git a/wt-status.c b/wt-status.c index f1120e6f62..086ae793f2 100644 --- a/wt-status.c +++ b/wt-status.c @@ -2214,13 +2214,14 @@ void wt_status_print(struct wt_status *s) /** * Returns 1 if there are unstaged changes, 0 otherwise. */ -int has_unstaged_changes(void) +int has_unstaged_changes(int ignore_submodules) { struct rev_info rev_info; int result; init_revisions(&rev_info, NULL); - DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES); + if (ignore_submodules) + DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES); DIFF_OPT_SET(&rev_info.diffopt, QUICK); diff_setup_done(&rev_info.diffopt); result = run_diff_files(&rev_info, 0); @@ -2230,7 +2231,7 @@ int has_unstaged_changes(void) /** * Returns 1 if there are uncommitted changes, 0 otherwise. */ -int has_uncommitted_changes(void) +int has_uncommitted_changes(int ignore_submodules) { struct rev_info rev_info; int result; @@ -2239,7 +2240,8 @@ int has_uncommitted_changes(void) return 0; init_revisions(&rev_info, NULL); - DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES); + if (ignore_submodules) + DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES); DIFF_OPT_SET(&rev_info.diffopt, QUICK); add_head_to_pending(&rev_info); diff_setup_done(&rev_info.diffopt); @@ -2251,7 +2253,7 @@ int has_uncommitted_changes(void) * If the work tree has unstaged or uncommitted changes, dies with the * appropriate message. */ -int require_clean_work_tree(const char *action, const char *hint, int gently) +int require_clean_work_tree(const char *action, const char *hint, int ignore_submodules, int gently) { struct lock_file *lock_file = xcalloc(1, sizeof(*lock_file)); int err = 0; @@ -2261,12 +2263,12 @@ int require_clean_work_tree(const char *action, const char *hint, int gently) update_index_if_able(&the_index, lock_file); rollback_lock_file(lock_file); - if (has_unstaged_changes()) { + if (has_unstaged_changes(ignore_submodules)) { error(_("Cannot %s: You have unstaged changes."), _(action)); err = 1; } - if (has_uncommitted_changes()) { + if (has_uncommitted_changes(ignore_submodules)) { if (err) error(_("Additionally, your index contains uncommitted changes.")); else diff --git a/wt-status.h b/wt-status.h index 68e367a235..54fec77032 100644 --- a/wt-status.h +++ b/wt-status.h @@ -129,8 +129,9 @@ __attribute__((format (printf, 3, 4))) void status_printf(struct wt_status *s, const char *color, const char *fmt, ...); /* The following functions expect that the caller took care of reading the index. */ -int has_unstaged_changes(void); -int has_uncommitted_changes(void); -int require_clean_work_tree(const char *action, const char *hint, int gently); +int has_unstaged_changes(int ignore_submodules); +int has_uncommitted_changes(int ignore_submodules); +int require_clean_work_tree(const char *action, const char *hint, + int ignore_submodules, int gently); #endif /* STATUS_H */ From ff326864317652fd86b4b07aed8ca894b7bfd8cf Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 9 Feb 2016 14:10:43 +0100 Subject: [PATCH 48/92] sequencer: support a new action: 'interactive rebase' This patch introduces a new action for the sequencer. It really does not do a whole lot of its own right now, but lays the ground work for patches to come. The intention, of course, is to finally make the sequencer the work horse of the interactive rebase (the original idea behind the "sequencer" concept). Signed-off-by: Johannes Schindelin --- sequencer.c | 32 +++++++++++++++++++++++++++++--- sequencer.h | 3 ++- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/sequencer.c b/sequencer.c index cdff0f1954..c89a0cffa1 100644 --- a/sequencer.c +++ b/sequencer.c @@ -28,6 +28,14 @@ static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo") static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts") static GIT_PATH_FUNC(git_path_head_file, "sequencer/head") +static GIT_PATH_FUNC(rebase_path, "rebase-merge") +/* + * The file containing rebase commands, comments, and empty lines. + * This file is created by "git rebase -i" then edited by the user. As + * the lines are processed, they are removed from the front of this + * file and written to the tail of 'done'. + */ +static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo") /* * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and * GIT_AUTHOR_DATE that will be used for the commit that is currently @@ -43,16 +51,20 @@ static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt") /* We will introduce the 'interactive rebase' mode later */ static inline int is_rebase_i(const struct replay_opts *opts) { - return 0; + return opts->action == REPLAY_INTERACTIVE_REBASE; } static const char *get_dir(const struct replay_opts *opts) { + if (is_rebase_i(opts)) + return rebase_path(); return git_path_seq_dir(); } static const char *get_todo_path(const struct replay_opts *opts) { + if (is_rebase_i(opts)) + return rebase_path_todo(); return git_path_todo_file(); } @@ -176,7 +188,15 @@ int sequencer_remove_state(struct replay_opts *opts) static const char *action_name(const struct replay_opts *opts) { - return opts->action == REPLAY_REVERT ? N_("revert") : N_("cherry-pick"); + switch (opts->action) { + case REPLAY_REVERT: + return N_("revert"); + case REPLAY_PICK: + return N_("cherry-pick"); + case REPLAY_INTERACTIVE_REBASE: + return N_("rebase -i"); + } + die(_("Unknown action: %d"), opts->action); } struct commit_message { @@ -407,7 +427,10 @@ static int do_recursive_merge(struct commit *base, struct commit *next, if (active_cache_changed && write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) - /* TRANSLATORS: %s will be "revert" or "cherry-pick" */ + /* + * TRANSLATORS: %s will be "revert", "cherry-pick" or + * "rebase -i". + */ return error(_("%s: Unable to write new index file"), _(action_name(opts))); rollback_lock_file(&index_lock); @@ -1192,6 +1215,9 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts) const char *todo_path = get_todo_path(opts); int next = todo_list->current, offset, fd; + if (is_rebase_i(opts)) + next++; + fd = hold_lock_file_for_update(&todo_lock, todo_path, 0); if (fd < 0) return error_errno(_("Could not lock '%s'"), todo_path); diff --git a/sequencer.h b/sequencer.h index 688fff16dd..edd7d4a8d1 100644 --- a/sequencer.h +++ b/sequencer.h @@ -7,7 +7,8 @@ const char *git_path_seq_dir(void); enum replay_action { REPLAY_REVERT, - REPLAY_PICK + REPLAY_PICK, + REPLAY_INTERACTIVE_REBASE }; struct replay_opts { From e7a3558bee3345bc9195947039bbaedbd7e0b5be Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 14:07:35 +0200 Subject: [PATCH 49/92] sequencer (rebase -i): implement the 'noop' command The 'noop' command is probably the most boring of all rebase -i commands to support in the sequencer. Which makes it an excellent candidate for this first stab to add support for rebase -i's commands to the sequencer. For the moment, let's also treat empty lines and commented-out lines as 'noop'; We will refine that handling later in this patch series. To make it easier to identify "classes" of todo_commands (such as: determine whether a command is pick-like, i.e. handles a single commit), let's enforce a certain order of said commands. Signed-off-by: Johannes Schindelin --- sequencer.c | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/sequencer.c b/sequencer.c index c89a0cffa1..a148d3a9ad 100644 --- a/sequencer.c +++ b/sequencer.c @@ -642,14 +642,23 @@ static int allow_empty(struct replay_opts *opts, struct commit *commit) return 1; } +/* + * Note that ordering matters in this enum. Not only must it match the mapping + * below, it is also divided into several sections that matter. When adding + * new commands, make sure you add it in the right section. + */ enum todo_command { + /* commands that handle commits */ TODO_PICK = 0, - TODO_REVERT + TODO_REVERT, + /* commands that do nothing but are counted for reporting progress */ + TODO_NOOP }; static const char *todo_command_strings[] = { "pick", - "revert" + "revert", + "noop" }; static const char *command_to_string(const enum todo_command command) @@ -914,6 +923,14 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) /* left-trim */ bol += strspn(bol, " \t"); + if (bol == eol || *bol == '\r' || *bol == comment_line_char) { + item->command = TODO_NOOP; + item->commit = NULL; + item->arg = bol; + item->arg_len = eol - bol; + return 0; + } + for (i = 0; i < ARRAY_SIZE(todo_command_strings); i++) if (skip_prefix(bol, todo_command_strings[i], &bol)) { item->command = i; @@ -922,6 +939,13 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) if (i >= ARRAY_SIZE(todo_command_strings)) return -1; + if (item->command == TODO_NOOP) { + item->commit = NULL; + item->arg = bol; + item->arg_len = eol - bol; + return 0; + } + /* Eat up extra spaces/ tabs before object name */ padding = strspn(bol, " \t"); if (!padding) @@ -1281,7 +1305,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) struct todo_item *item = todo_list->items + todo_list->current; if (save_todo(todo_list, opts)) return -1; - res = do_pick_commit(item->command, item->commit, opts); + if (item->command <= TODO_REVERT) + res = do_pick_commit(item->command, item->commit, + opts); + else if (item->command != TODO_NOOP) + return error("Unknown command %d", item->command); + todo_list->current++; if (res) return res; From 6ec43bcbfb22856357ab6d81807f3ed663fba304 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 14:07:47 +0200 Subject: [PATCH 50/92] sequencer (rebase -i): implement the 'edit' command This patch is a straight-forward reimplementation of the `edit` operation of the interactive rebase command. Well, not *quite* straight-forward: when stopping, the `edit` command wants to write the `patch` file (which is not only the patch, but includes the commit message and author information). To that end, this patch requires the earlier work that taught the log-tree machinery to respect the `file` setting of rev_info->diffopt to write to a file stream different than stdout. Signed-off-by: Johannes Schindelin --- sequencer.c | 114 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 112 insertions(+), 2 deletions(-) diff --git a/sequencer.c b/sequencer.c index a148d3a9ad..36a4c6b20b 100644 --- a/sequencer.c +++ b/sequencer.c @@ -16,6 +16,7 @@ #include "refs.h" #include "argv-array.h" #include "quote.h" +#include "log-tree.h" #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@ -42,6 +43,20 @@ static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo") * being rebased. */ static GIT_PATH_FUNC(rebase_path_author_script, "rebase-merge/author-script") +/* + * When an "edit" rebase command is being processed, the SHA1 of the + * commit to be edited is recorded in this file. When "git rebase + * --continue" is executed, if there are any staged changes then they + * will be amended to the HEAD commit, but only provided the HEAD + * commit is still the commit to be edited. When any other rebase + * command is processed, this file is deleted. + */ +static GIT_PATH_FUNC(rebase_path_amend, "rebase-merge/amend") +/* + * When we stop at a given patch via the "edit" command, this file contains + * the long commit name of the corresponding patch. + */ +static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha") /* * The following files are written by git-rebase just after parsing the * command-line (and are only consumed, not modified, by the sequencer). @@ -651,6 +666,7 @@ enum todo_command { /* commands that handle commits */ TODO_PICK = 0, TODO_REVERT, + TODO_EDIT, /* commands that do nothing but are counted for reporting progress */ TODO_NOOP }; @@ -658,6 +674,7 @@ enum todo_command { static const char *todo_command_strings[] = { "pick", "revert", + "edit", "noop" }; @@ -1290,9 +1307,85 @@ static int save_opts(struct replay_opts *opts) return res; } +static int make_patch(struct commit *commit, struct replay_opts *opts) +{ + struct strbuf buf = STRBUF_INIT; + struct rev_info log_tree_opt; + const char *commit_buffer = get_commit_buffer(commit, NULL), *subject; + int res = 0; + + if (write_file_gently(rebase_path_stopped_sha(), + short_commit_name(commit), 1) < 0) + return -1; + + strbuf_addf(&buf, "%s/patch", get_dir(opts)); + memset(&log_tree_opt, 0, sizeof(log_tree_opt)); + init_revisions(&log_tree_opt, NULL); + log_tree_opt.abbrev = 0; + log_tree_opt.diff = 1; + log_tree_opt.diffopt.output_format = DIFF_FORMAT_PATCH; + log_tree_opt.disable_stdin = 1; + log_tree_opt.no_commit_id = 1; + log_tree_opt.diffopt.file = fopen(buf.buf, "w"); + log_tree_opt.diffopt.use_color = GIT_COLOR_NEVER; + if (!log_tree_opt.diffopt.file) + res |= error_errno("could not open '%s'", buf.buf); + else { + res |= log_tree_commit(&log_tree_opt, commit); + fclose(log_tree_opt.diffopt.file); + } + strbuf_reset(&buf); + + strbuf_addf(&buf, "%s/message", get_dir(opts)); + if (!file_exists(buf.buf)) { + find_commit_subject(commit_buffer, &subject); + res |= write_file_gently(buf.buf, subject, 1); + unuse_commit_buffer(commit, commit_buffer); + } + strbuf_release(&buf); + + return res; +} + +static int intend_to_amend(void) +{ + unsigned char head[20]; + + if (get_sha1("HEAD", head)) + return error("Cannot read HEAD"); + + return write_file_gently(rebase_path_amend(), sha1_to_hex(head), 1); +} + +static int error_with_patch(struct commit *commit, + const char *subject, int subject_len, + struct replay_opts *opts, int exit_code, int to_amend) +{ + if (make_patch(commit, opts)) + return -1; + + if (to_amend) { + if (intend_to_amend()) + return -1; + + fprintf(stderr, "You can amend the commit now, with\n" + "\n" + " git commit --amend %s\n" + "\n" + "Once you are satisfied with your changes, run\n" + "\n" + " git rebase --continue\n", gpg_sign_opt_quoted(opts)); + } + else if (exit_code) + fprintf(stderr, "Could not apply %s... %.*s\n", + short_commit_name(commit), subject_len, subject); + + return exit_code; +} + static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) { - int res; + int res = 0; setenv(GIT_REFLOG_ACTION, action_name(opts), 0); if (opts->allow_ff) @@ -1305,9 +1398,20 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) struct todo_item *item = todo_list->items + todo_list->current; if (save_todo(todo_list, opts)) return -1; - if (item->command <= TODO_REVERT) + if (item->command <= TODO_EDIT) { res = do_pick_commit(item->command, item->commit, opts); + if (item->command == TODO_EDIT) { + struct commit *commit = item->commit; + if (!res) + warning("Stopped at %s... %.*s", + short_commit_name(commit), + item->arg_len, item->arg); + return error_with_patch(commit, + item->arg, item->arg_len, opts, res, + !res); + } + } else if (item->command != TODO_NOOP) return error("Unknown command %d", item->command); @@ -1316,6 +1420,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) return res; } + if (is_rebase_i(opts)) { + /* Stopped in the middle, as planned? */ + if (todo_list->current < todo_list->nr) + return 0; + } + /* * Sequence of picks finished successfully; cleanup by * removing the .git/sequencer directory From 494747e7a1c0d76aa32ec31391f94545feb201b2 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 14:08:01 +0200 Subject: [PATCH 51/92] sequencer (rebase -i): implement the 'exec' command The 'exec' command is a little special among rebase -i's commands, as it does *not* have a SHA-1 as first parameter. Instead, everything after the `exec` command is treated as command-line to execute. Let's reuse the arg/arg_len fields of the todo_item structure (which hold the oneline for pick/edit commands) to point to the command-line. Signed-off-by: Johannes Schindelin --- sequencer.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/sequencer.c b/sequencer.c index 36a4c6b20b..166444c1f1 100644 --- a/sequencer.c +++ b/sequencer.c @@ -17,6 +17,7 @@ #include "argv-array.h" #include "quote.h" #include "log-tree.h" +#include "wt-status.h" #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@ -667,6 +668,8 @@ enum todo_command { TODO_PICK = 0, TODO_REVERT, TODO_EDIT, + /* commands that do something else than handling a single commit */ + TODO_EXEC, /* commands that do nothing but are counted for reporting progress */ TODO_NOOP }; @@ -675,6 +678,7 @@ static const char *todo_command_strings[] = { "pick", "revert", "edit", + "exec", "noop" }; @@ -969,6 +973,12 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) return -1; bol += padding; + if (item->command == TODO_EXEC) { + item->arg = bol; + item->arg_len = (int)(eol - bol); + return 0; + } + end_of_object_name = (char *) bol + strcspn(bol, " \t\n"); saved = *end_of_object_name; *end_of_object_name = '\0'; @@ -1383,6 +1393,47 @@ static int error_with_patch(struct commit *commit, return exit_code; } +static int do_exec(const char *command_line) +{ + const char *child_argv[] = { NULL, NULL }; + int dirty, status; + + fprintf(stderr, "Executing: %s\n", command_line); + child_argv[0] = command_line; + status = run_command_v_opt(child_argv, RUN_USING_SHELL); + + /* force re-reading of the cache */ + if (discard_cache() < 0 || read_cache() < 0) + return error(_("Could not read index")); + + dirty = require_clean_work_tree("rebase", NULL, 1, 1); + + if (status) { + warning("Execution failed: %s\n%s" + "You can fix the problem, and then run\n" + "\n" + " git rebase --continue\n" + "\n", + command_line, + dirty ? "and made changes to the index and/or the " + "working tree\n" : ""); + if (status == 127) + /* command not found */ + status = 1; + } + else if (dirty) { + warning("Execution succeeded: %s\nbut " + "left changes to the index and/or the working tree\n" + "Commit or stash your changes, and then run\n" + "\n" + " git rebase --continue\n" + "\n", command_line); + status = 1; + } + + return status; +} + static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) { int res = 0; @@ -1412,6 +1463,14 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) !res); } } + else if (item->command == TODO_EXEC) { + char *end_of_arg = (char *)(item->arg + item->arg_len); + int saved = *end_of_arg; + + *end_of_arg = '\0'; + res = do_exec(item->arg); + *end_of_arg = saved; + } else if (item->command != TODO_NOOP) return error("Unknown command %d", item->command); From 80f7817c0c8434616fd73dba17e1449051bd6795 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 12 Apr 2016 15:30:26 +0200 Subject: [PATCH 52/92] sequencer (rebase -i): learn about the 'verbose' mode When calling `git rebase -i -v`, the user wants to see some statistics after the commits were rebased. Let's show some. The strbuf we use to perform that task will be used for other things in subsequent commits, hence it is declared and initialized in a wider scope than strictly needed here. Signed-off-by: Johannes Schindelin --- sequencer.c | 22 ++++++++++++++++++++++ sequencer.h | 1 + 2 files changed, 23 insertions(+) diff --git a/sequencer.c b/sequencer.c index 166444c1f1..d0578bbbeb 100644 --- a/sequencer.c +++ b/sequencer.c @@ -63,6 +63,8 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha") * command-line (and are only consumed, not modified, by the sequencer). */ static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt") +static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head") +static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose") /* We will introduce the 'interactive rebase' mode later */ static inline int is_rebase_i(const struct replay_opts *opts) @@ -1110,6 +1112,9 @@ static int read_populate_opts(struct replay_opts *opts) } } + if (file_exists(rebase_path_verbose())) + opts->verbose = 1; + return 0; } @@ -1480,9 +1485,26 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) } if (is_rebase_i(opts)) { + struct strbuf buf = STRBUF_INIT; + /* Stopped in the middle, as planned? */ if (todo_list->current < todo_list->nr) return 0; + + if (opts->verbose) { + const char *argv[] = { + "diff-tree", "--stat", NULL, NULL + }; + + if (!read_oneliner(&buf, rebase_path_orig_head(), 0)) + return error("Could not read %s", + rebase_path_orig_head()); + strbuf_addstr(&buf, "..HEAD"); + argv[2] = buf.buf; + run_command_v_opt(argv, RUN_GIT_CMD); + strbuf_reset(&buf); + } + strbuf_release(&buf); } /* diff --git a/sequencer.h b/sequencer.h index edd7d4a8d1..fd2a719e36 100644 --- a/sequencer.h +++ b/sequencer.h @@ -24,6 +24,7 @@ struct replay_opts { int allow_empty; int allow_empty_message; int keep_redundant_commits; + int verbose; int mainline; From 0cc1904d16847382ebce383387bc6079dd8f8b10 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 4 Mar 2016 16:18:50 +0100 Subject: [PATCH 53/92] sequencer (rebase -i): write the 'done' file In the interactive rebase, commands that were successfully processed are not simply discarded, but appended to the 'done' file instead. This is used e.g. to display the current state to the user in the output of `git status` or the progress. Signed-off-by: Johannes Schindelin --- sequencer.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/sequencer.c b/sequencer.c index d0578bbbeb..b49d087191 100644 --- a/sequencer.c +++ b/sequencer.c @@ -38,6 +38,12 @@ static GIT_PATH_FUNC(rebase_path, "rebase-merge") * file and written to the tail of 'done'. */ static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo") +/* + * The rebase command lines that have already been processed. A line + * is moved here when it is first handled, before any associated user + * actions. + */ +static GIT_PATH_FUNC(rebase_path_done, "rebase-merge/done") /* * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and * GIT_AUTHOR_DATE that will be used for the commit that is currently @@ -1284,6 +1290,20 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts) return error_errno(_("Could not write to '%s'"), todo_path); if (commit_lock_file(&todo_lock) < 0) return error(_("Error wrapping up '%s'."), todo_path); + + if (is_rebase_i(opts)) { + const char *done_path = rebase_path_done(); + int fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666); + int prev_offset = !next ? 0 : + todo_list->items[next - 1].offset_in_buf; + + if (offset > prev_offset && write_in_full(fd, + todo_list->buf.buf + prev_offset, + offset - prev_offset) < 0) + return error_errno(_("Could not write to '%s'"), + done_path); + close(fd); + } return 0; } From b2c7c683f28682501fabdbcbddd39fedd2970a43 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 23 May 2016 16:49:51 +0200 Subject: [PATCH 54/92] sequencer (rebase -i): add support for the 'fixup' and 'squash' commands This is a huge patch, and at the same time a huge step forward to execute the performance-critical parts of the interactive rebase in a builtin command. Since 'fixup' and 'squash' are not only similar, but also need to know about each other (we want to reduce a series of fixups/squashes into a single, final commit message edit, from the user's point of view), we really have to implement them both at the same time. Most of the actual work is done by the existing code path that already handles the "pick" and the "edit" commands; We added support for other features (e.g. to amend the commit message) in the patches leading up to this one, yet there are still quite a few bits in this patch that simply would not make sense as individual patches (such as: determining whether there was anything to "fix up" in the "todo" script, etc). In theory, it would be possible to reuse the fast-forward code path also for the fixup and the squash code paths, but in practice this would make the code less readable. The end result cannot be fast-forwarded anyway, therefore let's just extend the cherry-picking code path for now. Since the sequencer parses the entire `git-rebase-todo` script in one go, fixup or squash commands without a preceding pick can be reported early (in git-rebase--interactive, we could only report such errors just before executing the fixup/squash). Signed-off-by: Johannes Schindelin --- sequencer.c | 229 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 218 insertions(+), 11 deletions(-) diff --git a/sequencer.c b/sequencer.c index b49d087191..f6bc4318e1 100644 --- a/sequencer.c +++ b/sequencer.c @@ -44,6 +44,35 @@ static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo") * actions. */ static GIT_PATH_FUNC(rebase_path_done, "rebase-merge/done") +/* + * The commit message that is planned to be used for any changes that + * need to be committed following a user interaction. + */ +static GIT_PATH_FUNC(rebase_path_message, "rebase-merge/message") +/* + * The file into which is accumulated the suggested commit message for + * squash/fixup commands. When the first of a series of squash/fixups + * is seen, the file is created and the commit message from the + * previous commit and from the first squash/fixup commit are written + * to it. The commit message for each subsequent squash/fixup commit + * is appended to the file as it is processed. + * + * The first line of the file is of the form + * # This is a combination of $count commits. + * where $count is the number of commits whose messages have been + * written to the file so far (including the initial "pick" commit). + * Each time that a commit message is processed, this line is read and + * updated. It is deleted just before the combined commit is made. + */ +static GIT_PATH_FUNC(rebase_path_squash_msg, "rebase-merge/message-squash") +/* + * If the current series of squash/fixups has not yet included a squash + * command, then this file exists and holds the commit message of the + * original "pick" commit. (If the series ends without a "squash" + * command, then this can be used as the commit message of the combined + * commit without opening the editor.) + */ +static GIT_PATH_FUNC(rebase_path_fixup_msg, "rebase-merge/message-fixup") /* * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and * GIT_AUTHOR_DATE that will be used for the commit that is currently @@ -676,6 +705,8 @@ enum todo_command { TODO_PICK = 0, TODO_REVERT, TODO_EDIT, + TODO_FIXUP, + TODO_SQUASH, /* commands that do something else than handling a single commit */ TODO_EXEC, /* commands that do nothing but are counted for reporting progress */ @@ -686,6 +717,8 @@ static const char *todo_command_strings[] = { "pick", "revert", "edit", + "fixup", + "squash", "exec", "noop" }; @@ -697,16 +730,116 @@ static const char *command_to_string(const enum todo_command command) die("Unknown command: %d", command); } +static int is_fixup(enum todo_command command) +{ + return command == TODO_FIXUP || command == TODO_SQUASH; +} + +static int update_squash_messages(enum todo_command command, + struct commit *commit, struct replay_opts *opts) +{ + struct strbuf buf = STRBUF_INIT; + int count; + const char *message, *body; + + if (file_exists(rebase_path_squash_msg())) { + char *p, *p2; + + if (strbuf_read_file(&buf, rebase_path_squash_msg(), 2048) <= 0) + return error(_("Could not read '%s'"), + rebase_path_squash_msg()); + + if (buf.buf[0] == '\n' || !skip_prefix(buf.buf + 1, + " This is a combination of ", + (const char **)&p)) + return error(_("Unexpected 1st line of squash message:" + "\n\n\t%.*s"), + (int)(strchrnul(buf.buf, '\n') - buf.buf), + buf.buf); + count = strtol(p, &p2, 10); + + if (count < 1 || *p2 != ' ') + return error(_("Invalid 1st line of squash message:\n" + "\n\t%.*s"), + (int)(strchrnul(buf.buf, '\n') - buf.buf), + buf.buf); + + sprintf((char *)p, "%d", ++count); + if (!*p2) + *p2 = ' '; + else { + *(++p2) = 'c'; + strbuf_insert(&buf, p2 - buf.buf, " ", 1); + } + } + else { + unsigned char head[20]; + struct commit *head_commit; + const char *head_message, *body; + + if (get_sha1("HEAD", head)) + return error(_("Need a HEAD to fixup")); + if (!(head_commit = lookup_commit_reference(head))) + return error(_("Could not read HEAD")); + if (!(head_message = get_commit_buffer(head_commit, NULL))) + return error(_("Could not read HEAD's commit message")); + + body = strstr(head_message, "\n\n"); + if (!body) + body = ""; + else + body = skip_blank_lines(body + 2); + if (write_file_gently(rebase_path_fixup_msg(), body, 0)) + return error(_("Cannot write '%s'"), + rebase_path_fixup_msg()); + + count = 2; + strbuf_addf(&buf, _("%c This is a combination of 2 commits.\n" + "%c The first commit's message is:\n\n%s"), + comment_line_char, comment_line_char, body); + + unuse_commit_buffer(head_commit, head_message); + } + + if (!(message = get_commit_buffer(commit, NULL))) + return error(_("Could not read commit message of %s"), + oid_to_hex(&commit->object.oid)); + body = strstr(message, "\n\n"); + if (!body) + body = ""; + else + body = skip_blank_lines(body + 2); + + if (command == TODO_SQUASH) { + unlink(rebase_path_fixup_msg()); + strbuf_addf(&buf, _("\n%c This is the commit message #%d:\n" + "\n%s"), + comment_line_char, count, body); + } + else if (command == TODO_FIXUP) { + strbuf_addf(&buf, _("\n%c The commit message #%d " + "will be skipped:\n\n"), + comment_line_char, count); + strbuf_add_commented_lines(&buf, body, strlen(body)); + } + else + return error(_("Unknown command: %d"), command); + unuse_commit_buffer(commit, message); + + return write_message(&buf, rebase_path_squash_msg()); +} static int do_pick_commit(enum todo_command command, struct commit *commit, - struct replay_opts *opts) + struct replay_opts *opts, int final_fixup) { + int edit = opts->edit, cleanup_commit_message = 0; + const char *msg_file = edit ? NULL : git_path_merge_msg(); unsigned char head[20]; struct commit *base, *next, *parent; const char *base_label, *next_label; struct commit_message msg = { NULL, NULL, NULL, NULL }; struct strbuf msgbuf = STRBUF_INIT; - int res = 0, unborn = 0, allow; + int res = 0, unborn = 0, amend = 0, allow; if (opts->no_commit) { /* @@ -752,7 +885,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, else parent = commit->parents->item; - if (opts->allow_ff && + if (opts->allow_ff && !is_fixup(command) && ((parent && !hashcmp(parent->object.oid.hash, head)) || (!parent && unborn))) return fast_forward_to(commit->object.oid.hash, head, unborn, opts); @@ -814,6 +947,28 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } } + if (is_fixup(command)) { + if (update_squash_messages(command, commit, opts)) + return -1; + amend = 1; + if (!final_fixup) + msg_file = rebase_path_squash_msg(); + else if (file_exists(rebase_path_fixup_msg())) { + cleanup_commit_message = 1; + msg_file = rebase_path_fixup_msg(); + } + else { + const char *dest = git_path("SQUASH_MSG"); + unlink(dest); + if (copy_file(dest, rebase_path_squash_msg(), 0666)) + return error("Could not rename %s to " + "%s", rebase_path_squash_msg(), dest); + unlink(git_path("MERGE_MSG")); + msg_file = dest; + edit = 1; + } + } + if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) { res |= do_recursive_merge(base, next, base_label, next_label, head, &msgbuf, opts); @@ -865,9 +1020,13 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, goto leave; } if (!opts->no_commit) - res |= sequencer_commit(opts->edit ? - NULL : git_path_merge_msg(), - opts, allow, opts->edit, 0, 0); + res |= sequencer_commit(msg_file, opts, allow, edit, amend, + cleanup_commit_message); + + if (!res && final_fixup) { + unlink(rebase_path_fixup_msg()); + unlink(rebase_path_squash_msg()); + } leave: free_message(commit, &msg); @@ -1007,7 +1166,7 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list) { struct todo_item *item; char *p = buf; - int i, res = 0; + int i, res = 0, fixup_okay = file_exists(rebase_path_done()); for (i = 1; *p; i++) { char *eol = strchrnul(p, '\n'); @@ -1017,8 +1176,15 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list) if (parse_insn_line(item, p, eol)) { res |= error(_("Invalid line %d: %.*s"), i, (int)(eol - p), p); - item->command = -1; + item->command = TODO_NOOP; } + if (fixup_okay) + ; /* do nothing */ + else if (is_fixup(item->command)) + return error("Cannot '%s' without a previous commit", + command_to_string(item->command)); + else if (item->command != TODO_NOOP) + fixup_okay = 1; p = *eol ? eol + 1 : eol; } if (!todo_list->nr) @@ -1418,6 +1584,20 @@ static int error_with_patch(struct commit *commit, return exit_code; } +static int error_failed_squash(struct commit *commit, + struct replay_opts *opts, int subject_len, const char *subject) +{ + if (rename(rebase_path_squash_msg(), rebase_path_message())) + return error("Could not rename %s to %s", + rebase_path_squash_msg(), rebase_path_message()); + unlink(rebase_path_fixup_msg()); + unlink(git_path("MERGE_MSG")); + if (copy_file(git_path("MERGE_MSG"), rebase_path_message(), 0666)) + return error("Could not copy %s to %s", rebase_path_message(), + git_path("MERGE_MSG")); + return error_with_patch(commit, subject, subject_len, opts, 1, 0); +} + static int do_exec(const char *command_line) { const char *child_argv[] = { NULL, NULL }; @@ -1459,6 +1639,21 @@ static int do_exec(const char *command_line) return status; } +static int is_final_fixup(struct todo_list *todo_list) +{ + int i = todo_list->current; + + if (!is_fixup(todo_list->items[i].command)) + return 0; + + while (++i < todo_list->nr) + if (is_fixup(todo_list->items[i].command)) + return 0; + else if (todo_list->items[i].command < TODO_NOOP) + break; + return 1; +} + static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) { int res = 0; @@ -1474,9 +1669,15 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) struct todo_item *item = todo_list->items + todo_list->current; if (save_todo(todo_list, opts)) return -1; - if (item->command <= TODO_EDIT) { + if (is_rebase_i(opts)) { + unlink(rebase_path_message()); + unlink(rebase_path_author_script()); + unlink(rebase_path_stopped_sha()); + unlink(rebase_path_amend()); + } + if (item->command <= TODO_SQUASH) { res = do_pick_commit(item->command, item->commit, - opts); + opts, is_final_fixup(todo_list)); if (item->command == TODO_EDIT) { struct commit *commit = item->commit; if (!res) @@ -1487,6 +1688,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) item->arg, item->arg_len, opts, res, !res); } + if (res && is_fixup(item->command)) { + if (res == 1) + intend_to_amend(); + return error_failed_squash(item->commit, opts, + item->arg_len, item->arg); + } } else if (item->command == TODO_EXEC) { char *end_of_arg = (char *)(item->arg + item->arg_len); @@ -1581,7 +1788,7 @@ static int single_pick(struct commit *cmit, struct replay_opts *opts) { setenv(GIT_REFLOG_ACTION, action_name(opts), 0); return do_pick_commit(opts->action == REPLAY_PICK ? - TODO_PICK : TODO_REVERT, cmit, opts); + TODO_PICK : TODO_REVERT, cmit, opts, 0); } int sequencer_pick_revisions(struct replay_opts *opts) From d036424d43a3e10aa1a329efe2daae2d26e42749 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 14:08:09 +0200 Subject: [PATCH 55/92] sequencer (rebase -i): implement the short commands For users' convenience, most rebase commands can be abbreviated, e.g. 'p' instead of 'pick' and 'x' instead of 'exec'. Let's teach the sequencer to handle those abbreviated commands just fine. Signed-off-by: Johannes Schindelin --- sequencer.c | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/sequencer.c b/sequencer.c index f6bc4318e1..e0c63187e7 100644 --- a/sequencer.c +++ b/sequencer.c @@ -713,20 +713,23 @@ enum todo_command { TODO_NOOP }; -static const char *todo_command_strings[] = { - "pick", - "revert", - "edit", - "fixup", - "squash", - "exec", - "noop" +static struct { + char c; + const char *str; +} todo_command_info[] = { + { 'p', "pick" }, + { 0, "revert" }, + { 'e', "edit" }, + { 'f', "fixup" }, + { 's', "squash" }, + { 'x', "exec" }, + { 0, "noop" } }; static const char *command_to_string(const enum todo_command command) { - if (command < ARRAY_SIZE(todo_command_strings)) - return todo_command_strings[command]; + if (command < ARRAY_SIZE(todo_command_info)) + return todo_command_info[command].str; die("Unknown command: %d", command); } @@ -1119,12 +1122,17 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) return 0; } - for (i = 0; i < ARRAY_SIZE(todo_command_strings); i++) - if (skip_prefix(bol, todo_command_strings[i], &bol)) { + for (i = 0; i < ARRAY_SIZE(todo_command_info); i++) + if (skip_prefix(bol, todo_command_info[i].str, &bol)) { item->command = i; break; } - if (i >= ARRAY_SIZE(todo_command_strings)) + else if (bol[1] == ' ' && *bol == todo_command_info[i].c) { + bol++; + item->command = i; + break; + } + if (i >= ARRAY_SIZE(todo_command_info)) return -1; if (item->command == TODO_NOOP) { @@ -1309,7 +1317,7 @@ static int walk_revs_populate_todo(struct todo_list *todo_list, { enum todo_command command = opts->action == REPLAY_PICK ? TODO_PICK : TODO_REVERT; - const char *command_string = todo_command_strings[command]; + const char *command_string = todo_command_info[command].str; struct commit *commit; if (prepare_revs(opts)) From dbe70b6b66347b2ea3740edb571da57acce36a5a Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 14:11:12 +0200 Subject: [PATCH 56/92] sequencer (rebase -i): write an author-script file When the interactive rebase aborts, it writes out an author-script file to record the author information for the current commit. As we are about to teach the sequencer how to perform the actions behind an interactive rebase, it needs to write those author-script files, too. Signed-off-by: Johannes Schindelin --- sequencer.c | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/sequencer.c b/sequencer.c index e0c63187e7..5485c131cf 100644 --- a/sequencer.c +++ b/sequencer.c @@ -528,6 +528,51 @@ static int is_index_unchanged(void) return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.oid.hash); } +static int write_author_script(const char *message) +{ + struct strbuf buf = STRBUF_INIT; + const char *eol; + + for (;;) + if (!*message || starts_with(message, "\n")) { +missing_author: + /* Missing 'author' line? */ + unlink(rebase_path_author_script()); + return 0; + } + else if (skip_prefix(message, "author ", &message)) + break; + else if ((eol = strchr(message, '\n'))) + message = eol + 1; + else + goto missing_author; + + strbuf_addstr(&buf, "GIT_AUTHOR_NAME='"); + while (*message && *message != '\n' && *message != '\r') + if (skip_prefix(message, " <", &message)) + break; + else if (*message != '\'') + strbuf_addch(&buf, *(message++)); + else + strbuf_addf(&buf, "'\\\\%c'", *(message++)); + strbuf_addstr(&buf, "'\nGIT_AUTHOR_EMAIL='"); + while (*message && *message != '\n' && *message != '\r') + if (skip_prefix(message, "> ", &message)) + break; + else if (*message != '\'') + strbuf_addch(&buf, *(message++)); + else + strbuf_addf(&buf, "'\\\\%c'", *(message++)); + strbuf_addstr(&buf, "'\nGIT_AUTHOR_DATE='@"); + while (*message && *message != '\n' && *message != '\r') + if (*message != '\'') + strbuf_addch(&buf, *(message++)); + else + strbuf_addf(&buf, "'\\\\%c'", *(message++)); + strbuf_addstr(&buf, "'\n"); + return write_message(&buf, rebase_path_author_script()); +} + static char **read_author_script(void) { struct strbuf script = STRBUF_INIT; @@ -972,7 +1017,9 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } } - if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) { + if (is_rebase_i(opts) && write_author_script(msg.message) < 0) + res |= -1; + else if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) { res |= do_recursive_merge(base, next, base_label, next_label, head, &msgbuf, opts); if (res < 0) From a3e5dbd9847f3cb2d396140df87fb146d022c7e7 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 9 Mar 2016 17:12:20 +0100 Subject: [PATCH 57/92] sequencer (rebase -i): allow continuing with staged changes When an interactive rebase is interrupted, the user may stage changes before continuing, and we need to commit those changes in that case. Signed-off-by: Johannes Schindelin --- sequencer.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/sequencer.c b/sequencer.c index 5485c131cf..2f0abe2e6b 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1806,6 +1806,41 @@ static int continue_single_pick(void) return run_command_v_opt(argv, RUN_GIT_CMD); } +static int commit_staged_changes(struct replay_opts *opts) +{ + int amend = 0; + + if (has_unstaged_changes(1)) + return error(_("Cannot rebase: You have unstaged changes.")); + if (!has_uncommitted_changes(0)) + return 0; + + if (file_exists(rebase_path_amend())) { + struct strbuf rev = STRBUF_INIT; + unsigned char head[20], to_amend[20]; + + if (get_sha1("HEAD", head)) + return error("Cannot amend non-existing commit"); + if (!read_oneliner(&rev, rebase_path_amend(), 0)) + return error("Invalid file: %s", rebase_path_amend()); + if (get_sha1_hex(rev.buf, to_amend)) + return error("Invalid contents: %s", + rebase_path_amend()); + if (hashcmp(head, to_amend)) + return error("\nYou have uncommitted changes in your " + "working tree. Please, commit them\nfirst and " + "then run 'git rebase --continue' again."); + + strbuf_release(&rev); + amend = 1; + } + + if (sequencer_commit(rebase_path_message(), opts, 1, 1, amend, 0)) + return error("Could not commit staged changes."); + unlink(rebase_path_amend()); + return 0; +} + int sequencer_continue(struct replay_opts *opts) { struct todo_list todo_list = TODO_LIST_INIT; @@ -1814,6 +1849,10 @@ int sequencer_continue(struct replay_opts *opts) if (read_and_refresh_cache(opts)) return -1; + if (is_rebase_i(opts)) { + if (commit_staged_changes(opts)) + return -1; + } if (!file_exists(get_todo_path(opts))) return continue_single_pick(); if (read_populate_opts(opts)) From 7569d33f5e9b2c946d7a23711608969b7a25853e Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 9 Apr 2016 16:29:21 +0200 Subject: [PATCH 58/92] sequencer (rebase -i): remove CHERRY_PICK_HEAD when no longer needed The scripted version of the interactive rebase already does that. Signed-off-by: Johannes Schindelin --- sequencer.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sequencer.c b/sequencer.c index 2f0abe2e6b..e49e83d118 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1812,8 +1812,13 @@ static int commit_staged_changes(struct replay_opts *opts) if (has_unstaged_changes(1)) return error(_("Cannot rebase: You have unstaged changes.")); - if (!has_uncommitted_changes(0)) + if (!has_uncommitted_changes(0)) { + const char *cherry_pick_head = git_path("CHERRY_PICK_HEAD"); + + if (file_exists(cherry_pick_head) && unlink(cherry_pick_head)) + return error("Could not remove CHERRY_PICK_HEAD"); return 0; + } if (file_exists(rebase_path_amend())) { struct strbuf rev = STRBUF_INIT; From 7fdb7caa8636f1461e931a9fbcc5531749e1bda2 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 31 Aug 2016 08:43:51 +0200 Subject: [PATCH 59/92] sequencer (rebase -i): skip some revert/cherry-pick specific code path When a cherry-pick continues without a "todo script", the intention is simply to pick a single commit. However, when an interactive rebase is continued without a "todo script", it means that the last command has been completed and that we now need to clean up. This commit guards the revert/cherry-pick specific steps so that they are not executed in rebase -i mode. Signed-off-by: Johannes Schindelin --- sequencer.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/sequencer.c b/sequencer.c index e49e83d118..59819dc95b 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1858,25 +1858,28 @@ int sequencer_continue(struct replay_opts *opts) if (commit_staged_changes(opts)) return -1; } - if (!file_exists(get_todo_path(opts))) + else if (!file_exists(get_todo_path(opts))) return continue_single_pick(); if (read_populate_opts(opts)) return -1; if ((res = read_populate_todo(&todo_list, opts))) goto release_todo_list; - /* Verify that the conflict has been resolved */ - if (file_exists(git_path_cherry_pick_head()) || - file_exists(git_path_revert_head())) { - res = continue_single_pick(); - if (res) + if (!is_rebase_i(opts)) { + /* Verify that the conflict has been resolved */ + if (file_exists(git_path_cherry_pick_head()) || + file_exists(git_path_revert_head())) { + res = continue_single_pick(); + if (res) + goto release_todo_list; + } + if (index_differs_from("HEAD", 0)) { + res = error_dirty_index(opts); goto release_todo_list; + } + todo_list.current++; } - if (index_differs_from("HEAD", 0)) { - res = error_dirty_index(opts); - goto release_todo_list; - } - todo_list.current++; + res = pick_commits(&todo_list, opts); release_todo_list: todo_list_release(&todo_list); From 3c78acbef798052be7f75865e8c4ad9fe5187e51 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 14:11:54 +0200 Subject: [PATCH 60/92] sequencer (rebase -i): the todo can be empty when continuing When the last command of an interactive rebase fails, the user needs to resolve the problem and then continue the interactive rebase. Naturally, the todo script is empty by then. So let's not complain about that! To that end, let's move that test out of the function that parses the todo script, and into the more high-level function read_populate_todo(). This is also necessary by now because the lower-level parse_insn_buffer() has no idea whether we are performing an interactive rebase or not. Signed-off-by: Johannes Schindelin --- sequencer.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sequencer.c b/sequencer.c index 59819dc95b..c301253e9b 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1242,8 +1242,7 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list) fixup_okay = 1; p = *eol ? eol + 1 : eol; } - if (!todo_list->nr) - return error(_("No commits parsed.")); + return res; } @@ -1267,6 +1266,10 @@ static int read_populate_todo(struct todo_list *todo_list, if (res) return error(_("Unusable instruction sheet: '%s'"), todo_file); + if (!todo_list->nr && + (!is_rebase_i(opts) || !file_exists(rebase_path_done()))) + return error(_("No commits parsed.")); + if (!is_rebase_i(opts)) { enum todo_command valid = opts->action == REPLAY_PICK ? TODO_PICK : TODO_REVERT; From 84e2e26368d6a37e3eba29ba950b3e987a43d28a Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 14:12:54 +0200 Subject: [PATCH 61/92] sequencer (rebase -i): update refs after a successful rebase An interactive rebase operates on a detached HEAD (to keep the reflog of the original branch relatively clean), and updates the branch only at the end. Now that the sequencer learns to perform interactive rebases, it also needs to learn the trick to update the branch before removing the directory containing the state of the interactive rebase. Signed-off-by: Johannes Schindelin --- sequencer.c | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/sequencer.c b/sequencer.c index c301253e9b..b9b952f2e1 100644 --- a/sequencer.c +++ b/sequencer.c @@ -100,6 +100,8 @@ static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha") static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt") static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head") static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose") +static GIT_PATH_FUNC(rebase_path_head_name, "rebase-merge/head-name") +static GIT_PATH_FUNC(rebase_path_onto, "rebase-merge/onto") /* We will introduce the 'interactive rebase' mode later */ static inline int is_rebase_i(const struct replay_opts *opts) @@ -1770,12 +1772,39 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) } if (is_rebase_i(opts)) { - struct strbuf buf = STRBUF_INIT; + struct strbuf head_ref = STRBUF_INIT, buf = STRBUF_INIT; /* Stopped in the middle, as planned? */ if (todo_list->current < todo_list->nr) return 0; + if (read_oneliner(&head_ref, rebase_path_head_name(), 0) && + starts_with(head_ref.buf, "refs/")) { + unsigned char head[20], orig[20]; + + if (get_sha1("HEAD", head)) + return error("Cannot read HEAD"); + if (!read_oneliner(&buf, rebase_path_orig_head(), 0) || + get_sha1_hex(buf.buf, orig)) + return error("Could not read orig-head"); + strbuf_addf(&buf, "rebase -i (finish): %s onto ", + head_ref.buf); + if (!read_oneliner(&buf, rebase_path_onto(), 0)) + return error("Could not read 'onto'"); + if (update_ref(buf.buf, head_ref.buf, head, orig, + REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) + return error("Could not update %s", + head_ref.buf); + strbuf_reset(&buf); + strbuf_addf(&buf, + "rebase -i (finish): returning to %s", + head_ref.buf); + if (create_symref("HEAD", head_ref.buf, buf.buf)) + return error("Could not update HEAD to %s", + head_ref.buf); + strbuf_reset(&buf); + } + if (opts->verbose) { const char *argv[] = { "diff-tree", "--stat", NULL, NULL @@ -1790,6 +1819,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) strbuf_reset(&buf); } strbuf_release(&buf); + strbuf_release(&head_ref); } /* From 71e3fd0c3c4d0837ee694de8e9e46812bbf40534 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 10 Mar 2016 21:47:32 +0100 Subject: [PATCH 62/92] sequencer (rebase -i): leave a patch upon error When doing an interactive rebase, we want to leave a 'patch' file for further inspection by the user (even if we never tried to actually apply that patch, since we're cherry-picking instead). Signed-off-by: Johannes Schindelin --- sequencer.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sequencer.c b/sequencer.c index b9b952f2e1..cf49e5d6cc 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1754,6 +1754,9 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) return error_failed_squash(item->commit, opts, item->arg_len, item->arg); } + else if (res && is_rebase_i(opts)) + return res | error_with_patch(item->commit, + item->arg, item->arg_len, opts, res, 0); } else if (item->command == TODO_EXEC) { char *end_of_arg = (char *)(item->arg + item->arg_len); From c2a4b08c46d9e4c4c591cc0abac67cc6f30a9993 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 14:13:31 +0200 Subject: [PATCH 63/92] sequencer (rebase -i): implement the 'reword' command This is now trivial, as all the building blocks are in place: all we need to do is to flip the "edit" switch when committing. Signed-off-by: Johannes Schindelin --- sequencer.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sequencer.c b/sequencer.c index cf49e5d6cc..8f0a8cea42 100644 --- a/sequencer.c +++ b/sequencer.c @@ -752,6 +752,7 @@ enum todo_command { TODO_PICK = 0, TODO_REVERT, TODO_EDIT, + TODO_REWORD, TODO_FIXUP, TODO_SQUASH, /* commands that do something else than handling a single commit */ @@ -767,6 +768,7 @@ static struct { { 'p', "pick" }, { 0, "revert" }, { 'e', "edit" }, + { 'r', "reword" }, { 'f', "fixup" }, { 's', "squash" }, { 'x', "exec" }, @@ -997,7 +999,9 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } } - if (is_fixup(command)) { + if (command == TODO_REWORD) + edit = 1; + else if (is_fixup(command)) { if (update_squash_messages(command, commit, opts)) return -1; amend = 1; @@ -1756,7 +1760,8 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) } else if (res && is_rebase_i(opts)) return res | error_with_patch(item->commit, - item->arg, item->arg_len, opts, res, 0); + item->arg, item->arg_len, opts, res, + item->command == TODO_REWORD); } else if (item->command == TODO_EXEC) { char *end_of_arg = (char *)(item->arg + item->arg_len); From dd344bd5f6c34555b688630779788982d1e47374 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 23 May 2016 16:56:21 +0200 Subject: [PATCH 64/92] sequencer (rebase -i): allow fast-forwarding for edit/reword The sequencer already knew how to fast-forward instead of cherry-picking, if possible. We want to continue to do this, of course, but in case of the 'reword' command, we will need to call `git commit` after fast-forwarding. Signed-off-by: Johannes Schindelin --- sequencer.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/sequencer.c b/sequencer.c index 8f0a8cea42..be051764ff 100644 --- a/sequencer.c +++ b/sequencer.c @@ -891,7 +891,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, const char *base_label, *next_label; struct commit_message msg = { NULL, NULL, NULL, NULL }; struct strbuf msgbuf = STRBUF_INIT; - int res = 0, unborn = 0, amend = 0, allow; + int res = 0, unborn = 0, amend = 0, allow = 0; if (opts->no_commit) { /* @@ -937,20 +937,28 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, else parent = commit->parents->item; + if (get_message(commit, &msg) != 0) + return error(_("Cannot get commit message for %s"), + oid_to_hex(&commit->object.oid)); + if (opts->allow_ff && !is_fixup(command) && ((parent && !hashcmp(parent->object.oid.hash, head)) || - (!parent && unborn))) - return fast_forward_to(commit->object.oid.hash, head, unborn, opts); - + (!parent && unborn))) { + if (is_rebase_i(opts)) + write_author_script(msg.message); + res |= fast_forward_to(commit->object.oid.hash, head, unborn, + opts); + if (res || command != TODO_REWORD) + goto leave; + edit = amend = 1; + msg_file = NULL; + goto fast_forward_edit; + } if (parent && parse_commit(parent) < 0) return error(_("%s: cannot parse parent commit %s"), command_to_string(command), oid_to_hex(&parent->object.oid)); - if (get_message(commit, &msg) != 0) - return error(_("Cannot get commit message for %s"), - oid_to_hex(&commit->object.oid)); - /* * "commit" is an existing commit. We would want to apply * the difference it introduces since its first parent "prev" @@ -1076,6 +1084,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, goto leave; } if (!opts->no_commit) +fast_forward_edit: res |= sequencer_commit(msg_file, opts, allow, edit, amend, cleanup_commit_message); From 10ab63008dc5e227667822127b147a220efc8958 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 9 Apr 2016 16:27:51 +0200 Subject: [PATCH 65/92] sequencer (rebase -i): refactor setting the reflog message This makes the code DRYer, with the obvious benefit that we can enhance the code further in a single place. We can also reuse the functionality elsewhere by calling this new function. Signed-off-by: Johannes Schindelin --- sequencer.c | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/sequencer.c b/sequencer.c index be051764ff..7354ca3e6f 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1727,6 +1727,26 @@ static int is_final_fixup(struct todo_list *todo_list) return 1; } +static const char *reflog_message(struct replay_opts *opts, + const char *sub_action, const char *fmt, ...) +{ + va_list ap; + static struct strbuf buf = STRBUF_INIT; + + va_start(ap, fmt); + strbuf_reset(&buf); + strbuf_addstr(&buf, action_name(opts)); + if (sub_action) + strbuf_addf(&buf, " (%s)", sub_action); + if (fmt) { + strbuf_addstr(&buf, ": "); + strbuf_vaddf(&buf, fmt, ap); + } + va_end(ap); + + return buf.buf; +} + static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) { int res = 0; @@ -1797,6 +1817,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) if (read_oneliner(&head_ref, rebase_path_head_name(), 0) && starts_with(head_ref.buf, "refs/")) { + const char *msg; unsigned char head[20], orig[20]; if (get_sha1("HEAD", head)) @@ -1804,19 +1825,17 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) if (!read_oneliner(&buf, rebase_path_orig_head(), 0) || get_sha1_hex(buf.buf, orig)) return error("Could not read orig-head"); - strbuf_addf(&buf, "rebase -i (finish): %s onto ", - head_ref.buf); if (!read_oneliner(&buf, rebase_path_onto(), 0)) return error("Could not read 'onto'"); - if (update_ref(buf.buf, head_ref.buf, head, orig, + msg = reflog_message(opts, "finish", "%s onto %s", + head_ref.buf, buf.buf); + if (update_ref(msg, head_ref.buf, head, orig, REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) return error("Could not update %s", head_ref.buf); - strbuf_reset(&buf); - strbuf_addf(&buf, - "rebase -i (finish): returning to %s", + msg = reflog_message(opts, "finish", "returning to %s", head_ref.buf); - if (create_symref("HEAD", head_ref.buf, buf.buf)) + if (create_symref("HEAD", head_ref.buf, msg)) return error("Could not update HEAD to %s", head_ref.buf); strbuf_reset(&buf); From bdcfb7e710422aaff3a9e985731dbb29a76f8185 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 9 Apr 2016 16:28:07 +0200 Subject: [PATCH 66/92] sequencer (rebase -i): set the reflog message consistently We already used the same reflog message as the scripted version of rebase -i when finishing. With this commit, we do that also for all the commands before that. Signed-off-by: Johannes Schindelin --- sequencer.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sequencer.c b/sequencer.c index 7354ca3e6f..d3e0b48bae 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1769,6 +1769,10 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) unlink(rebase_path_amend()); } if (item->command <= TODO_SQUASH) { + if (is_rebase_i(opts)) + setenv("GIT_REFLOG_ACTION", reflog_message(opts, + command_to_string(item->command), NULL), + 1); res = do_pick_commit(item->command, item->commit, opts, is_final_fixup(todo_list)); if (item->command == TODO_EDIT) { From dc97ac4a051e7a4d7b976e33339100ae5bc949c8 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 12 Apr 2016 17:26:31 +0200 Subject: [PATCH 67/92] sequencer (rebase -i): copy commit notes at end When rebasing commits that have commit notes attached, the interactive rebase rewrites those notes faithfully at the end. The sequencer must do this, too, if it wishes to do interactive rebase's job. Signed-off-by: Johannes Schindelin --- sequencer.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/sequencer.c b/sequencer.c index d3e0b48bae..2f436cde44 100644 --- a/sequencer.c +++ b/sequencer.c @@ -93,6 +93,15 @@ static GIT_PATH_FUNC(rebase_path_amend, "rebase-merge/amend") * the long commit name of the corresponding patch. */ static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha") +/* + * For the post-rewrite hook, we make a list of rewritten commits and + * their new sha1s. The rewritten-pending list keeps the sha1s of + * commits that have been processed, but not committed yet, + * e.g. because they are waiting for a 'squash' command. + */ +static GIT_PATH_FUNC(rebase_path_rewritten_list, "rebase-merge/rewritten-list") +static GIT_PATH_FUNC(rebase_path_rewritten_pending, + "rebase-merge/rewritten-pending") /* * The following files are written by git-rebase just after parsing the * command-line (and are only consumed, not modified, by the sequencer). @@ -881,6 +890,43 @@ static int update_squash_messages(enum todo_command command, return write_message(&buf, rebase_path_squash_msg()); } +static void flush_rewritten_pending(void) { + struct strbuf buf = STRBUF_INIT; + unsigned char newsha1[20]; + FILE *out; + + if (strbuf_read_file(&buf, rebase_path_rewritten_pending(), 82) > 0 && + !get_sha1("HEAD", newsha1) && + (out = fopen(rebase_path_rewritten_list(), "a"))) { + char *bol = buf.buf, *eol; + + while (*bol) { + eol = strchrnul(bol, '\n'); + fprintf(out, "%.*s %s\n", (int)(eol - bol), + bol, sha1_to_hex(newsha1)); + if (!*eol) + break; + bol = eol + 1; + } + fclose(out); + unlink(rebase_path_rewritten_pending()); + } +} + +static void record_in_rewritten(struct object_id *oid, + enum todo_command next_command) { + FILE *out = fopen(rebase_path_rewritten_pending(), "a"); + + if (!out) + return; + + fprintf(out, "%s\n", oid_to_hex(oid)); + fclose(out); + + if (!is_fixup(next_command)) + flush_rewritten_pending(); +} + static int do_pick_commit(enum todo_command command, struct commit *commit, struct replay_opts *opts, int final_fixup) { @@ -1727,6 +1773,17 @@ static int is_final_fixup(struct todo_list *todo_list) return 1; } +static enum todo_command peek_command(struct todo_list *todo_list, int offset) +{ + int i; + + for (i = todo_list->current + offset; i < todo_list->nr; i++) + if (todo_list->items[i].command != TODO_NOOP) + return todo_list->items[i].command; + + return -1; +} + static const char *reflog_message(struct replay_opts *opts, const char *sub_action, const char *fmt, ...) { @@ -1785,6 +1842,9 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) item->arg, item->arg_len, opts, res, !res); } + if (is_rebase_i(opts) && !res) + record_in_rewritten(&item->commit->object.oid, + peek_command(todo_list, 1)); if (res && is_fixup(item->command)) { if (res == 1) intend_to_amend(); @@ -1814,6 +1874,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) if (is_rebase_i(opts)) { struct strbuf head_ref = STRBUF_INIT, buf = STRBUF_INIT; + struct stat st; /* Stopped in the middle, as planned? */ if (todo_list->current < todo_list->nr) @@ -1858,6 +1919,20 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) run_command_v_opt(argv, RUN_GIT_CMD); strbuf_reset(&buf); } + flush_rewritten_pending(); + if (!stat(rebase_path_rewritten_list(), &st) && + st.st_size > 0) { + struct child_process child = CHILD_PROCESS_INIT; + + child.in = open(rebase_path_rewritten_list(), O_RDONLY); + child.git_cmd = 1; + argv_array_push(&child.args, "notes"); + argv_array_push(&child.args, "copy"); + argv_array_push(&child.args, "--for-rewrite=rebase"); + /* we don't care if this copying failed */ + run_command(&child); + } + strbuf_release(&buf); strbuf_release(&head_ref); } From 2c3a77f53395ddac549c3a893adbdcbb0ab2ce55 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 8 Apr 2016 17:30:25 +0200 Subject: [PATCH 68/92] sequencer (rebase -i): record interrupted commits in rewritten, too When continuing after a `pick` command failed, we want that commit to show up in the rewritten-list (and its notes to be rewritten), too. Signed-off-by: Johannes Schindelin --- sequencer.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sequencer.c b/sequencer.c index 2f436cde44..7ae4faa461 100644 --- a/sequencer.c +++ b/sequencer.c @@ -2027,6 +2027,17 @@ int sequencer_continue(struct replay_opts *opts) } todo_list.current++; } + else if (file_exists(rebase_path_stopped_sha())) { + struct strbuf buf = STRBUF_INIT; + struct object_id oid; + + if (read_oneliner(&buf, rebase_path_stopped_sha(), 1)) { + if (!get_sha1_committish(buf.buf, oid.hash)) + record_in_rewritten(&oid, + peek_command(&todo_list, 0)); + } + strbuf_release(&buf); + } res = pick_commits(&todo_list, opts); release_todo_list: From 46397fbe33ccd67c9ea68a3c5d2e864e0aad62f0 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 8 Apr 2016 17:27:03 +0200 Subject: [PATCH 69/92] sequencer (rebase -i): run the post-rewrite hook, if needed Signed-off-by: Johannes Schindelin --- sequencer.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sequencer.c b/sequencer.c index 7ae4faa461..e93cf26aed 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1923,6 +1923,8 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) if (!stat(rebase_path_rewritten_list(), &st) && st.st_size > 0) { struct child_process child = CHILD_PROCESS_INIT; + const char *post_rewrite_hook = + find_hook("post-rewrite"); child.in = open(rebase_path_rewritten_list(), O_RDONLY); child.git_cmd = 1; @@ -1931,6 +1933,17 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) argv_array_push(&child.args, "--for-rewrite=rebase"); /* we don't care if this copying failed */ run_command(&child); + + if (post_rewrite_hook) { + struct child_process hook = CHILD_PROCESS_INIT; + + hook.in = open(rebase_path_rewritten_list(), + O_RDONLY); + argv_array_push(&hook.args, post_rewrite_hook); + argv_array_push(&hook.args, "rebase"); + /* we don't care if this hook failed */ + run_command(&hook); + } } strbuf_release(&buf); From 37d6a8d49e317a66e9421ae3eec2011569e1d728 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 8 Apr 2016 16:05:06 +0200 Subject: [PATCH 70/92] sequencer (rebase -i): respect the rebase.autostash setting Git's `rebase` command inspects the `rebase.autostash` config setting to determine whether it should stash any uncommitted changes before rebasing and re-apply them afterwards. As we introduce more bits and pieces to let the sequencer act as interactive rebase's backend, here is the part that adds support for the autostash feature. Signed-off-by: Johannes Schindelin --- sequencer.c | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/sequencer.c b/sequencer.c index e93cf26aed..e4bf0d615a 100644 --- a/sequencer.c +++ b/sequencer.c @@ -111,6 +111,7 @@ static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head") static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose") static GIT_PATH_FUNC(rebase_path_head_name, "rebase-merge/head-name") static GIT_PATH_FUNC(rebase_path_onto, "rebase-merge/onto") +static GIT_PATH_FUNC(rebase_path_autostash, "rebase-merge/autostash") /* We will introduce the 'interactive rebase' mode later */ static inline int is_rebase_i(const struct replay_opts *opts) @@ -1784,6 +1785,47 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset) return -1; } +static int apply_autostash(struct replay_opts *opts) +{ + struct strbuf stash_sha1 = STRBUF_INIT; + struct child_process child = CHILD_PROCESS_INIT; + int ret = 0; + + if (!read_oneliner(&stash_sha1, rebase_path_autostash(), 1)) { + strbuf_release(&stash_sha1); + return 0; + } + strbuf_trim(&stash_sha1); + + child.git_cmd = 1; + argv_array_push(&child.args, "stash"); + argv_array_push(&child.args, "apply"); + argv_array_push(&child.args, stash_sha1.buf); + if (!run_command(&child)) + printf(_("Applied autostash.")); + else { + struct child_process store = CHILD_PROCESS_INIT; + + store.git_cmd = 1; + argv_array_push(&store.args, "stash"); + argv_array_push(&store.args, "store"); + argv_array_push(&store.args, "-m"); + argv_array_push(&store.args, "autostash"); + argv_array_push(&store.args, "-q"); + argv_array_push(&store.args, stash_sha1.buf); + if (run_command(&store)) + ret = error(_("Cannot store %s"), stash_sha1.buf); + else + printf(_("Applying autostash resulted in conflicts.\n" + "Your changes are safe in the stash.\n" + "You can run \"git stash pop\" or" + " \"git stash drop\" at any time.\n")); + } + + strbuf_release(&stash_sha1); + return ret; +} + static const char *reflog_message(struct replay_opts *opts, const char *sub_action, const char *fmt, ...) { @@ -1945,6 +1987,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) run_command(&hook); } } + apply_autostash(opts); strbuf_release(&buf); strbuf_release(&head_ref); From d9e61a1efa4b211e99b4d9f097c40cf9e2dd8650 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 30 Mar 2016 17:58:55 +0200 Subject: [PATCH 71/92] sequencer (rebase -i): respect strategy/strategy_opts settings The sequencer already has an idea about using different merge strategies. We just piggy-back on top of that, using rebase -i's own settings, when running the sequencer in interactive rebase mode. Signed-off-by: Johannes Schindelin --- sequencer.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/sequencer.c b/sequencer.c index e4bf0d615a..9ddc6734ca 100644 --- a/sequencer.c +++ b/sequencer.c @@ -112,6 +112,8 @@ static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose") static GIT_PATH_FUNC(rebase_path_head_name, "rebase-merge/head-name") static GIT_PATH_FUNC(rebase_path_onto, "rebase-merge/onto") static GIT_PATH_FUNC(rebase_path_autostash, "rebase-merge/autostash") +static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy") +static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts") /* We will introduce the 'interactive rebase' mode later */ static inline int is_rebase_i(const struct replay_opts *opts) @@ -1407,6 +1409,26 @@ static int read_populate_opts(struct replay_opts *opts) if (file_exists(rebase_path_verbose())) opts->verbose = 1; + if (read_oneliner(&buf, rebase_path_strategy(), 0)) { + opts->strategy = + sequencer_entrust(opts, + strbuf_detach(&buf, NULL)); + if (read_oneliner(&buf, + rebase_path_strategy_opts(), 0)) { + int i; + opts->xopts_nr = split_cmdline(buf.buf, + &opts->xopts); + for (i = 0; i < opts->xopts_nr; i++) + skip_prefix(opts->xopts[i], "--", + &opts->xopts[i]); + if (opts->xopts_nr) + sequencer_entrust(opts, + strbuf_detach(&buf, NULL)); + else + strbuf_release(&buf); + } + } + return 0; } From bda2de89c1631fa98651244335b1679747925804 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 7 Apr 2016 16:31:59 +0200 Subject: [PATCH 72/92] sequencer (rebase -i): allow rescheduling commands The interactive rebase has the very special magic that a cherry-pick that exits with a status different from 0 and 1 signifies a failure to even record that a cherry-pick was started. This can happen e.g. when a fast-forward fails because it would overwrite untracked files. In that case, we must reschedule the command that we thought we already had at least started successfully. Signed-off-by: Johannes Schindelin --- sequencer.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sequencer.c b/sequencer.c index 9ddc6734ca..d8a79d8384 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1896,6 +1896,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) 1); res = do_pick_commit(item->command, item->commit, opts, is_final_fixup(todo_list)); + if (is_rebase_i(opts) && res < 0) { + /* Reschedule */ + todo_list->current--; + if (save_todo(todo_list, opts)) + return -1; + } if (item->command == TODO_EDIT) { struct commit *commit = item->commit; if (!res) From 17facd36e9efbb258e3e398d0ce5bb31283e5631 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 15:18:29 +0200 Subject: [PATCH 73/92] sequencer (rebase -i): implement the 'drop' command The parsing part of a 'drop' command is almost identical to parsing a 'pick', while the operation is the same as that of a 'noop'. Signed-off-by: Johannes Schindelin --- sequencer.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/sequencer.c b/sequencer.c index d8a79d8384..6336c06c95 100644 --- a/sequencer.c +++ b/sequencer.c @@ -770,7 +770,8 @@ enum todo_command { /* commands that do something else than handling a single commit */ TODO_EXEC, /* commands that do nothing but are counted for reporting progress */ - TODO_NOOP + TODO_NOOP, + TODO_DROP }; static struct { @@ -784,7 +785,8 @@ static struct { { 'f', "fixup" }, { 's', "squash" }, { 'x', "exec" }, - { 0, "noop" } + { 0, "noop" }, + { 'd', "drop" } }; static const char *command_to_string(const enum todo_command command) @@ -1302,7 +1304,7 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list) else if (is_fixup(item->command)) return error("Cannot '%s' without a previous commit", command_to_string(item->command)); - else if (item->command != TODO_NOOP) + else if (item->command < TODO_NOOP) fixup_okay = 1; p = *eol ? eol + 1 : eol; } @@ -1801,7 +1803,7 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset) int i; for (i = todo_list->current + offset; i < todo_list->nr; i++) - if (todo_list->items[i].command != TODO_NOOP) + if (todo_list->items[i].command < TODO_NOOP) return todo_list->items[i].command; return -1; @@ -1934,7 +1936,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) res = do_exec(item->arg); *end_of_arg = saved; } - else if (item->command != TODO_NOOP) + else if (item->command < TODO_NOOP) return error("Unknown command %d", item->command); todo_list->current++; From 061b4d017e2cecd5a14d196547cb0f5cc44228a5 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 8 Apr 2016 08:45:17 +0200 Subject: [PATCH 74/92] sequencer (rebase -i): differentiate between comments and 'noop' In the upcoming patch, we will support rebase -i's progress reporting. The progress skips comments but counts 'noop's. Signed-off-by: Johannes Schindelin --- sequencer.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/sequencer.c b/sequencer.c index 6336c06c95..b3818c84a5 100644 --- a/sequencer.c +++ b/sequencer.c @@ -771,7 +771,9 @@ enum todo_command { TODO_EXEC, /* commands that do nothing but are counted for reporting progress */ TODO_NOOP, - TODO_DROP + TODO_DROP, + /* comments (not counted for reporting progress) */ + TODO_COMMENT }; static struct { @@ -786,12 +788,13 @@ static struct { { 's', "squash" }, { 'x', "exec" }, { 0, "noop" }, - { 'd', "drop" } + { 'd', "drop" }, + { 0, NULL } }; static const char *command_to_string(const enum todo_command command) { - if (command < ARRAY_SIZE(todo_command_info)) + if (command < TODO_COMMENT) return todo_command_info[command].str; die("Unknown command: %d", command); } @@ -1228,14 +1231,14 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) bol += strspn(bol, " \t"); if (bol == eol || *bol == '\r' || *bol == comment_line_char) { - item->command = TODO_NOOP; + item->command = TODO_COMMENT; item->commit = NULL; item->arg = bol; item->arg_len = eol - bol; return 0; } - for (i = 0; i < ARRAY_SIZE(todo_command_info); i++) + for (i = 0; i < TODO_COMMENT; i++) if (skip_prefix(bol, todo_command_info[i].str, &bol)) { item->command = i; break; @@ -1245,7 +1248,7 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) item->command = i; break; } - if (i >= ARRAY_SIZE(todo_command_info)) + if (i >= TODO_COMMENT) return -1; if (item->command == TODO_NOOP) { From c6b55dae86aa187660b26918427eb3242c555917 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 12 Apr 2016 14:54:20 +0200 Subject: [PATCH 75/92] run_command_opt(): optionally hide stderr when the command succeeds This will be needed to hide the output of `git commit` when the sequencer handles an interactive rebase's script. Signed-off-by: Johannes Schindelin --- run-command.c | 23 +++++++++++++++++++++++ run-command.h | 1 + 2 files changed, 24 insertions(+) diff --git a/run-command.c b/run-command.c index 5a4dbb66d7..921e43c4ea 100644 --- a/run-command.c +++ b/run-command.c @@ -575,6 +575,29 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const cmd.clean_on_exit = opt & RUN_CLEAN_ON_EXIT ? 1 : 0; cmd.dir = dir; cmd.env = env; + + if (opt & RUN_HIDE_STDERR_ON_SUCCESS) { + struct strbuf buf = STRBUF_INIT; + int res; + + cmd.err = -1; + if (start_command(&cmd) < 0) + return -1; + + if (strbuf_read(&buf, cmd.err, 0) < 0) { + close(cmd.err); + finish_command(&cmd); /* throw away exit code */ + return -1; + } + + close(cmd.err); + res = finish_command(&cmd); + if (res) + fputs(buf.buf, stderr); + strbuf_release(&buf); + return res; + } + return run_command(&cmd); } diff --git a/run-command.h b/run-command.h index 50666497ae..f87d01a0d6 100644 --- a/run-command.h +++ b/run-command.h @@ -70,6 +70,7 @@ extern int run_hook_ve(const char *const *env, const char *name, va_list args); #define RUN_SILENT_EXEC_FAILURE 8 #define RUN_USING_SHELL 16 #define RUN_CLEAN_ON_EXIT 32 +#define RUN_HIDE_STDERR_ON_SUCCESS 64 int run_command_v_opt(const char **argv, int opt); /* From 315eed6963456620b898fcdfbe7e2a04a3a10f78 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 12 Apr 2016 14:56:52 +0200 Subject: [PATCH 76/92] sequencer (rebase -i): show only failed `git commit`'s output This is the behavior of the shell script version of the interactive rebase, by using the `output` function defined in `git-rebase.sh`. Signed-off-by: Johannes Schindelin --- sequencer.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sequencer.c b/sequencer.c index b3818c84a5..b44743fab5 100644 --- a/sequencer.c +++ b/sequencer.c @@ -640,10 +640,15 @@ int sequencer_commit(const char *defmsg, struct replay_opts *opts, { char **env = NULL; struct argv_array array; - int rc; + int opt = RUN_GIT_CMD, rc; const char *value; if (is_rebase_i(opts)) { + if (!edit) { + opt |= RUN_COMMAND_STDOUT_TO_STDERR; + opt |= RUN_HIDE_STDERR_ON_SUCCESS; + } + env = read_author_script(); if (!env) { const char *gpg_opt = gpg_sign_opt_quoted(opts); @@ -688,7 +693,7 @@ int sequencer_commit(const char *defmsg, struct replay_opts *opts, if (opts->allow_empty_message) argv_array_push(&array, "--allow-empty-message"); - rc = run_command_v_opt_cd_env(array.argv, RUN_GIT_CMD, NULL, + rc = run_command_v_opt_cd_env(array.argv, opt, NULL, (const char *const *)env); argv_array_clear(&array); free(env); From 79742efe090eeb3e060bc9275a455e98d55971ca Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 12 Apr 2016 14:56:52 +0200 Subject: [PATCH 77/92] sequencer (rebase -i): show only failed cherry-picks' output This is the behavior of the shell script version of the interactive rebase, by using the `output` function defined in `git-rebase.sh`. Signed-off-by: Johannes Schindelin --- sequencer.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sequencer.c b/sequencer.c index b44743fab5..bbb583cadd 100644 --- a/sequencer.c +++ b/sequencer.c @@ -477,6 +477,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next, o.ancestor = base ? base_label : "(empty tree)"; o.branch1 = "HEAD"; o.branch2 = next ? next_label : "(empty tree)"; + if (is_rebase_i(opts)) + o.buffer_output = 2; head_tree = parse_tree_indirect(head); next_tree = next ? next->tree : empty_tree(); @@ -488,6 +490,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next, clean = merge_trees(&o, head_tree, next_tree, base_tree, &result); + if (is_rebase_i(opts) && clean <= 0) + fputs(o.obuf.buf, stdout); strbuf_release(&o.obuf); if (clean < 0) return clean; From 7c70c11af5b3046753f815f99ff71e1037379de5 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 21 Apr 2016 12:51:50 +0200 Subject: [PATCH 78/92] sequencer (rebase -i): suggest --edit-todo upon unknown command This is the same behavior as known from `git rebase -i`. Signed-off-by: Johannes Schindelin --- sequencer.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sequencer.c b/sequencer.c index bbb583cadd..2938b27ef9 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1341,8 +1341,12 @@ static int read_populate_todo(struct todo_list *todo_list, close(fd); res = parse_insn_buffer(todo_list->buf.buf, todo_list); - if (res) + if (res) { + if (is_rebase_i(opts)) + return error(_("Please fix this using " + "'git rebase --edit-todo'.")); return error(_("Unusable instruction sheet: '%s'"), todo_file); + } if (!todo_list->nr && (!is_rebase_i(opts) || !file_exists(rebase_path_done()))) From cf4b0902492df3d20d5f4f1f922a2a502b29ff02 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 8 Apr 2016 15:51:19 +0200 Subject: [PATCH 79/92] sequencer (rebase -i): show the progress The interactive rebase keeps the user informed about its progress. If the sequencer wants to do the grunt work of the interactive rebase, it also needs to show that progress. Signed-off-by: Johannes Schindelin --- sequencer.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/sequencer.c b/sequencer.c index 2938b27ef9..e00016acd1 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1212,6 +1212,7 @@ struct todo_list { struct strbuf buf; struct todo_item *items; int nr, alloc, current; + int done_nr, total_nr; }; #define TODO_LIST_INIT { STRBUF_INIT } @@ -1324,6 +1325,17 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list) return res; } +static int count_commands(struct todo_list *todo_list) +{ + int count = 0, i; + + for (i = 0; i < todo_list->nr; i++) + if (todo_list->items[i].command != TODO_COMMENT) + count++; + + return count; +} + static int read_populate_todo(struct todo_list *todo_list, struct replay_opts *opts) { @@ -1366,6 +1378,21 @@ static int read_populate_todo(struct todo_list *todo_list, return error(_("Cannot revert during a cherry-pick.")); } + if (is_rebase_i(opts)) { + struct todo_list done = TODO_LIST_INIT; + + if (strbuf_read_file(&done.buf, rebase_path_done(), 0) > 0 && + !parse_insn_buffer(done.buf.buf, &done)) + todo_list->done_nr = count_commands(&done); + else + todo_list->done_nr = 0; + + todo_list->total_nr = todo_list->done_nr + + count_commands(todo_list); + + todo_list_release(&done); + } + return 0; } @@ -1902,6 +1929,11 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) if (save_todo(todo_list, opts)) return -1; if (is_rebase_i(opts)) { + if (item->command != TODO_COMMENT) + fprintf(stderr, "Rebasing (%d/%d)%s", + ++(todo_list->done_nr), + todo_list->total_nr, + opts->verbose ? "\n" : "\r"); unlink(rebase_path_message()); unlink(rebase_path_author_script()); unlink(rebase_path_stopped_sha()); From 3affd042045a740abc52e091752f070f5578be09 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 8 Apr 2016 16:40:13 +0200 Subject: [PATCH 80/92] sequencer (rebase -i): write the progress into files For the benefit of e.g. the shell prompt, the interactive rebase not only displays the progress for the user to see, but also writes it into the msgnum/end files in the state directory. Teach the sequencer this new trick. Signed-off-by: Johannes Schindelin --- sequencer.c | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/sequencer.c b/sequencer.c index e00016acd1..c3626283d6 100644 --- a/sequencer.c +++ b/sequencer.c @@ -44,6 +44,16 @@ static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo") * actions. */ static GIT_PATH_FUNC(rebase_path_done, "rebase-merge/done") +/* + * The file to keep track of how many commands were already processed (e.g. + * for the prompt). + */ +static GIT_PATH_FUNC(rebase_path_msgnum, "rebase-merge/msgnum"); +/* + * The file to keep track of how many commands are to be processed in total + * (e.g. for the prompt). + */ +static GIT_PATH_FUNC(rebase_path_msgtotal, "rebase-merge/end"); /* * The commit message that is planned to be used for any changes that * need to be committed following a user interaction. @@ -1380,17 +1390,22 @@ static int read_populate_todo(struct todo_list *todo_list, if (is_rebase_i(opts)) { struct todo_list done = TODO_LIST_INIT; + FILE *f = fopen(rebase_path_msgtotal(), "w"); if (strbuf_read_file(&done.buf, rebase_path_done(), 0) > 0 && !parse_insn_buffer(done.buf.buf, &done)) todo_list->done_nr = count_commands(&done); else todo_list->done_nr = 0; + todo_list_release(&done); todo_list->total_nr = todo_list->done_nr + count_commands(todo_list); - todo_list_release(&done); + if (f) { + fprintf(f, "%d\n", todo_list->total_nr); + fclose(f); + } } return 0; @@ -1929,11 +1944,20 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) if (save_todo(todo_list, opts)) return -1; if (is_rebase_i(opts)) { - if (item->command != TODO_COMMENT) + if (item->command != TODO_COMMENT) { + FILE *f = fopen(rebase_path_msgnum(), "w"); + + todo_list->done_nr++; + + if (f) { + fprintf(f, "%d\n", todo_list->done_nr); + fclose(f); + } fprintf(stderr, "Rebasing (%d/%d)%s", - ++(todo_list->done_nr), + todo_list->done_nr, todo_list->total_nr, opts->verbose ? "\n" : "\r"); + } unlink(rebase_path_message()); unlink(rebase_path_author_script()); unlink(rebase_path_stopped_sha()); From 4e1aa6b09549359e3ee86c3ce817cb8468c56b84 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 8 Apr 2016 17:28:04 +0200 Subject: [PATCH 81/92] sequencer (rebase -i): write out the final message The shell script version of the interactive rebase has a very specific final message. Teach the sequencer to print the same. Signed-off-by: Johannes Schindelin --- sequencer.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sequencer.c b/sequencer.c index c3626283d6..ba49c3f56f 100644 --- a/sequencer.c +++ b/sequencer.c @@ -2091,6 +2091,9 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) } apply_autostash(opts); + fprintf(stderr, "Successfully rebased and updated %s.\n", + head_ref.buf); + strbuf_release(&buf); strbuf_release(&head_ref); } From be819f880bde3be26f6f5db2952016e950a1530f Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 12 Apr 2016 16:02:27 +0200 Subject: [PATCH 82/92] Add a builtin helper for interactive rebases Git's interactive rebase is still implemented as a shell script, despite its complexity. This implies that it suffers from the portability point of view, from lack of expressibility, and of course also from performance. The latter issue is particularly serious on Windows, where we pay a hefty price for relying so much on POSIX. Unfortunately, being such a huge shell script also means that we missed the train when it would have been relatively easy to port it to C, and instead piled feature upon feature onto that poor script that originally never intended to be more than a slightly pimped cherry-pick in a loop. To open the road toward better performance (in addition to all the other benefits of C over shell scripts), let's just start *somewhere*. The approach taken here is to add a builtin helper that at first intends to take care of the parts of the interactive rebase that are most affected by the performance penalties mentioned above. In particular, after we spent all those efforts on preparing the sequencer to process rebase -i's git-rebase-todo scripts, we implement the `git rebase -i --continue` functionality as a new builtin, git-rebase--helper. Once that is in place, we can work gradually on tackling the rest of the technical debt. Note that the rebase--helper needs to learn about the transient --ff/--no-ff options of git-rebase, as the corresponding flag is not persisted to, and re-read from, the state directory. Signed-off-by: Johannes Schindelin --- .gitignore | 1 + Makefile | 1 + builtin.h | 1 + builtin/rebase--helper.c | 40 ++++++++++++++++++++++++++++++++++++++++ git.c | 1 + 5 files changed, 44 insertions(+) create mode 100644 builtin/rebase--helper.c diff --git a/.gitignore b/.gitignore index 05cb58a3d4..a9b8c968c9 100644 --- a/.gitignore +++ b/.gitignore @@ -114,6 +114,7 @@ /git-read-tree /git-rebase /git-rebase--am +/git-rebase--helper /git-rebase--interactive /git-rebase--merge /git-receive-pack diff --git a/Makefile b/Makefile index 7a36af8665..e14744bc87 100644 --- a/Makefile +++ b/Makefile @@ -925,6 +925,7 @@ BUILTIN_OBJS += builtin/prune.o BUILTIN_OBJS += builtin/pull.o BUILTIN_OBJS += builtin/push.o BUILTIN_OBJS += builtin/read-tree.o +BUILTIN_OBJS += builtin/rebase--helper.o BUILTIN_OBJS += builtin/receive-pack.o BUILTIN_OBJS += builtin/reflog.o BUILTIN_OBJS += builtin/remote.o diff --git a/builtin.h b/builtin.h index 6b95006a0a..2e5de141eb 100644 --- a/builtin.h +++ b/builtin.h @@ -102,6 +102,7 @@ extern int cmd_prune_packed(int argc, const char **argv, const char *prefix); extern int cmd_pull(int argc, const char **argv, const char *prefix); extern int cmd_push(int argc, const char **argv, const char *prefix); extern int cmd_read_tree(int argc, const char **argv, const char *prefix); +extern int cmd_rebase__helper(int argc, const char **argv, const char *prefix); extern int cmd_receive_pack(int argc, const char **argv, const char *prefix); extern int cmd_reflog(int argc, const char **argv, const char *prefix); extern int cmd_remote(int argc, const char **argv, const char *prefix); diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c new file mode 100644 index 0000000000..ca1ebb2fa1 --- /dev/null +++ b/builtin/rebase--helper.c @@ -0,0 +1,40 @@ +#include "builtin.h" +#include "cache.h" +#include "parse-options.h" +#include "sequencer.h" + +static const char * const builtin_rebase_helper_usage[] = { + N_("git rebase--helper []"), + NULL +}; + +int cmd_rebase__helper(int argc, const char **argv, const char *prefix) +{ + struct replay_opts opts = REPLAY_OPTS_INIT; + enum { + CONTINUE = 1, ABORT + } command = 0; + struct option options[] = { + OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")), + OPT_CMDMODE(0, "continue", &command, N_("continue rebase"), + CONTINUE), + OPT_CMDMODE(0, "abort", &command, N_("abort rebase"), + ABORT), + OPT_END() + }; + + git_config(git_default_config, NULL); + + opts.action = REPLAY_INTERACTIVE_REBASE; + opts.allow_ff = 1; + opts.allow_empty = 1; + + argc = parse_options(argc, argv, NULL, options, + builtin_rebase_helper_usage, PARSE_OPT_KEEP_ARGV0); + + if (command == CONTINUE && argc == 1) + return !!sequencer_continue(&opts); + if (command == ABORT && argc == 1) + return !!sequencer_remove_state(&opts); + usage_with_options(builtin_rebase_helper_usage, options); +} diff --git a/git.c b/git.c index 0f1937fd0c..26b4ad3bee 100644 --- a/git.c +++ b/git.c @@ -451,6 +451,7 @@ static struct cmd_struct commands[] = { { "pull", cmd_pull, RUN_SETUP | NEED_WORK_TREE }, { "push", cmd_push, RUN_SETUP }, { "read-tree", cmd_read_tree, RUN_SETUP }, + { "rebase--helper", cmd_rebase__helper, RUN_SETUP | NEED_WORK_TREE }, { "receive-pack", cmd_receive_pack }, { "reflog", cmd_reflog, RUN_SETUP }, { "remote", cmd_remote, RUN_SETUP }, From 09d4656ba1a69767c617bfc115b0cfac374a1207 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 28 Mar 2016 13:34:50 +0200 Subject: [PATCH 83/92] rebase -i: use the rebase--helper builtin Now that the sequencer learned to process a "normal" interactive rebase, we use it. The original shell script is still used for "non-normal" interactive rebases, i.e. when --root or --preserve-merges was passed. Please note that the --root option (via the $squash_onto variable) needs special handling only for the very first command, hence it is still okay to use the helper upon continue/skip. Also please note that the --no-ff setting is volatile, i.e. when the interactive rebase is interrupted at any stage, there is no record of it. Therefore, we have to pass it from the shell script to the rebase--helper. Signed-off-by: Johannes Schindelin --- git-rebase--interactive.sh | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index ca994c5c54..88e6d97e0c 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -1059,6 +1059,10 @@ git_rebase__interactive () { case "$action" in continue) + if test ! -d "$rewritten" + then + exec git rebase--helper ${force_rebase:+--no-ff} --continue + fi # do we have anything to commit? if git diff-index --cached --quiet HEAD -- then @@ -1118,6 +1122,10 @@ first and then run 'git rebase --continue' again.")" skip) git rerere clear + if test ! -d "$rewritten" + then + exec git rebase--helper ${force_rebase:+--no-ff} --continue + fi do_rest return 0 ;; @@ -1304,6 +1312,11 @@ expand_todo_ids test -d "$rewritten" || test -n "$force_rebase" || skip_unnecessary_picks checkout_onto +if test -z "$rebase_root" && test ! -d "$rewritten" +then + require_clean_work_tree "rebase" + exec git rebase--helper ${force_rebase:+--no-ff} --continue +fi do_rest } From ef8b5ad85664b2108aa5dbcec29fed037f14263f Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 17:43:05 +0200 Subject: [PATCH 84/92] rebase -i: generate the script via rebase--helper The first step of an interactive rebase is to generate the so-called "todo script", to be stored in the state directory as "git-rebase-todo" and to be edited by the user. Originally, we adjusted the output of `git log ` using a simple sed script. Over the course of the years, the code became more complicated. We now use shell scripting to edit the output of `git log` conditionally, depending whether to keep "empty" commits (i.e. commits that do not change any files). On platforms where shell scripting is not native, this can be a serious drag. And it opens the door for incompatibilities between platforms when it comes to shell scripting or to Unix-y commands. Let's just re-implement the todo script generation in plain C, using the revision machinery directly. This is substantially faster, improving the speed relative to the shell script version of the interactive rebase from 2x to 3x on Windows. Note that the rearrange_squash() function in git-rebase--interactive relied on the fact that we set the "format" variable to the config setting rebase.instructionFormat. Relying on a side effect like this is no good, hence we explicitly perform that assignment (possibly again) in rearrange_squash(). Signed-off-by: Johannes Schindelin --- builtin/rebase--helper.c | 8 ++++++- git-rebase--interactive.sh | 42 +++++++++++++++++++----------------- sequencer.c | 44 ++++++++++++++++++++++++++++++++++++++ sequencer.h | 2 ++ 4 files changed, 75 insertions(+), 21 deletions(-) diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c index ca1ebb2fa1..821058d452 100644 --- a/builtin/rebase--helper.c +++ b/builtin/rebase--helper.c @@ -11,15 +11,19 @@ static const char * const builtin_rebase_helper_usage[] = { int cmd_rebase__helper(int argc, const char **argv, const char *prefix) { struct replay_opts opts = REPLAY_OPTS_INIT; + int keep_empty = 0; enum { - CONTINUE = 1, ABORT + CONTINUE = 1, ABORT, MAKE_SCRIPT } command = 0; struct option options[] = { OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")), + OPT_BOOL(0, "keep-empty", &keep_empty, N_("keep empty commits")), OPT_CMDMODE(0, "continue", &command, N_("continue rebase"), CONTINUE), OPT_CMDMODE(0, "abort", &command, N_("abort rebase"), ABORT), + OPT_CMDMODE(0, "make-script", &command, + N_("make rebase script"), MAKE_SCRIPT), OPT_END() }; @@ -36,5 +40,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix) return !!sequencer_continue(&opts); if (command == ABORT && argc == 1) return !!sequencer_remove_state(&opts); + if (command == MAKE_SCRIPT && argc > 1) + return !!sequencer_make_script(keep_empty, stdout, argc, argv); usage_with_options(builtin_rebase_helper_usage, options); } diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 88e6d97e0c..5b98ee2700 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -775,6 +775,7 @@ collapse_todo_ids() { # each log message will be re-retrieved in order to normalize the # autosquash arrangement rearrange_squash () { + format=$(git config --get rebase.instructionFormat) # extract fixup!/squash! lines and resolve any referenced sha1's while read -r pick sha1 message do @@ -1200,26 +1201,27 @@ else revisions=$onto...$orig_head shortrevisions=$shorthead fi -format=$(git config --get rebase.instructionFormat) -# the 'rev-list .. | sed' requires %m to parse; the instruction requires %H to parse -git rev-list $merges_option --format="%m%H ${format:-%s}" \ - --reverse --left-right --topo-order \ - $revisions ${restrict_revision+^$restrict_revision} | \ - sed -n "s/^>//p" | -while read -r sha1 rest -do +if test t != "$preserve_merges" +then + git rebase--helper --make-script ${keep_empty:+--keep-empty} \ + $revisions ${restrict_revision+^$restrict_revision} >"$todo" +else + format=$(git config --get rebase.instructionFormat) + # the 'rev-list .. | sed' requires %m to parse; the instruction requires %H to parse + git rev-list $merges_option --format="%m%H ${format:-%s}" \ + --reverse --left-right --topo-order \ + $revisions ${restrict_revision+^$restrict_revision} | \ + sed -n "s/^>//p" | + while read -r sha1 rest + do - if test -z "$keep_empty" && is_empty_commit $sha1 && ! is_merge_commit $sha1 - then - comment_out="$comment_char " - else - comment_out= - fi + if test -z "$keep_empty" && is_empty_commit $sha1 && ! is_merge_commit $sha1 + then + comment_out="$comment_char " + else + comment_out= + fi - if test t != "$preserve_merges" - then - printf '%s\n' "${comment_out}pick $sha1 $rest" >>"$todo" - else if test -z "$rebase_root" then preserve=t @@ -1238,8 +1240,8 @@ do touch "$rewritten"/$sha1 printf '%s\n' "${comment_out}pick $sha1 $rest" >>"$todo" fi - fi -done + done +fi # Watch for commits that been dropped by --cherry-pick if test t = "$preserve_merges" diff --git a/sequencer.c b/sequencer.c index ba49c3f56f..32b8238d65 100644 --- a/sequencer.c +++ b/sequencer.c @@ -2345,3 +2345,47 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag) strbuf_release(&sob); } + +int sequencer_make_script(int keep_empty, FILE *out, + int argc, const char **argv) +{ + char *format = "%s"; + struct pretty_print_context pp = {0}; + struct strbuf buf = STRBUF_INIT; + struct rev_info revs; + struct commit *commit; + + init_revisions(&revs, NULL); + revs.verbose_header = 1; + revs.max_parents = 1; + revs.cherry_pick = 1; + revs.limited = 1; + revs.reverse = 1; + revs.right_only = 1; + revs.sort_order = REV_SORT_IN_GRAPH_ORDER; + revs.topo_order = 1; + + revs.pretty_given = 1; + git_config_get_string("rebase.instructionFormat", &format); + get_commit_format(format, &revs); + pp.fmt = revs.commit_format; + pp.output_encoding = get_log_output_encoding(); + + if (setup_revisions(argc, argv, &revs, NULL) > 1) + return error("make_script: unhandled options"); + + if (prepare_revision_walk(&revs) < 0) + return error("make_script: error preparing revisions"); + + while ((commit = get_revision(&revs))) { + strbuf_reset(&buf); + if (!keep_empty && is_original_commit_empty(commit)) + strbuf_addf(&buf, "%c ", comment_line_char); + strbuf_addf(&buf, "pick %s ", oid_to_hex(&commit->object.oid)); + pretty_print_commit(&pp, commit, &buf); + strbuf_addch(&buf, '\n'); + fputs(buf.buf, out); + } + strbuf_release(&buf); + return 0; +} diff --git a/sequencer.h b/sequencer.h index fd2a719e36..bc524be713 100644 --- a/sequencer.h +++ b/sequencer.h @@ -58,6 +58,8 @@ int sequencer_remove_state(struct replay_opts *opts); int sequencer_commit(const char *defmsg, struct replay_opts *opts, int allow_empty, int edit, int amend, int cleanup_commit_message); +int sequencer_make_script(int keep_empty, FILE *out, + int argc, const char **argv); extern const char sign_off_header[]; From 6d9ecbb0f4a05cc235d0d5f5857052da80bdda4b Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 15 Apr 2016 10:00:39 +0200 Subject: [PATCH 85/92] rebase -i: remove useless indentation The commands used to be indented, and it is nice to look at, but when we transform the SHA-1s, the indentation is removed. So let's do away with it. For the moment, at least: when we will use the upcoming rebase--helper to transform the SHA-1s, we *will* keep the indentation and can reintroduce it. Yet, to be able to validate the rebase--helper against the output of the current shell script version, we need to remove the extra indentation. Signed-off-by: Johannes Schindelin --- git-rebase--interactive.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 5b98ee2700..eeaac3dd87 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -146,13 +146,13 @@ reschedule_last_action () { append_todo_help () { gettext " Commands: - p, pick = use commit - r, reword = use commit, but edit the commit message - e, edit = use commit, but stop for amending - s, squash = use commit, but meld into previous commit - f, fixup = like \"squash\", but discard this commit's log message - x, exec = run command (the rest of the line) using shell - d, drop = remove commit +p, pick = use commit +r, reword = use commit, but edit the commit message +e, edit = use commit, but stop for amending +s, squash = use commit, but meld into previous commit +f, fixup = like \"squash\", but discard this commit's log message +x, exec = run command (the rest of the line) using shell +d, drop = remove commit These lines can be re-ordered; they are executed from top to bottom. " | git stripspace --comment-lines >>"$todo" From ae241699be3ef5b8e9500ea01ad5a3571394141b Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 17 Apr 2016 08:48:44 +0200 Subject: [PATCH 86/92] rebase -i: do not invent onelines when expanding/collapsing SHA-1s To avoid problems with short SHA-1s that become non-unique during the rebase, we rewrite the todo script with short/long SHA-1s before and after letting the user edit the script. Since SHA-1s are not intuitive for humans, rebase -i also provides the onelines (commit message subjects) in the script, purely for the user's convenience. It is very possible to generate a todo script via different means than rebase -i and then to let rebase -i run with it; In this case, these onelines are not required. And this is where the expand/collapse machinery has a bug: it *expects* that oneline, and failing to find one reuses the previous SHA-1 as "oneline". It was most likely an oversight, and made implementation in the (quite limiting) shell script language less convoluted. However, we are about to reimplement performance-critical parts in C (and due to spawning a git.exe process for every single line of the todo script, the expansion/collapsing of the SHA-1s *is* performance-hampering on Windows), therefore let's fix this bug to make cross-validation with the C version of that functionality possible. Signed-off-by: Johannes Schindelin --- git-rebase--interactive.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index eeaac3dd87..6988808d6f 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -750,7 +750,12 @@ transform_todo_ids () { ;; *) sha1=$(git rev-parse --verify --quiet "$@" ${rest%%[ ]*}) && - rest="$sha1 ${rest#*[ ]}" + if test "a$rest" = "a${rest#*[ ]}" + then + rest=$sha1 + else + rest="$sha1 ${rest#*[ ]}" + fi ;; esac printf '%s\n' "$command${rest:+ }$rest" From 402250cafbcb8f58b8c2532332e721be9946bf21 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 13 Apr 2016 18:17:19 +0200 Subject: [PATCH 87/92] rebase -i: also expand/collapse the SHA-1s via the rebase--helper This is crucial to improve performance on Windows, as the speed is now mostly dominated by the SHA-1 transformation (because it spawns a new rev-parse process for *every* line, and spawning processes is pretty slow from Git for Windows' MSYS2 Bash). Signed-off-by: Johannes Schindelin --- builtin/rebase--helper.c | 10 ++++++- git-rebase--interactive.sh | 4 +-- sequencer.c | 59 ++++++++++++++++++++++++++++++++++++++ sequencer.h | 2 ++ 4 files changed, 72 insertions(+), 3 deletions(-) diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c index 821058d452..9444c8d6c6 100644 --- a/builtin/rebase--helper.c +++ b/builtin/rebase--helper.c @@ -13,7 +13,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix) struct replay_opts opts = REPLAY_OPTS_INIT; int keep_empty = 0; enum { - CONTINUE = 1, ABORT, MAKE_SCRIPT + CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_SHA1S, EXPAND_SHA1S } command = 0; struct option options[] = { OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")), @@ -24,6 +24,10 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix) ABORT), OPT_CMDMODE(0, "make-script", &command, N_("make rebase script"), MAKE_SCRIPT), + OPT_CMDMODE(0, "shorten-sha1s", &command, + N_("shorten SHA-1s in the todo list"), SHORTEN_SHA1S), + OPT_CMDMODE(0, "expand-sha1s", &command, + N_("expand SHA-1s in the todo list"), EXPAND_SHA1S), OPT_END() }; @@ -42,5 +46,9 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix) return !!sequencer_remove_state(&opts); if (command == MAKE_SCRIPT && argc > 1) return !!sequencer_make_script(keep_empty, stdout, argc, argv); + if (command == SHORTEN_SHA1S && argc == 1) + return !!transform_todo_ids(1); + if (command == EXPAND_SHA1S && argc == 1) + return !!transform_todo_ids(0); usage_with_options(builtin_rebase_helper_usage, options); } diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 6988808d6f..3d00c21722 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -764,11 +764,11 @@ transform_todo_ids () { } expand_todo_ids() { - transform_todo_ids + git rebase--helper --expand-sha1s } collapse_todo_ids() { - transform_todo_ids --short + git rebase--helper --shorten-sha1s } # Rearrange the todo list that has both "pick sha1 msg" and diff --git a/sequencer.c b/sequencer.c index 32b8238d65..3520aa0433 100644 --- a/sequencer.c +++ b/sequencer.c @@ -2389,3 +2389,62 @@ int sequencer_make_script(int keep_empty, FILE *out, strbuf_release(&buf); return 0; } + + +int transform_todo_ids(int shorten_sha1s) +{ + const char *todo_file = rebase_path_todo(); + struct todo_list todo_list = TODO_LIST_INIT; + int fd, res, i; + FILE *out; + + strbuf_reset(&todo_list.buf); + fd = open(todo_file, O_RDONLY); + if (fd < 0) + return error_errno(_("Could not open '%s'"), todo_file); + if (strbuf_read(&todo_list.buf, fd, 0) < 0) { + close(fd); + return error(_("Could not read %s."), todo_file); + } + close(fd); + + res = parse_insn_buffer(todo_list.buf.buf, &todo_list); + if (res) { + todo_list_release(&todo_list); + return error(_("Unusable instruction sheet: %s"), todo_file); + } + + out = fopen(todo_file, "w"); + if (!out) { + todo_list_release(&todo_list); + return error(_("Unable to open '%s' for writing"), todo_file); + } + for (i = 0; i < todo_list.nr; i++) { + struct todo_item *item = todo_list.items + i; + int bol = item->offset_in_buf; + const char *p = todo_list.buf.buf + bol; + int eol = i + 1 < todo_list.nr ? + todo_list.items[i + 1].offset_in_buf : + todo_list.buf.len; + + if (item->command >= TODO_EXEC && item->command != TODO_DROP) + fwrite(p, eol - bol, 1, out); + else { + int eoc = strcspn(p, " \t"); + const char *sha1 = shorten_sha1s ? + short_commit_name(item->commit) : + oid_to_hex(&item->commit->object.oid); + + if (!eoc) { + p += strspn(p, " \t"); + eoc = strcspn(p, " \t"); + } + + fprintf(out, "%.*s %s %.*s\n", + eoc, p, sha1, item->arg_len, item->arg); + } + } + fclose(out); + todo_list_release(&todo_list); + return 0; +} diff --git a/sequencer.h b/sequencer.h index bc524be713..5feb52579e 100644 --- a/sequencer.h +++ b/sequencer.h @@ -61,6 +61,8 @@ int sequencer_commit(const char *defmsg, struct replay_opts *opts, int sequencer_make_script(int keep_empty, FILE *out, int argc, const char **argv); +int transform_todo_ids(int shorten_sha1s); + extern const char sign_off_header[]; void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag); From 975e5269aaf53e544b9bba5ce1612fe0367c0247 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 14 Jun 2016 16:54:43 +0200 Subject: [PATCH 88/92] t3404: relax rebase.missingCommitsCheck tests These tests were a bit anal about the *exact* warning/error message printed by git rebase. But those messages are intended for the *end user*, therefore it does not make sense to test so rigidly for the *exact* wording. In the following, we will reimplement the missing commits check in the sequencer, with slightly different words. So let's just test for the parts in the warning/error message that we *really* care about, nothing more, nothing less. Signed-off-by: Johannes Schindelin --- t/t3404-rebase-interactive.sh | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index e38e296388..6e32436727 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -1215,20 +1215,13 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' ' test B = $(git cat-file commit HEAD^ | sed -ne \$p) ' -cat >expect <actual && - test_i18ncmp expect actual && + test_i18ngrep "badcmd $(git rev-list --oneline -1 master~1)" actual && + test_i18ngrep "You can fix this with .git rebase --edit-todo.." actual && FAKE_LINES="1 2 3 drop 4 5" git rebase --edit-todo && git rebase --continue && test E = $(git cat-file commit HEAD | sed -ne \$p) && @@ -1250,20 +1243,13 @@ test_expect_success 'tabs and spaces are accepted in the todolist' ' test E = $(git cat-file commit HEAD | sed -ne \$p) ' -cat >expect <actual && - test_i18ncmp expect actual && + test_i18ngrep "edit XXXXXXX False commit" actual && + test_i18ngrep "You can fix this with .git rebase --edit-todo.." actual && FAKE_LINES="1 2 4 5 6" git rebase --edit-todo && git rebase --continue && test E = $(git cat-file commit HEAD | sed -ne \$p) From 5b96f6e9671662fe2f7e040fbedcc89d8f3ba3ce Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 14 Jun 2016 16:58:59 +0200 Subject: [PATCH 89/92] rebase -i: check for missing commits in the rebase--helper In particular on Windows, where shell scripts are even more expensive than on MacOSX or Linux, it makes sense to move a loop that forks Git at least once for every line in the todo list into a builtin. Signed-off-by: Johannes Schindelin --- builtin/rebase--helper.c | 7 +- git-rebase--interactive.sh | 164 ++----------------------------------- sequencer.c | 125 ++++++++++++++++++++++++++++ sequencer.h | 1 + 4 files changed, 137 insertions(+), 160 deletions(-) diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c index 9444c8d6c6..e706eac710 100644 --- a/builtin/rebase--helper.c +++ b/builtin/rebase--helper.c @@ -13,7 +13,8 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix) struct replay_opts opts = REPLAY_OPTS_INIT; int keep_empty = 0; enum { - CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_SHA1S, EXPAND_SHA1S + CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_SHA1S, EXPAND_SHA1S, + CHECK_TODO_LIST } command = 0; struct option options[] = { OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")), @@ -28,6 +29,8 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix) N_("shorten SHA-1s in the todo list"), SHORTEN_SHA1S), OPT_CMDMODE(0, "expand-sha1s", &command, N_("expand SHA-1s in the todo list"), EXPAND_SHA1S), + OPT_CMDMODE(0, "check-todo-list", &command, + N_("check the todo list"), CHECK_TODO_LIST), OPT_END() }; @@ -50,5 +53,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix) return !!transform_todo_ids(1); if (command == EXPAND_SHA1S && argc == 1) return !!transform_todo_ids(0); + if (command == CHECK_TODO_LIST && argc == 1) + return !!check_todo_list(); usage_with_options(builtin_rebase_helper_usage, options); } diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 3d00c21722..f474c2a5aa 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -880,96 +880,6 @@ add_exec_commands () { mv "$1.new" "$1" } -# Check if the SHA-1 passed as an argument is a -# correct one, if not then print $2 in "$todo".badsha -# $1: the SHA-1 to test -# $2: the line number of the input -# $3: the input filename -check_commit_sha () { - badsha=0 - if test -z "$1" - then - badsha=1 - else - sha1_verif="$(git rev-parse --verify --quiet $1^{commit})" - if test -z "$sha1_verif" - then - badsha=1 - fi - fi - - if test $badsha -ne 0 - then - line="$(sed -n -e "${2}p" "$3")" - warn "$(eval_gettext "\ -Warning: the SHA-1 is missing or isn't a commit in the following line: - - \$line")" - warn - fi - - return $badsha -} - -# prints the bad commits and bad commands -# from the todolist in stdin -check_bad_cmd_and_sha () { - retval=0 - lineno=0 - while read -r command rest - do - lineno=$(( $lineno + 1 )) - case $command in - "$comment_char"*|''|noop|x|exec) - # Doesn't expect a SHA-1 - ;; - "$cr") - # Work around CR left by "read" (e.g. with Git for - # Windows' Bash). - ;; - pick|p|drop|d|reword|r|edit|e|squash|s|fixup|f) - if ! check_commit_sha "${rest%%[ ]*}" "$lineno" "$1" - then - retval=1 - fi - ;; - *) - line="$(sed -n -e "${lineno}p" "$1")" - warn "$(eval_gettext "\ -Warning: the command isn't recognized in the following line: - - \$line")" - warn - retval=1 - ;; - esac - done <"$1" - return $retval -} - -# Print the list of the SHA-1 of the commits -# from stdin to stdout -todo_list_to_sha_list () { - git stripspace --strip-comments | - while read -r command sha1 rest - do - case $command in - "$comment_char"*|''|noop|x|"exec") - ;; - *) - long_sha=$(git rev-list --no-walk "$sha1" 2>/dev/null) - printf "%s\n" "$long_sha" - ;; - esac - done -} - -# Use warn for each line in stdin -warn_lines () { - while read -r line - do - warn " - $line" - done -} - # Switch to the branch in $into and notify it in the reflog checkout_onto () { GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name" @@ -984,74 +894,6 @@ get_missing_commit_check_level () { printf '%s' "$check_level" | tr 'A-Z' 'a-z' } -# Check if the user dropped some commits by mistake -# Behaviour determined by rebase.missingCommitsCheck. -# Check if there is an unrecognized command or a -# bad SHA-1 in a command. -check_todo_list () { - raise_error=f - - check_level=$(get_missing_commit_check_level) - - case "$check_level" in - warn|error) - # Get the SHA-1 of the commits - todo_list_to_sha_list <"$todo".backup >"$todo".oldsha1 - todo_list_to_sha_list <"$todo" >"$todo".newsha1 - - # Sort the SHA-1 and compare them - sort -u "$todo".oldsha1 >"$todo".oldsha1+ - mv "$todo".oldsha1+ "$todo".oldsha1 - sort -u "$todo".newsha1 >"$todo".newsha1+ - mv "$todo".newsha1+ "$todo".newsha1 - comm -2 -3 "$todo".oldsha1 "$todo".newsha1 >"$todo".miss - - # Warn about missing commits - if test -s "$todo".miss - then - test "$check_level" = error && raise_error=t - - warn "$(gettext "\ -Warning: some commits may have been dropped accidentally. -Dropped commits (newer to older):")" - - # Make the list user-friendly and display - opt="--no-walk=sorted --format=oneline --abbrev-commit --stdin" - git rev-list $opt <"$todo".miss | warn_lines - - warn "$(gettext "\ -To avoid this message, use \"drop\" to explicitly remove a commit. - -Use 'git config rebase.missingCommitsCheck' to change the level of warnings. -The possible behaviours are: ignore, warn, error.")" - warn - fi - ;; - ignore) - ;; - *) - warn "$(eval_gettext "Unrecognized setting \$check_level for option rebase.missingCommitsCheck. Ignoring.")" - ;; - esac - - if ! check_bad_cmd_and_sha "$todo" - then - raise_error=t - fi - - if test $raise_error = t - then - # Checkout before the first commit of the - # rebase: this way git rebase --continue - # will work correctly as it expects HEAD to be - # placed before the commit of the next action - checkout_onto - - warn "$(gettext "You can fix this with 'git rebase --edit-todo' and then run 'git rebase --continue'.")" - die "$(gettext "Or you can abort the rebase with 'git rebase --abort'.")" - fi -} - # The whole contents of this file is run by dot-sourcing it from # inside a shell function. It used to be that "return"s we see # below were not inside any function, and expected to return @@ -1312,7 +1154,11 @@ git_sequence_editor "$todo" || has_action "$todo" || return 2 -check_todo_list +git rebase--helper --check-todo-list || { + ret=$? + checkout_onto + exit $ret +} expand_todo_ids diff --git a/sequencer.c b/sequencer.c index 3520aa0433..498f8d6981 100644 --- a/sequencer.c +++ b/sequencer.c @@ -2448,3 +2448,128 @@ int transform_todo_ids(int shorten_sha1s) todo_list_release(&todo_list); return 0; } + +enum check_level { + CHECK_IGNORE = 0, CHECK_WARN, CHECK_ERROR +}; + +static enum check_level get_missing_commit_check_level(void) +{ + const char *value; + + if (git_config_get_value("rebase.missingcommitscheck", &value) || + !strcasecmp("ignore", value)) + return CHECK_IGNORE; + if (!strcasecmp("warn", value)) + return CHECK_WARN; + if (!strcasecmp("error", value)) + return CHECK_ERROR; + warning(_("Unrecognized setting $check_level for option" + "rebase.missingCommitsCheck. Ignoring.")); + return CHECK_IGNORE; +} + +/* + * Check if the user dropped some commits by mistake + * Behaviour determined by rebase.missingCommitsCheck. + * Check if there is an unrecognized command or a + * bad SHA-1 in a command. + */ +int check_todo_list(void) +{ + enum check_level check_level = get_missing_commit_check_level(); + struct strbuf todo_file = STRBUF_INIT; + struct todo_list todo_list = TODO_LIST_INIT; + struct commit_list *missing = NULL; + int raise_error = 0, res = 0, fd, i; + + strbuf_addstr(&todo_file, rebase_path_todo()); + fd = open(todo_file.buf, O_RDONLY); + if (fd < 0) { + res = error_errno(_("Could not open %s"), todo_file.buf); + goto leave_check; + } + if (strbuf_read(&todo_list.buf, fd, 0) < 0) { + close(fd); + res = error(_("Could not read %s."), todo_file.buf); + goto leave_check; + } + close(fd); + raise_error = res = + parse_insn_buffer(todo_list.buf.buf, &todo_list); + + if (check_level == CHECK_IGNORE) + goto leave_check; + + /* Get the SHA-1 of the commits */ + for (i = 0; i < todo_list.nr; i++) { + struct commit *commit = todo_list.items[i].commit; + if (commit) + commit->util = todo_list.items + i; + } + + todo_list_release(&todo_list); + strbuf_addstr(&todo_file, ".backup"); + fd = open(todo_file.buf, O_RDONLY); + if (fd < 0) { + res = error_errno(_("Could not open %s"), todo_file.buf); + goto leave_check; + } + if (strbuf_read(&todo_list.buf, fd, 0) < 0) { + close(fd); + res = error(_("Could not read %s."), todo_file.buf); + goto leave_check; + } + close(fd); + strbuf_release(&todo_file); + res = !!parse_insn_buffer(todo_list.buf.buf, &todo_list); + + /* Find commits that are missing after editing */ + for (i = 0; i < todo_list.nr; i++) { + struct commit *commit = todo_list.items[i].commit; + if (commit && !commit->util) { + commit_list_insert(commit, &missing); + commit->util = todo_list.items + i; + } + } + + /* Warn about missing commits */ + if (!missing) + goto leave_check; + + if (check_level == CHECK_ERROR) + raise_error = res = 1; + + fprintf(stderr, + _("Warning: some commits may have been dropped accidentally.\n" + "Dropped commits (newer to older):\n")); + + /* Make the list user-friendly and display */ + while (missing) { + struct commit *commit = pop_commit(&missing); + struct todo_item *item = commit->util; + + fprintf(stderr, " - %s %.*s\n", short_commit_name(commit), + item->arg_len, item->arg); + } + free_commit_list(missing); + + fprintf(stderr, _("To avoid this message, use \"drop\" to " + "explicitly remove a commit.\n\n" + "Use 'git config rebase.missingCommitsCheck' to change " + "the level of warnings.\n" + "The possible behaviours are: ignore, warn, error.\n\n")); + +leave_check: + strbuf_release(&todo_file); + todo_list_release(&todo_list); + + if (raise_error) + fprintf(stderr, + _("You can fix this with 'git rebase --edit-todo' " + "and then run 'git rebase --continue'.\n" + "Or you can abort the rebase with 'git rebase" + " --abort'.\n")); + + return res; +} diff --git a/sequencer.h b/sequencer.h index 5feb52579e..8e3daf921a 100644 --- a/sequencer.h +++ b/sequencer.h @@ -62,6 +62,7 @@ int sequencer_make_script(int keep_empty, FILE *out, int argc, const char **argv); int transform_todo_ids(int shorten_sha1s); +int check_todo_list(void); extern const char sign_off_header[]; From 520135efd0fe5cbef7c5c53061a0765d0197e97e Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 15 Jun 2016 09:09:09 +0200 Subject: [PATCH 90/92] rebase -i: skip unnecessary picks using the rebase--helper In particular on Windows, where shell scripts are even more expensive than on MacOSX or Linux, it makes sense to move a loop that forks Git at least once for every line in the todo list into a builtin. Note: The original code did not try to skip unnecessary picks of root commits but punts instead (probably --root was not considered common enough of a use case to bother optimizing). We do the same, for now. Signed-off-by: Johannes Schindelin --- builtin/rebase--helper.c | 6 ++- git-rebase--interactive.sh | 41 ++--------------- sequencer.c | 90 ++++++++++++++++++++++++++++++++++++++ sequencer.h | 1 + 4 files changed, 99 insertions(+), 39 deletions(-) diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c index e706eac710..de3ccd9bfb 100644 --- a/builtin/rebase--helper.c +++ b/builtin/rebase--helper.c @@ -14,7 +14,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix) int keep_empty = 0; enum { CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_SHA1S, EXPAND_SHA1S, - CHECK_TODO_LIST + CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS } command = 0; struct option options[] = { OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")), @@ -31,6 +31,8 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix) N_("expand SHA-1s in the todo list"), EXPAND_SHA1S), OPT_CMDMODE(0, "check-todo-list", &command, N_("check the todo list"), CHECK_TODO_LIST), + OPT_CMDMODE(0, "skip-unnecessary-picks", &command, + N_("skip unnecessary picks"), SKIP_UNNECESSARY_PICKS), OPT_END() }; @@ -55,5 +57,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix) return !!transform_todo_ids(0); if (command == CHECK_TODO_LIST && argc == 1) return !!check_todo_list(); + if (command == SKIP_UNNECESSARY_PICKS && argc == 1) + return !!skip_unnecessary_picks(); usage_with_options(builtin_rebase_helper_usage, options); } diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index f474c2a5aa..db96d83df5 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -703,43 +703,6 @@ do_rest () { done } -# skip picking commits whose parents are unchanged -skip_unnecessary_picks () { - fd=3 - while read -r command rest - do - # fd=3 means we skip the command - case "$fd,$command" in - 3,pick|3,p) - # pick a commit whose parent is current $onto -> skip - sha1=${rest%% *} - case "$(git rev-parse --verify --quiet "$sha1"^)" in - "$onto"*) - onto=$sha1 - ;; - *) - fd=1 - ;; - esac - ;; - 3,"$comment_char"*|3,) - # copy comments - ;; - *) - fd=1 - ;; - esac - printf '%s\n' "$command${rest:+ }$rest" >&$fd - done <"$todo" >"$todo.new" 3>>"$done" && - mv -f "$todo".new "$todo" && - case "$(peek_next_command)" in - squash|s|fixup|f) - record_in_rewritten "$onto" - ;; - esac || - die "$(gettext "Could not skip unnecessary pick commands")" -} - transform_todo_ids () { while read -r command rest do @@ -1162,7 +1125,9 @@ git rebase--helper --check-todo-list || { expand_todo_ids -test -d "$rewritten" || test -n "$force_rebase" || skip_unnecessary_picks +test -d "$rewritten" || test -n "$force_rebase" || +onto="$(git rebase--helper --skip-unnecessary-picks)" || +die "Could not skip unnecessary pick commands" checkout_onto if test -z "$rebase_root" && test ! -d "$rewritten" diff --git a/sequencer.c b/sequencer.c index 498f8d6981..7b1836cf60 100644 --- a/sequencer.c +++ b/sequencer.c @@ -2573,3 +2573,93 @@ leave_check: return res; } + +/* skip picking commits whose parents are unchanged */ +int skip_unnecessary_picks(void) +{ + const char *todo_file = rebase_path_todo(); + struct strbuf buf = STRBUF_INIT; + struct todo_list todo_list = TODO_LIST_INIT; + struct object_id onto_oid, *oid = &onto_oid, *parent_oid; + int fd, i; + + if (!read_oneliner(&buf, rebase_path_onto(), 0)) + return error("Could not read 'onto'"); + if (get_sha1(buf.buf, onto_oid.hash)) { + strbuf_release(&buf); + return error("Need a HEAD to fixup"); + } + strbuf_release(&buf); + + fd = open(todo_file, O_RDONLY); + if (fd < 0) { + return error_errno(_("Could not open '%s'"), todo_file); + } + if (strbuf_read(&todo_list.buf, fd, 0) < 0) { + close(fd); + return error(_("Could not read '%s'."), todo_file); + } + close(fd); + if (parse_insn_buffer(todo_list.buf.buf, &todo_list) < 0) { + todo_list_release(&todo_list); + return -1; + } + + for (i = 0; i < todo_list.nr; i++) { + struct todo_item *item = todo_list.items + i; + + if (item->command >= TODO_NOOP) + continue; + if (item->command != TODO_PICK) + break; + if (parse_commit(item->commit)) { + todo_list_release(&todo_list); + return error(_("Could not parse commit '%s'"), + oid_to_hex(&item->commit->object.oid)); + } + if (!item->commit->parents) + break; /* root commit */ + if (item->commit->parents->next) + break; /* merge commit */ + parent_oid = &item->commit->parents->item->object.oid; + if (hashcmp(parent_oid->hash, oid->hash)) + break; + oid = &item->commit->object.oid; + } + if (i > 0) { + int offset = i < todo_list.nr ? + todo_list.items[i].offset_in_buf : todo_list.buf.len; + const char *done_path = rebase_path_done(); + + fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666); + if (write_in_full(fd, todo_list.buf.buf, offset) < 0) { + todo_list_release(&todo_list); + return error_errno(_("Could not write to '%s'"), + done_path); + } + close(fd); + + fd = open(rebase_path_todo(), O_WRONLY, 0666); + if (write_in_full(fd, todo_list.buf.buf + offset, + todo_list.buf.len - offset) < 0) { + todo_list_release(&todo_list); + return error_errno(_("Could not write to '%s'"), + rebase_path_todo()); + } + if (ftruncate(fd, todo_list.buf.len - offset) < 0) { + todo_list_release(&todo_list); + return error_errno(_("Could not truncate '%s'"), + rebase_path_todo()); + } + close(fd); + + todo_list.current = i; + if (is_fixup(peek_command(&todo_list, 0))) + record_in_rewritten(oid, peek_command(&todo_list, 0)); + } + + todo_list_release(&todo_list); + printf("%s\n", oid_to_hex(oid)); + + return 0; +} diff --git a/sequencer.h b/sequencer.h index 8e3daf921a..4da215c9b7 100644 --- a/sequencer.h +++ b/sequencer.h @@ -63,6 +63,7 @@ int sequencer_make_script(int keep_empty, FILE *out, int transform_todo_ids(int shorten_sha1s); int check_todo_list(void); +int skip_unnecessary_picks(void); extern const char sign_off_header[]; From 5b3b448ce765af378ad166f099fb9371de100bad Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 24 Jul 2016 10:22:13 +0200 Subject: [PATCH 91/92] t3415: test fixup with wrapped oneline The `git commit --fixup` command unwraps wrapped onelines when constructing the commit message, without wrapping the result. We need to make sure that `git rebase --autosquash` keeps handling such cases correctly, in particular since we are about to move the autosquash handling into the rebase--helper. Signed-off-by: Johannes Schindelin --- t/t3415-rebase-autosquash.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh index 48346f1cc0..9fd629a6e2 100755 --- a/t/t3415-rebase-autosquash.sh +++ b/t/t3415-rebase-autosquash.sh @@ -304,4 +304,18 @@ test_expect_success 'extra spaces after fixup!' ' test $base = $parent ' +test_expect_success 'wrapped original subject' ' + if test -d .git/rebase-merge; then git rebase --abort; fi && + base=$(git rev-parse HEAD) && + echo "wrapped subject" >wrapped && + git add wrapped && + test_tick && + git commit --allow-empty -m "$(printf "To\nfixup")" && + test_tick && + git commit --allow-empty -m "fixup! To fixup" && + git rebase -i --autosquash --keep-empty HEAD~2 && + parent=$(git rev-parse HEAD^) && + test $base = $parent +' + test_done From d7587bcc7c10b94395165502f49a1c504cd62baf Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 16 Jun 2016 15:37:45 +0200 Subject: [PATCH 92/92] rebase -i: rearrange fixup/squash lines using the rebase--helper This operation has quadratic complexity, which is especially painful on Windows, where shell scripts are *already* slow (mainly due to the overhead of the POSIX emulation layer). Let's reimplement this with linear complexity (using a hash map to match the commits' subject lines) for the common case; Sadly, the fixup/squash feature's design neglected performance considerations, allowing arbitrary prefixes (read: `fixup! hell` will match the commit subject `hello world`), which means that we are stuck with quadratic performance in the worst case. The reimplemented logic also happens to fix a bug where commented-out lines (representing empty patches) were dropped by the previous code. Signed-off-by: Johannes Schindelin --- builtin/rebase--helper.c | 6 +- git-rebase--interactive.sh | 90 +--------------- sequencer.c | 197 +++++++++++++++++++++++++++++++++++ sequencer.h | 1 + t/t3415-rebase-autosquash.sh | 2 +- 5 files changed, 205 insertions(+), 91 deletions(-) diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c index de3ccd9bfb..e6591f0111 100644 --- a/builtin/rebase--helper.c +++ b/builtin/rebase--helper.c @@ -14,7 +14,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix) int keep_empty = 0; enum { CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_SHA1S, EXPAND_SHA1S, - CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS + CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH } command = 0; struct option options[] = { OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")), @@ -33,6 +33,8 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix) N_("check the todo list"), CHECK_TODO_LIST), OPT_CMDMODE(0, "skip-unnecessary-picks", &command, N_("skip unnecessary picks"), SKIP_UNNECESSARY_PICKS), + OPT_CMDMODE(0, "rearrange-squash", &command, + N_("rearrange fixup/squash lines"), REARRANGE_SQUASH), OPT_END() }; @@ -59,5 +61,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix) return !!check_todo_list(); if (command == SKIP_UNNECESSARY_PICKS && argc == 1) return !!skip_unnecessary_picks(); + if (command == REARRANGE_SQUASH && argc == 1) + return !!rearrange_squash(); usage_with_options(builtin_rebase_helper_usage, options); } diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index db96d83df5..68b570b8e2 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -734,94 +734,6 @@ collapse_todo_ids() { git rebase--helper --shorten-sha1s } -# Rearrange the todo list that has both "pick sha1 msg" and -# "pick sha1 fixup!/squash! msg" appears in it so that the latter -# comes immediately after the former, and change "pick" to -# "fixup"/"squash". -# -# Note that if the config has specified a custom instruction format -# each log message will be re-retrieved in order to normalize the -# autosquash arrangement -rearrange_squash () { - format=$(git config --get rebase.instructionFormat) - # extract fixup!/squash! lines and resolve any referenced sha1's - while read -r pick sha1 message - do - test -z "${format}" || message=$(git log -n 1 --format="%s" ${sha1}) - case "$message" in - "squash! "*|"fixup! "*) - action="${message%%!*}" - rest=$message - prefix= - # skip all squash! or fixup! (but save for later) - while : - do - case "$rest" in - "squash! "*|"fixup! "*) - prefix="$prefix${rest%%!*}," - rest="${rest#*! }" - ;; - *) - break - ;; - esac - done - printf '%s %s %s %s\n' "$sha1" "$action" "$prefix" "$rest" - # if it's a single word, try to resolve to a full sha1 and - # emit a second copy. This allows us to match on both message - # and on sha1 prefix - if test "${rest#* }" = "$rest"; then - fullsha="$(git rev-parse -q --verify "$rest" 2>/dev/null)" - if test -n "$fullsha"; then - # prefix the action to uniquely identify this line as - # intended for full sha1 match - echo "$sha1 +$action $prefix $fullsha" - fi - fi - esac - done >"$1.sq" <"$1" - test -s "$1.sq" || return - - used= - while read -r pick sha1 message - do - case " $used" in - *" $sha1 "*) continue ;; - esac - printf '%s\n' "$pick $sha1 $message" - test -z "${format}" || message=$(git log -n 1 --format="%s" ${sha1}) - used="$used$sha1 " - while read -r squash action msg_prefix msg_content - do - case " $used" in - *" $squash "*) continue ;; - esac - emit=0 - case "$action" in - +*) - action="${action#+}" - # full sha1 prefix test - case "$msg_content" in "$sha1"*) emit=1;; esac ;; - *) - # message prefix test - case "$message" in "$msg_content"*) emit=1;; esac ;; - esac - if test $emit = 1; then - if test -n "${format}" - then - msg_content=$(git log -n 1 --format="${format}" ${squash}) - else - msg_content="$(echo "$msg_prefix" | sed "s/,/! /g")$msg_content" - fi - printf '%s\n' "$action $squash $msg_content" - used="$used$squash " - fi - done <"$1.sq" - done >"$1.rearranged" <"$1" - cat "$1.rearranged" >"$1" - rm -f "$1.sq" "$1.rearranged" -} - # Add commands after a pick or after a squash/fixup serie # in the todo list. add_exec_commands () { @@ -1081,7 +993,7 @@ then fi test -s "$todo" || echo noop >> "$todo" -test -n "$autosquash" && rearrange_squash "$todo" +test -z "$autosquash" || git rebase--helper --rearrange-squash || exit test -n "$cmd" && add_exec_commands "$todo" todocount=$(git stripspace --strip-comments <"$todo" | wc -l) diff --git a/sequencer.c b/sequencer.c index 7b1836cf60..386d16e6db 100644 --- a/sequencer.c +++ b/sequencer.c @@ -18,6 +18,7 @@ #include "quote.h" #include "log-tree.h" #include "wt-status.h" +#include "hashmap.h" #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@ -2663,3 +2664,199 @@ int skip_unnecessary_picks(void) return 0; } + +struct subject2item_entry { + struct hashmap_entry entry; + int i; + char subject[FLEX_ARRAY]; +}; + +static int subject2item_cmp(const struct subject2item_entry *a, + const struct subject2item_entry *b, const void *key) +{ + return key ? strcmp(a->subject, key) : strcmp(a->subject, b->subject); +} + +/* + * Rearrange the todo list that has both "pick sha1 msg" and "pick sha1 + * fixup!/squash! msg" in it so that the latter is put immediately after the + * former, and change "pick" to "fixup"/"squash". + * + * Note that if the config has specified a custom instruction format, each log + * message will have to be retrieved from the commit (as the oneline in the + * script cannot be trusted) in order to normalize the autosquash arrangement. + */ +int rearrange_squash(void) +{ + const char *todo_file = rebase_path_todo(); + struct todo_list todo_list = TODO_LIST_INIT; + struct hashmap subject2item; + int res = 0, rearranged = 0, *next, *tail, fd, i; + char **subjects; + + fd = open(todo_file, O_RDONLY); + if (fd < 0) + return error_errno(_("Could not open %s"), todo_file); + if (strbuf_read(&todo_list.buf, fd, 0) < 0) { + close(fd); + return error(_("Could not read %s."), todo_file); + } + close(fd); + if (parse_insn_buffer(todo_list.buf.buf, &todo_list) < 0) { + todo_list_release(&todo_list); + return -1; + } + + /* + * The hashmap maps onelines to the respective todo list index. + * + * If any items need to be rearranged, the next[i] value will indicate + * which item was moved directly after the i'th. + * + * In that case, last[i] will indicate the index of the latest item to + * be moved to appear after the i'th. + */ + hashmap_init(&subject2item, (hashmap_cmp_fn) subject2item_cmp, + todo_list.nr); + ALLOC_ARRAY(next, todo_list.nr); + ALLOC_ARRAY(tail, todo_list.nr); + ALLOC_ARRAY(subjects, todo_list.nr); + for (i = 0; i < todo_list.nr; i++) { + struct strbuf buf = STRBUF_INIT; + struct todo_item *item = todo_list.items + i; + const char *commit_buffer, *subject, *p; + int i2 = -1; + struct subject2item_entry *entry; + + next[i] = tail[i] = -1; + if (item->command >= TODO_EXEC) { + subjects[i] = NULL; + continue; + } + + if (is_fixup(item->command)) { + todo_list_release(&todo_list); + return error(_("The script was already rearranged.")); + } + + item->commit->util = item; + + parse_commit(item->commit); + commit_buffer = get_commit_buffer(item->commit, NULL); + find_commit_subject(commit_buffer, &subject); + format_subject(&buf, subject, " "); + subject = subjects[i] = buf.buf; + unuse_commit_buffer(item->commit, commit_buffer); + if ((skip_prefix(subject, "fixup! ", &p) || + skip_prefix(subject, "squash! ", &p))) { + struct commit *commit2; + + for (;;) { + while (isspace(*p)) + p++; + if (!skip_prefix(p, "fixup! ", &p) && + !skip_prefix(p, "squash! ", &p)) + break; + } + + if ((entry = hashmap_get_from_hash(&subject2item, + strhash(p), p))) + /* found by title */ + i2 = entry->i; + else if (!strchr(p, ' ') && + (commit2 = + lookup_commit_reference_by_name(p)) && + commit2->util) + /* found by commit name */ + i2 = (struct todo_item *)commit2->util + - todo_list.items; + else { + /* copy can be a prefix of the commit subject */ + for (i2 = 0; i2 < i; i2++) + if (subjects[i2] && + starts_with(subjects[i2], p)) + break; + if (i2 == i) + i2 = -1; + } + } + if (i2 >= 0) { + rearranged = 1; + todo_list.items[i].command = + starts_with(subject, "fixup!") ? + TODO_FIXUP : TODO_SQUASH; + if (next[i2] < 0) + next[i2] = i; + else + next[tail[i2]] = i; + tail[i2] = i; + } + else if (!hashmap_get_from_hash(&subject2item, + strhash(subject), subject)) { + FLEX_ALLOC_MEM(entry, subject, buf.buf, buf.len); + entry->i = i; + hashmap_entry_init(entry, strhash(entry->subject)); + hashmap_put(&subject2item, entry); + } + strbuf_detach(&buf, NULL); + } + + if (rearranged) { + struct strbuf buf = STRBUF_INIT; + char *format = NULL; + + git_config_get_string("rebase.instructionFormat", &format); + for (i = 0; i < todo_list.nr; i++) { + enum todo_command command = todo_list.items[i].command; + int cur = i; + + /* + * Initially, all commands are 'pick's. If it is a + * fixup or a squash now, we have rearranged it. + */ + if (is_fixup(command)) + continue; + + while (cur >= 0) { + int offset = todo_list.items[cur].offset_in_buf; + int end_offset = cur + 1 < todo_list.nr ? + todo_list.items[cur + 1].offset_in_buf : + todo_list.buf.len; + char *bol = todo_list.buf.buf + offset; + char *eol = todo_list.buf.buf + end_offset; + + /* replace 'pick', by 'fixup' or 'squash' */ + command = todo_list.items[cur].command; + if (is_fixup(command)) { + strbuf_addstr(&buf, + todo_command_info[command].str); + bol += strcspn(bol, " \t"); + } + + strbuf_add(&buf, bol, eol - bol); + + cur = next[cur]; + } + } + + fd = open(todo_file, O_WRONLY); + if (fd < 0) + res |= error_errno(_("Could not open %s"), todo_file); + else if (write(fd, buf.buf, buf.len) < 0) + res |= error_errno(_("Could not read %s."), todo_file); + else if (ftruncate(fd, buf.len) < 0) + res |= error_errno(_("Could not finish %s"), todo_file); + close(fd); + strbuf_release(&buf); + } + + free(next); + free(tail); + for (i = 0; i < todo_list.nr; i++) + free(subjects[i]); + free(subjects); + hashmap_free(&subject2item, 1); + todo_list_release(&todo_list); + + return res; +} diff --git a/sequencer.h b/sequencer.h index 4da215c9b7..a3aa3cbabc 100644 --- a/sequencer.h +++ b/sequencer.h @@ -64,6 +64,7 @@ int sequencer_make_script(int keep_empty, FILE *out, int transform_todo_ids(int shorten_sha1s); int check_todo_list(void); int skip_unnecessary_picks(void); +int rearrange_squash(void); extern const char sign_off_header[]; diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh index 9fd629a6e2..b9e26008a7 100755 --- a/t/t3415-rebase-autosquash.sh +++ b/t/t3415-rebase-autosquash.sh @@ -278,7 +278,7 @@ set_backup_editor () { test_set_editor "$PWD/backup-editor.sh" } -test_expect_failure 'autosquash with multiple empty patches' ' +test_expect_success 'autosquash with multiple empty patches' ' test_tick && git commit --allow-empty -m "empty" && test_tick &&