* [PATCH v3 25/38] sequencer (rebase -i): run the post-rewrite hook, if needed
From: Johannes Schindelin @ 2017-01-02 15:28 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Kevin Daudt, Dennis Kaarsemaker, Stephan Beyer,
Jeff King
In-Reply-To: <cover.1483370556.git.johannes.schindelin@gmx.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
sequencer.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/sequencer.c b/sequencer.c
index d7273fd1b3..43ced8db31 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -1957,6 +1957,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;
@@ -1965,6 +1967,18 @@ 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);
+ hook.stdout_to_stderr = 1;
+ 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);
--
2.11.0.rc3.windows.1
^ permalink raw reply related
* [PATCH v3 24/38] sequencer (rebase -i): record interrupted commits in rewritten, too
From: Johannes Schindelin @ 2017-01-02 15:28 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Kevin Daudt, Dennis Kaarsemaker, Stephan Beyer,
Jeff King
In-Reply-To: <cover.1483370556.git.johannes.schindelin@gmx.de>
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 <johannes.schindelin@gmx.de>
---
sequencer.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/sequencer.c b/sequencer.c
index 50380a15b8..d7273fd1b3 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -2060,6 +2060,14 @@ int sequencer_continue(struct replay_opts *opts)
goto release_todo_list;
}
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) &&
+ !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);
--
2.11.0.rc3.windows.1
^ permalink raw reply related
* [PATCH v3 20/38] sequencer (rebase -i): allow fast-forwarding for edit/reword
From: Johannes Schindelin @ 2017-01-02 15:28 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Kevin Daudt, Dennis Kaarsemaker, Stephan Beyer,
Jeff King
In-Reply-To: <cover.1483370556.git.johannes.schindelin@gmx.de>
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 <johannes.schindelin@gmx.de>
---
sequencer.c | 25 +++++++++++++++++--------
1 file changed, 17 insertions(+), 8 deletions(-)
diff --git a/sequencer.c b/sequencer.c
index 50e998acc4..23161f593e 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -860,7 +860,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, amend = 0, allow;
+ int res, unborn = 0, amend = 0, allow = 0;
if (opts->no_commit) {
/*
@@ -905,11 +905,23 @@ 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)
/* TRANSLATORS: The first %s will be a "todo" command like
"revert" or "pick", the second %s a SHA1. */
@@ -917,10 +929,6 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
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"
@@ -1044,6 +1052,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
goto leave;
}
if (!opts->no_commit)
+fast_forward_edit:
res = run_git_commit(msg_file, opts, allow, edit, amend,
cleanup_commit_message);
--
2.11.0.rc3.windows.1
^ permalink raw reply related
* [PATCH v3 23/38] sequencer (rebase -i): copy commit notes at end
From: Johannes Schindelin @ 2017-01-02 15:28 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Kevin Daudt, Dennis Kaarsemaker, Stephan Beyer,
Jeff King
In-Reply-To: <cover.1483370556.git.johannes.schindelin@gmx.de>
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 <johannes.schindelin@gmx.de>
---
sequencer.c | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 76 insertions(+)
diff --git a/sequencer.c b/sequencer.c
index 95ae4bcd1e..50380a15b8 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -96,6 +96,15 @@ static GIT_PATH_FUNC(rebase_path_amend, "rebase-merge/amend")
*/
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).
*/
@@ -850,6 +859,44 @@ static int update_squash_messages(enum todo_command command,
return res;
}
+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());
+ }
+ strbuf_release(&buf);
+}
+
+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)
{
@@ -1743,6 +1790,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 (!is_noop(todo_list->items[i].command))
+ return todo_list->items[i].command;
+
+ return -1;
+}
+
static const char *reflog_message(struct replay_opts *opts,
const char *sub_action, const char *fmt, ...)
{
@@ -1801,6 +1859,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();
@@ -1827,6 +1888,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)
@@ -1891,6 +1953,20 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
log_tree_diff_flush(&log_tree_opt);
}
}
+ 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);
}
--
2.11.0.rc3.windows.1
^ permalink raw reply related
* [PATCH v3 21/38] sequencer (rebase -i): refactor setting the reflog message
From: Johannes Schindelin @ 2017-01-02 15:28 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Kevin Daudt, Dennis Kaarsemaker, Stephan Beyer,
Jeff King
In-Reply-To: <cover.1483370556.git.johannes.schindelin@gmx.de>
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 <johannes.schindelin@gmx.de>
---
sequencer.c | 33 ++++++++++++++++++++++++++-------
1 file changed, 26 insertions(+), 7 deletions(-)
diff --git a/sequencer.c b/sequencer.c
index 23161f593e..0d8e11f580 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -1743,6 +1743,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;
@@ -1810,6 +1830,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];
int res;
@@ -1825,23 +1846,21 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
res = error(_("could not read orig-head"));
goto cleanup_head_ref;
}
- strbuf_addf(&buf, "rebase -i (finish): %s onto ",
- head_ref.buf);
if (!read_oneliner(&buf, rebase_path_onto(), 0)) {
res = error(_("could not read 'onto'"));
goto cleanup_head_ref;
}
- 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)) {
res = error(_("could not update %s"),
head_ref.buf);
goto cleanup_head_ref;
}
- 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)) {
res = error(_("could not update HEAD to %s"),
head_ref.buf);
goto cleanup_head_ref;
--
2.11.0.rc3.windows.1
^ permalink raw reply related
* [PATCH v3 19/38] sequencer (rebase -i): implement the 'reword' command
From: Johannes Schindelin @ 2017-01-02 15:28 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Kevin Daudt, Dennis Kaarsemaker, Stephan Beyer,
Jeff King
In-Reply-To: <cover.1483370556.git.johannes.schindelin@gmx.de>
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 <johannes.schindelin@gmx.de>
---
sequencer.c | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/sequencer.c b/sequencer.c
index a2002f1c12..50e998acc4 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -718,6 +718,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 */
@@ -733,6 +734,7 @@ static struct {
{ 'p', "pick" },
{ 0, "revert" },
{ 'e', "edit" },
+ { 'r', "reword" },
{ 'f', "fixup" },
{ 's', "squash" },
{ 'x', "exec" },
@@ -962,7 +964,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;
@@ -1771,7 +1775,8 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *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);
+ 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);
int saved = *end_of_arg;
--
2.11.0.rc3.windows.1
^ permalink raw reply related
* [PATCH v3 17/38] sequencer (rebase -i): update refs after a successful rebase
From: Johannes Schindelin @ 2017-01-02 15:27 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Kevin Daudt, Dennis Kaarsemaker, Stephan Beyer,
Jeff King
In-Reply-To: <cover.1483370556.git.johannes.schindelin@gmx.de>
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.
We introduce a new head_ref variable in a wider scope than necessary at
the moment, to allow for a later patch that prints out "Successfully
rebased and updated <ref>".
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
sequencer.c | 46 +++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 45 insertions(+), 1 deletion(-)
diff --git a/sequencer.c b/sequencer.c
index 6a840216b1..80b2b2a975 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -102,6 +102,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")
static inline int is_rebase_i(const struct replay_opts *opts)
{
@@ -1784,12 +1786,53 @@ 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];
+ int res;
+
+ if (get_sha1("HEAD", head)) {
+ res = error(_("cannot read HEAD"));
+cleanup_head_ref:
+ strbuf_release(&head_ref);
+ strbuf_release(&buf);
+ return res;
+ }
+ if (!read_oneliner(&buf, rebase_path_orig_head(), 0) ||
+ get_sha1_hex(buf.buf, orig)) {
+ res = error(_("could not read orig-head"));
+ goto cleanup_head_ref;
+ }
+ strbuf_addf(&buf, "rebase -i (finish): %s onto ",
+ head_ref.buf);
+ if (!read_oneliner(&buf, rebase_path_onto(), 0)) {
+ res = error(_("could not read 'onto'"));
+ goto cleanup_head_ref;
+ }
+ if (update_ref(buf.buf, head_ref.buf, head, orig,
+ REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) {
+ res = error(_("could not update %s"),
+ head_ref.buf);
+ goto cleanup_head_ref;
+ }
+ strbuf_reset(&buf);
+ strbuf_addf(&buf,
+ "rebase -i (finish): returning to %s",
+ head_ref.buf);
+ if (create_symref("HEAD", head_ref.buf, buf.buf)) {
+ res = error(_("could not update HEAD to %s"),
+ head_ref.buf);
+ goto cleanup_head_ref;
+ }
+ strbuf_reset(&buf);
+ }
+
if (opts->verbose) {
struct rev_info log_tree_opt;
struct object_id orig, head;
@@ -1810,6 +1853,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
}
}
strbuf_release(&buf);
+ strbuf_release(&head_ref);
}
/*
--
2.11.0.rc3.windows.1
^ permalink raw reply related
* [PATCH v3 18/38] sequencer (rebase -i): leave a patch upon error
From: Johannes Schindelin @ 2017-01-02 15:27 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Kevin Daudt, Dennis Kaarsemaker, Stephan Beyer,
Jeff King
In-Reply-To: <cover.1483370556.git.johannes.schindelin@gmx.de>
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 <johannes.schindelin@gmx.de>
---
sequencer.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/sequencer.c b/sequencer.c
index 80b2b2a975..a2002f1c12 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -1769,7 +1769,9 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
intend_to_amend();
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);
int saved = *end_of_arg;
--
2.11.0.rc3.windows.1
^ permalink raw reply related
* [PATCH v3 16/38] sequencer (rebase -i): the todo can be empty when continuing
From: Johannes Schindelin @ 2017-01-02 15:27 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Kevin Daudt, Dennis Kaarsemaker, Stephan Beyer,
Jeff King
In-Reply-To: <cover.1483370556.git.johannes.schindelin@gmx.de>
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 <johannes.schindelin@gmx.de>
---
sequencer.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/sequencer.c b/sequencer.c
index a7b9ee0d04..6a840216b1 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -1215,8 +1215,7 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list)
else if (!is_noop(item->command))
fixup_okay = 1;
}
- if (!todo_list->nr)
- return error(_("no commits parsed."));
+
return res;
}
@@ -1240,6 +1239,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;
--
2.11.0.rc3.windows.1
^ permalink raw reply related
* [PATCH v3 13/38] sequencer (rebase -i): allow continuing with staged changes
From: Johannes Schindelin @ 2017-01-02 15:27 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Kevin Daudt, Dennis Kaarsemaker, Stephan Beyer,
Jeff King
In-Reply-To: <cover.1483370556.git.johannes.schindelin@gmx.de>
When an interactive rebase is interrupted, the user may stage changes
before continuing, and we need to commit those changes in that case.
Please note that the nested "if" added to the sequencer_continue() is
not combined into a single "if" because it will be extended with an
"else" clause in a later patch in this patch series.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
sequencer.c | 40 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 40 insertions(+)
diff --git a/sequencer.c b/sequencer.c
index 9913882603..69301fecc6 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -1826,6 +1826,42 @@ 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\n"
+ "first and then run 'git rebase "
+ "--continue' again."));
+
+ strbuf_release(&rev);
+ amend = 1;
+ }
+
+ if (run_git_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;
@@ -1834,6 +1870,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))
--
2.11.0.rc3.windows.1
^ permalink raw reply related
* [PATCH v3 14/38] sequencer (rebase -i): remove CHERRY_PICK_HEAD when no longer needed
From: Johannes Schindelin @ 2017-01-02 15:27 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Kevin Daudt, Dennis Kaarsemaker, Stephan Beyer,
Jeff King
In-Reply-To: <cover.1483370556.git.johannes.schindelin@gmx.de>
The scripted version of the interactive rebase already does that.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
sequencer.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/sequencer.c b/sequencer.c
index 69301fecc6..52e17c8887 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -1832,8 +1832,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;
--
2.11.0.rc3.windows.1
^ permalink raw reply related
* [PATCH v3 15/38] sequencer (rebase -i): skip some revert/cherry-pick specific code path
From: Johannes Schindelin @ 2017-01-02 15:27 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Kevin Daudt, Dennis Kaarsemaker, Stephan Beyer,
Jeff King
In-Reply-To: <cover.1483370556.git.johannes.schindelin@gmx.de>
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 <johannes.schindelin@gmx.de>
---
sequencer.c | 26 ++++++++++++++------------
1 file changed, 14 insertions(+), 12 deletions(-)
diff --git a/sequencer.c b/sequencer.c
index 52e17c8887..a7b9ee0d04 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -1878,26 +1878,28 @@ int sequencer_continue(struct replay_opts *opts)
if (is_rebase_i(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, 0)) {
+ res = error_dirty_index(opts);
goto release_todo_list;
+ }
+ todo_list.current++;
}
- if (index_differs_from("HEAD", 0, 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);
--
2.11.0.rc3.windows.1
^ permalink raw reply related
* [PATCH v3 12/38] sequencer (rebase -i): write an author-script file
From: Johannes Schindelin @ 2017-01-02 15:27 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Kevin Daudt, Dennis Kaarsemaker, Stephan Beyer,
Jeff King
In-Reply-To: <cover.1483370556.git.johannes.schindelin@gmx.de>
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 <johannes.schindelin@gmx.de>
---
sequencer.c | 50 +++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 49 insertions(+), 1 deletion(-)
diff --git a/sequencer.c b/sequencer.c
index 29b944d724..9913882603 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -483,6 +483,52 @@ 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;
+ int res;
+
+ 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++));
+ res = write_message(buf.buf, buf.len, rebase_path_author_script(), 1);
+ strbuf_release(&buf);
+ return res;
+}
+
/*
* Read the author-script file into an environment block, ready for use in
* run_command(), that can be free()d afterwards.
@@ -935,7 +981,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)
--
2.11.0.rc3.windows.1
^ permalink raw reply related
* [PATCH v3 10/38] sequencer (rebase -i): add support for the 'fixup' and 'squash' commands
From: Johannes Schindelin @ 2017-01-02 15:27 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Kevin Daudt, Dennis Kaarsemaker, Stephan Beyer,
Jeff King
In-Reply-To: <cover.1483370556.git.johannes.schindelin@gmx.de>
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 <johannes.schindelin@gmx.de>
---
sequencer.c | 227 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 217 insertions(+), 10 deletions(-)
diff --git a/sequencer.c b/sequencer.c
index 8ea3d6aa94..6a939a10bd 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -47,6 +47,35 @@ static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo")
*/
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
* being rebased.
@@ -641,6 +670,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 */
@@ -651,6 +682,8 @@ static const char *todo_command_strings[] = {
"pick",
"revert",
"edit",
+ "fixup",
+ "squash",
"exec",
"noop"
};
@@ -667,15 +700,114 @@ static int is_noop(const enum todo_command command)
return TODO_NOOP <= (size_t)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, res;
+ const char *message, *body;
+
+ if (file_exists(rebase_path_squash_msg())) {
+ struct strbuf header = STRBUF_INIT;
+ char *eol, *p;
+
+ if (strbuf_read_file(&buf, rebase_path_squash_msg(), 2048) <= 0)
+ return error(_("could not read '%s'"),
+ rebase_path_squash_msg());
+
+ p = buf.buf + 1;
+ eol = strchrnul(buf.buf, '\n');
+ if (buf.buf[0] != comment_line_char ||
+ (p += strcspn(p, "0123456789\n")) == eol)
+ return error(_("unexpected 1st line of squash message:"
+ "\n\n\t%.*s"),
+ (int)(eol - buf.buf), buf.buf);
+ count = strtol(p, NULL, 10);
+
+ if (count < 1)
+ return error(_("invalid 1st line of squash message:\n"
+ "\n\t%.*s"),
+ (int)(eol - buf.buf), buf.buf);
+
+ strbuf_addf(&header, "%c ", comment_line_char);
+ strbuf_addf(&header,
+ _("This is a combination of %d commits."), ++count);
+ strbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len);
+ strbuf_release(&header);
+ } 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"));
+
+ find_commit_subject(head_message, &body);
+ if (write_message(body, strlen(body),
+ rebase_path_fixup_msg(), 0)) {
+ unuse_commit_buffer(head_commit, head_message);
+ return error(_("cannot write '%s'"),
+ rebase_path_fixup_msg());
+ }
+
+ count = 2;
+ strbuf_addf(&buf, "%c ", comment_line_char);
+ strbuf_addf(&buf, _("This is a combination of %d commits."),
+ count);
+ strbuf_addf(&buf, "\n%c ", comment_line_char);
+ strbuf_addstr(&buf, _("This is the 1st commit message:"));
+ strbuf_addstr(&buf, "\n\n");
+ strbuf_addstr(&buf, 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));
+ find_commit_subject(message, &body);
+
+ if (command == TODO_SQUASH) {
+ unlink(rebase_path_fixup_msg());
+ strbuf_addf(&buf, "\n%c ", comment_line_char);
+ strbuf_addf(&buf, _("This is the commit message #%d:"), count);
+ strbuf_addstr(&buf, "\n\n");
+ strbuf_addstr(&buf, body);
+ } else if (command == TODO_FIXUP) {
+ strbuf_addf(&buf, "\n%c ", comment_line_char);
+ strbuf_addf(&buf, _("The commit message #%d will be skipped:"),
+ count);
+ strbuf_addstr(&buf, "\n\n");
+ strbuf_add_commented_lines(&buf, body, strlen(body));
+ } else
+ return error(_("unknown command: %d"), command);
+ unuse_commit_buffer(commit, message);
+
+ res = write_message(buf.buf, buf.len, rebase_path_squash_msg(), 0);
+ strbuf_release(&buf);
+ return res;
+}
+
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, unborn = 0, allow;
+ int res, unborn = 0, amend = 0, allow;
if (opts->no_commit) {
/*
@@ -720,7 +852,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);
@@ -779,6 +911,27 @@ 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);
@@ -834,8 +987,13 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
goto leave;
}
if (!opts->no_commit)
- res = run_git_commit(opts->edit ? NULL : git_path_merge_msg(),
- opts, allow, opts->edit, 0, 0);
+ res = run_git_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);
@@ -976,7 +1134,7 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list)
{
struct todo_item *item;
char *p = buf, *next_p;
- int i, res = 0;
+ int i, res = 0, fixup_okay = file_exists(rebase_path_done());
for (i = 1; *p; i++, p = next_p) {
char *eol = strchrnul(p, '\n');
@@ -991,8 +1149,16 @@ 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 (!is_noop(item->command))
+ fixup_okay = 1;
}
if (!todo_list->nr)
return error(_("no commits parsed."));
@@ -1435,6 +1601,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 };
@@ -1475,6 +1655,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 (!is_noop(todo_list->items[i].command))
+ break;
+ return 1;
+}
+
static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
{
int res = 0;
@@ -1490,9 +1685,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)
@@ -1503,6 +1704,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);
int saved = *end_of_arg;
@@ -1601,7 +1808,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)
--
2.11.0.rc3.windows.1
^ permalink raw reply related
* [PATCH v3 11/38] sequencer (rebase -i): implement the short commands
From: Johannes Schindelin @ 2017-01-02 15:27 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Kevin Daudt, Dennis Kaarsemaker, Stephan Beyer,
Jeff King
In-Reply-To: <cover.1483370556.git.johannes.schindelin@gmx.de>
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 <johannes.schindelin@gmx.de>
---
sequencer.c | 35 +++++++++++++++++++++--------------
1 file changed, 21 insertions(+), 14 deletions(-)
diff --git a/sequencer.c b/sequencer.c
index 6a939a10bd..29b944d724 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -678,20 +678,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 ((size_t)command < ARRAY_SIZE(todo_command_strings))
- return todo_command_strings[command];
+ if ((size_t)command < ARRAY_SIZE(todo_command_info))
+ return todo_command_info[command].str;
die("Unknown command: %d", command);
}
@@ -1087,12 +1090,16 @@ 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;
+ } else if (bol[1] == ' ' && *bol == todo_command_info[i].c) {
+ bol++;
item->command = i;
break;
}
- if (i >= ARRAY_SIZE(todo_command_strings))
+ if (i >= ARRAY_SIZE(todo_command_info))
return -1;
if (item->command == TODO_NOOP) {
@@ -1287,7 +1294,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))
--
2.11.0.rc3.windows.1
^ permalink raw reply related
* [PATCH v3 09/38] sequencer (rebase -i): write the 'done' file
From: Johannes Schindelin @ 2017-01-02 15:27 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Kevin Daudt, Dennis Kaarsemaker, Stephan Beyer,
Jeff King
In-Reply-To: <cover.1483370556.git.johannes.schindelin@gmx.de>
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 <johannes.schindelin@gmx.de>
---
sequencer.c | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/sequencer.c b/sequencer.c
index ddc4d144d7..8ea3d6aa94 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -41,6 +41,12 @@ static GIT_PATH_FUNC(rebase_path, "rebase-merge")
*/
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
* being rebased.
@@ -1296,6 +1302,23 @@ 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(_("failed to finalize '%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 (fd >= 0 && offset > prev_offset &&
+ write_in_full(fd, todo_list->buf.buf + prev_offset,
+ offset - prev_offset) < 0) {
+ close(fd);
+ return error_errno(_("could not write to '%s'"),
+ done_path);
+ }
+ if (fd >= 0)
+ close(fd);
+ }
return 0;
}
--
2.11.0.rc3.windows.1
^ permalink raw reply related
* [PATCH v3 08/38] sequencer (rebase -i): learn about the 'verbose' mode
From: Johannes Schindelin @ 2017-01-02 15:26 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Kevin Daudt, Dennis Kaarsemaker, Stephan Beyer,
Jeff King
In-Reply-To: <cover.1483370556.git.johannes.schindelin@gmx.de>
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 <johannes.schindelin@gmx.de>
---
sequencer.c | 28 ++++++++++++++++++++++++++++
sequencer.h | 1 +
2 files changed, 29 insertions(+)
diff --git a/sequencer.c b/sequencer.c
index e9c10d7fe5..ddc4d144d7 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -65,6 +65,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")
static inline int is_rebase_i(const struct replay_opts *opts)
{
@@ -1088,6 +1090,9 @@ static int read_populate_opts(struct replay_opts *opts)
}
strbuf_release(&buf);
+ if (file_exists(rebase_path_verbose()))
+ opts->verbose = 1;
+
return 0;
}
@@ -1491,9 +1496,32 @@ 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) {
+ struct rev_info log_tree_opt;
+ struct object_id orig, head;
+
+ memset(&log_tree_opt, 0, sizeof(log_tree_opt));
+ init_revisions(&log_tree_opt, NULL);
+ log_tree_opt.diff = 1;
+ log_tree_opt.diffopt.output_format =
+ DIFF_FORMAT_DIFFSTAT;
+ log_tree_opt.disable_stdin = 1;
+
+ if (read_oneliner(&buf, rebase_path_orig_head(), 0) &&
+ !get_sha1(buf.buf, orig.hash) &&
+ !get_sha1("HEAD", head.hash)) {
+ diff_tree_sha1(orig.hash, head.hash,
+ "", &log_tree_opt.diffopt);
+ log_tree_diff_flush(&log_tree_opt);
+ }
+ }
+ strbuf_release(&buf);
}
/*
diff --git a/sequencer.h b/sequencer.h
index cb21cfddee..f885b68395 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;
--
2.11.0.rc3.windows.1
^ permalink raw reply related
* [PATCH v3 07/38] sequencer (rebase -i): implement the 'exec' command
From: Johannes Schindelin @ 2017-01-02 15:26 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Kevin Daudt, Dennis Kaarsemaker, Stephan Beyer,
Jeff King
In-Reply-To: <cover.1483370556.git.johannes.schindelin@gmx.de>
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 <johannes.schindelin@gmx.de>
---
sequencer.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 57 insertions(+)
diff --git a/sequencer.c b/sequencer.c
index b138a3906c..e9c10d7fe5 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -18,6 +18,7 @@
#include "quote.h"
#include "trailer.h"
#include "log-tree.h"
+#include "wt-status.h"
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
@@ -632,6 +633,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
};
@@ -640,6 +643,7 @@ static const char *todo_command_strings[] = {
"pick",
"revert",
"edit",
+ "exec",
"noop"
};
@@ -938,6 +942,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';
@@ -1397,6 +1407,46 @@ 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 ? N_("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;
@@ -1425,6 +1475,13 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
item->arg, item->arg_len, opts, res,
!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 (!is_noop(item->command))
return error(_("unknown command %d"), item->command);
--
2.11.0.rc3.windows.1
^ permalink raw reply related
* [PATCH v3 06/38] sequencer (rebase -i): implement the 'edit' command
From: Johannes Schindelin @ 2017-01-02 15:26 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Kevin Daudt, Dennis Kaarsemaker, Stephan Beyer,
Jeff King
In-Reply-To: <cover.1483370556.git.johannes.schindelin@gmx.de>
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 <johannes.schindelin@gmx.de>
---
sequencer.c | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 114 insertions(+), 3 deletions(-)
diff --git a/sequencer.c b/sequencer.c
index 84f18e64e9..b138a3906c 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -17,6 +17,7 @@
#include "argv-array.h"
#include "quote.h"
#include "trailer.h"
+#include "log-tree.h"
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
@@ -45,6 +46,20 @@ static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo")
*/
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 abbreviated 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).
*/
@@ -616,6 +631,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
};
@@ -623,6 +639,7 @@ enum todo_command {
static const char *todo_command_strings[] = {
"pick",
"revert",
+ "edit",
"noop"
};
@@ -1302,9 +1319,87 @@ 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 *subject, *p;
+ int res = 0;
+
+ p = short_commit_name(commit);
+ if (write_message(p, strlen(p), rebase_path_stopped_sha(), 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)) {
+ const char *commit_buffer = get_commit_buffer(commit, NULL);
+ find_commit_subject(commit_buffer, &subject);
+ res |= write_message(subject, strlen(subject), buf.buf, 1);
+ unuse_commit_buffer(commit, commit_buffer);
+ }
+ strbuf_release(&buf);
+
+ return res;
+}
+
+static int intend_to_amend(void)
+{
+ unsigned char head[20];
+ char *p;
+
+ if (get_sha1("HEAD", head))
+ return error(_("cannot read HEAD"));
+
+ p = sha1_to_hex(head);
+ return write_message(p, strlen(p), rebase_path_amend(), 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)
@@ -1317,10 +1412,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);
- else if (!is_noop(item->command))
+ 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 (!is_noop(item->command))
return error(_("unknown command %d"), item->command);
todo_list->current++;
@@ -1328,6 +1433,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
--
2.11.0.rc3.windows.1
^ permalink raw reply related
* [PATCH v3 05/38] sequencer (rebase -i): implement the 'noop' command
From: Johannes Schindelin @ 2017-01-02 15:26 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Kevin Daudt, Dennis Kaarsemaker, Stephan Beyer,
Jeff King
In-Reply-To: <cover.1483370556.git.johannes.schindelin@gmx.de>
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 <johannes.schindelin@gmx.de>
---
sequencer.c | 39 ++++++++++++++++++++++++++++++++++++---
1 file changed, 36 insertions(+), 3 deletions(-)
diff --git a/sequencer.c b/sequencer.c
index 690460bc67..84f18e64e9 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -607,14 +607,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)
@@ -624,6 +633,10 @@ static const char *command_to_string(const enum todo_command command)
die("Unknown command: %d", command);
}
+static int is_noop(const enum todo_command command)
+{
+ return TODO_NOOP <= (size_t)command;
+}
static int do_pick_commit(enum todo_command command, struct commit *commit,
struct replay_opts *opts)
@@ -879,6 +892,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;
@@ -887,6 +908,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)
@@ -1289,7 +1317,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 (!is_noop(item->command))
+ return error(_("unknown command %d"), item->command);
+
todo_list->current++;
if (res)
return res;
--
2.11.0.rc3.windows.1
^ permalink raw reply related
* [PATCH v3 04/38] sequencer: support a new action: 'interactive rebase'
From: Johannes Schindelin @ 2017-01-02 15:26 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Kevin Daudt, Dennis Kaarsemaker, Stephan Beyer,
Jeff King
In-Reply-To: <cover.1483370556.git.johannes.schindelin@gmx.de>
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 <johannes.schindelin@gmx.de>
---
sequencer.c | 36 ++++++++++++++++++++++++++++++++----
sequencer.h | 3 ++-
2 files changed, 34 insertions(+), 5 deletions(-)
diff --git a/sequencer.c b/sequencer.c
index 720857beda..690460bc67 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -30,6 +30,14 @@ static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts")
static GIT_PATH_FUNC(git_path_head_file, "sequencer/head")
static GIT_PATH_FUNC(git_path_abort_safety_file, "sequencer/abort-safety")
+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
@@ -42,19 +50,22 @@ static GIT_PATH_FUNC(rebase_path_author_script, "rebase-merge/author-script")
*/
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();
}
@@ -122,7 +133,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 {
@@ -364,7 +383,9 @@ 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);
@@ -1198,6 +1219,13 @@ 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;
+ /*
+ * rebase -i writes "git-rebase-todo" without the currently executing
+ * command, appending it to "done" instead.
+ */
+ 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 7a513c576b..cb21cfddee 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 {
--
2.11.0.rc3.windows.1
^ permalink raw reply related
* [PATCH v3 03/38] sequencer: use a helper to find the commit message
From: Johannes Schindelin @ 2017-01-02 15:26 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Kevin Daudt, Dennis Kaarsemaker, Stephan Beyer,
Jeff King
In-Reply-To: <cover.1483370556.git.johannes.schindelin@gmx.de>
It is actually not safe to look for a commit message by looking for the
first empty line and skipping it.
The find_commit_subject() function looks more carefully, so let's use
it. Since we are interested in the entire commit message, we re-compute
the string length after verifying that the commit subject is not empty
(in which case the entire commit message would be empty, something that
should not happen but that we want to handle gracefully).
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
sequencer.c | 11 +++--------
1 file changed, 3 insertions(+), 8 deletions(-)
diff --git a/sequencer.c b/sequencer.c
index 3eededcb98..720857beda 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -703,14 +703,9 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
next = commit;
next_label = msg.label;
- /*
- * Append the commit log message to msgbuf; it starts
- * after the tree, parent, author, committer
- * information followed by "\n\n".
- */
- p = strstr(msg.message, "\n\n");
- if (p)
- strbuf_addstr(&msgbuf, skip_blank_lines(p + 2));
+ /* Append the commit log message to msgbuf. */
+ if (find_commit_subject(msg.message, &p))
+ strbuf_addstr(&msgbuf, p);
if (opts->record_origin) {
if (!has_conforming_footer(&msgbuf, NULL, 0))
--
2.11.0.rc3.windows.1
^ permalink raw reply related
* [PATCH v3 01/38] sequencer: avoid unnecessary curly braces
From: Johannes Schindelin @ 2017-01-02 15:26 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Kevin Daudt, Dennis Kaarsemaker, Stephan Beyer,
Jeff King
In-Reply-To: <cover.1483370556.git.johannes.schindelin@gmx.de>
This was noticed while addressing Junio Hamano's concern that some
"else" operators were on separate lines than the preceding closing
brace.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
sequencer.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/sequencer.c b/sequencer.c
index 9adb7bbf1d..23793db08b 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -632,9 +632,8 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
}
discard_cache();
- if (!commit->parents) {
+ if (!commit->parents)
parent = NULL;
- }
else if (commit->parents->next) {
/* Reverting or cherry-picking a merge commit */
int cnt;
--
2.11.0.rc3.windows.1
^ permalink raw reply related
* [PATCH v3 02/38] sequencer: move "else" keyword onto the same line as preceding brace
From: Johannes Schindelin @ 2017-01-02 15:26 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Kevin Daudt, Dennis Kaarsemaker, Stephan Beyer,
Jeff King
In-Reply-To: <cover.1483370556.git.johannes.schindelin@gmx.de>
It is the current coding style of the Git project to write
if (...) {
...
} else {
...
}
instead of putting the closing brace and the "else" keyword on separate
lines.
Pointed out by Junio Hamano.
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
sequencer.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/sequencer.c b/sequencer.c
index 23793db08b..3eededcb98 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -1070,8 +1070,7 @@ static int create_seq_dir(void)
error(_("a cherry-pick or revert is already in progress"));
advise(_("try \"git cherry-pick (--continue | --quit | --abort)\""));
return -1;
- }
- else if (mkdir(git_path_seq_dir(), 0777) < 0)
+ } else if (mkdir(git_path_seq_dir(), 0777) < 0)
return error_errno(_("could not create sequencer directory '%s'"),
git_path_seq_dir());
return 0;
--
2.11.0.rc3.windows.1
^ permalink raw reply related
* [PATCH v3 00/38] Teach the sequencer to act as rebase -i's backend
From: Johannes Schindelin @ 2017-01-02 15:25 UTC (permalink / raw)
To: git
Cc: Junio C Hamano, Kevin Daudt, Dennis Kaarsemaker, Stephan Beyer,
Jeff King
In-Reply-To: <cover.1481642927.git.johannes.schindelin@gmx.de>
This marks the count down to '3': two more patch series after this
(really tiny ones) and we have a faster rebase -i.
The idea of this patch series is to teach the sequencer to understand
all of the commands in `git-rebase-todo` scripts, to execute them and to
behave pretty much very the same as `git rebase -i --continue` when
called with the newly-introduced REPLAY_INTERACTIVE_REBASE mode.
Most of these patches should be pretty much straight-forward. When not,
I tried to make a point of describing enough background in the commit
message. Please feel free to point out where my explanations fall short.
Note that even after this patch series is applied, rebase -i is still
unaffected. It will require the next patch series which introduces the
rebase--helper that essentially implements `git rebase -i --continue` by
calling the sequencer with the appropriate options.
The final patch series will move a couple of pre- and post-processing
steps into the rebase--helper/sequencer (such as expanding/shrinking the
SHA-1s, reordering the fixup!/squash! lines, etc). This might sound like
a mere add-on, but it is essential for the speed improvements: those
stupid little processing steps really dominated the execution time in my
tests.
Apart from mostly cosmetic patches (and the occasional odd bug that I
fixed promptly), I used these patches since mid May to perform all of my
interactive rebases. In mid June, I had the idea to teach rebase -i to
run *both* scripted rebase and rebase--helper and to cross-validate the
results. This slowed down all my interactive rebases since, but helped
me catch three rather obscure bugs (e.g. that git commit --fixup unfolds
long onelines and rebase -i still finds the correct original commit).
This is all only to say that I am rather confident that the current code
does the job.
Since sending out v1, I integrated all of these patch series
into Git for Windows v2.10.0, where they have been live ever since, and
used by myself (also in a Linux VM, as Git for Windows' master branch
always builds also on Linux and passes the test suite, too).
Just to reiterate why I do all this: it speeds up the interactive rebase
substantially. Even with a not yet fully builtin rebase -i, but just the
part after the user edited the `git-rebase-todo` script.
The performance test I introduced to demonstrate this (p3404) shows a
speed-up of +380% here (i.e. roughly 5x), from ~8.8 seconds to ~1.8
seconds. This is on Windows, where the performance impact of avoiding
shell scripting is most noticable.
On MacOSX and on Linux, the speed-up is less pronounced, but still
noticable, at least if you trust Travis CI, which I abused to perform
that test for me. Check for yourself (searching for "3404.2") here:
https://travis-ci.org/git/git/builds/156295227. According to those logs,
p3404 is speeded up from ~0.45 seconds to ~0.12 seconds on Linux (read:
about 3.5x) and from ~1.7 seconds to ~0.5 seconds on MacOSX (read:
almost 4x).
Changes since v2:
- fixed a TRANSLATORS: comment
- added a comment to clarify why save_todo() does not write the current
command to git-rebase-todo
- fixed the comment for "stopped-sha" that said that a long commit name
is stored in that file, when it is in fact an abbreviated one
- consistently put the "else" keyword on the same line as the preceding
closing brace, if any (this adds two patches to the already large
patch series, sorry)
- completely revamped the update_squash_messages() function to make it
more obvious why it does not overflow its buffer, and to fix it when
using it with localised messages
- now the sequencer code uses find_commit_subject() consistently to find
the commit message (it does not use the subject's length returned by
the find_commit_subject() function, of course); this adds yet another
patch to this already large patch series, sorry.
- introduced an is_noop() function (as opposed to testing <= TODO_NOOP
manually) to make the code less magic.
- fixed two potential leaks of get_commit_buffer()'s returned buffer
- fixed leaks in the error code paths when trying to update the ref at
the end
- renamed read_author_script() into read_env_script() to reflect that
it would accept any environment setting, and made it much more
readable
- completely reworked the strategy how to suppress the output of
successful cherry-picks: instead of introducing
RUN_HIDE_STDERR_ON_SUCCESS, the code now uses pipe_command() to catch
the output itself, so that it can be shown in case of error
- replaced a spawned `diff-tree` command by code using the diff functions
directly
Johannes Schindelin (38):
sequencer: avoid unnecessary curly braces
sequencer: move "else" keyword onto the same line as preceding brace
sequencer: use a helper to find the commit message
sequencer: support a new action: 'interactive rebase'
sequencer (rebase -i): implement the 'noop' command
sequencer (rebase -i): implement the 'edit' command
sequencer (rebase -i): implement the 'exec' command
sequencer (rebase -i): learn about the 'verbose' mode
sequencer (rebase -i): write the 'done' file
sequencer (rebase -i): add support for the 'fixup' and 'squash'
commands
sequencer (rebase -i): implement the short commands
sequencer (rebase -i): write an author-script file
sequencer (rebase -i): allow continuing with staged changes
sequencer (rebase -i): remove CHERRY_PICK_HEAD when no longer needed
sequencer (rebase -i): skip some revert/cherry-pick specific code path
sequencer (rebase -i): the todo can be empty when continuing
sequencer (rebase -i): update refs after a successful rebase
sequencer (rebase -i): leave a patch upon error
sequencer (rebase -i): implement the 'reword' command
sequencer (rebase -i): allow fast-forwarding for edit/reword
sequencer (rebase -i): refactor setting the reflog message
sequencer (rebase -i): set the reflog message consistently
sequencer (rebase -i): copy commit notes at end
sequencer (rebase -i): record interrupted commits in rewritten, too
sequencer (rebase -i): run the post-rewrite hook, if needed
sequencer (rebase -i): respect the rebase.autostash setting
sequencer (rebase -i): respect strategy/strategy_opts settings
sequencer (rebase -i): allow rescheduling commands
sequencer (rebase -i): implement the 'drop' command
sequencer (rebase -i): differentiate between comments and 'noop'
sequencer: make reading author-script more elegant
sequencer: use run_command() directly
sequencer (rebase -i): show only failed `git commit`'s output
sequencer (rebase -i): show only failed cherry-picks' output
sequencer (rebase -i): suggest --edit-todo upon unknown command
sequencer (rebase -i): show the progress
sequencer (rebase -i): write the progress into files
sequencer (rebase -i): write out the final message
sequencer.c | 1105 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
sequencer.h | 4 +-
2 files changed, 1013 insertions(+), 96 deletions(-)
base-commit: e05806da9ec4aff8adfed142ab2a2b3b02e33c8c
Published-As: https://github.com/dscho/git/releases/tag/sequencer-i-v3
Fetch-It-Via: git fetch https://github.com/dscho/git sequencer-i-v3
Interdiff vs v2:
diff --git a/run-command.c b/run-command.c
index 5bb957afdd..ca905a9e80 100644
--- a/run-command.c
+++ b/run-command.c
@@ -589,29 +589,6 @@ 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 65a21ddd4e..dd1c78c28d 100644
--- a/run-command.h
+++ b/run-command.h
@@ -72,7 +72,6 @@ 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);
/*
diff --git a/sequencer.c b/sequencer.c
index c0aeb4f5f7..0e7d2ca5c8 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -102,7 +102,7 @@ static GIT_PATH_FUNC(rebase_path_author_script, "rebase-merge/author-script")
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.
+ * the abbreviated commit name of the corresponding patch.
*/
static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha")
/*
@@ -464,8 +464,7 @@ 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", "cherry-pick" or
+ /* TRANSLATORS: %s will be "revert", "cherry-pick" or
* "rebase -i".
*/
return error(_("%s: Unable to write new index file"),
@@ -524,8 +523,7 @@ static int write_author_script(const char *message)
/* Missing 'author' line? */
unlink(rebase_path_author_script());
return 0;
- }
- else if (skip_prefix(message, "author ", &message))
+ } else if (skip_prefix(message, "author ", &message))
break;
else if ((eol = strchr(message, '\n')))
message = eol + 1;
@@ -560,18 +558,17 @@ static int write_author_script(const char *message)
}
/*
- * Read the author-script file into an environment block, ready for use in
- * run_command(), that can be free()d afterwards.
+ * Read a list of environment variable assignments (such as the author-script
+ * file) into an environment block. Returns -1 on error, 0 otherwise.
*/
-static char **read_author_script(void)
+static int read_env_script(struct argv_array *env)
{
struct strbuf script = STRBUF_INIT;
int i, count = 0;
- char *p, *p2, **env;
- size_t env_size;
+ char *p, *p2;
if (strbuf_read_file(&script, rebase_path_author_script(), 256) <= 0)
- return NULL;
+ return -1;
for (p = script.buf; *p; p++)
if (skip_prefix(p, "'\\\\''", (const char **)&p2))
@@ -583,19 +580,12 @@ static char **read_author_script(void)
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;
+ argv_array_push(env, p);
p += strlen(p) + 1;
}
- env[count] = NULL;
- return env;
+ return 0;
}
static const char staged_changes_advice[] =
@@ -628,19 +618,18 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
int allow_empty, int edit, int amend,
int cleanup_commit_message)
{
- char **env = NULL;
- struct argv_array array;
- int opt = RUN_GIT_CMD, rc;
+ struct child_process cmd = CHILD_PROCESS_INIT;
const char *value;
+ cmd.git_cmd = 1;
+
if (is_rebase_i(opts)) {
if (!edit) {
- opt |= RUN_COMMAND_STDOUT_TO_STDERR;
- opt |= RUN_HIDE_STDERR_ON_SUCCESS;
+ cmd.stdout_to_stderr = 1;
+ cmd.err = -1;
}
- env = read_author_script();
- if (!env) {
+ if (read_env_script(&cmd.env_array)) {
const char *gpg_opt = gpg_sign_opt_quoted(opts);
return error(_(staged_changes_advice),
@@ -648,39 +637,47 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
}
}
- argv_array_init(&array);
- argv_array_push(&array, "commit");
- argv_array_push(&array, "-n");
+ argv_array_push(&cmd.args, "commit");
+ argv_array_push(&cmd.args, "-n");
if (amend)
- argv_array_push(&array, "--amend");
+ argv_array_push(&cmd.args, "--amend");
if (opts->gpg_sign)
- argv_array_pushf(&array, "-S%s", opts->gpg_sign);
+ argv_array_pushf(&cmd.args, "-S%s", opts->gpg_sign);
if (opts->signoff)
- argv_array_push(&array, "-s");
+ argv_array_push(&cmd.args, "-s");
if (defmsg)
- argv_array_pushl(&array, "-F", defmsg, NULL);
+ argv_array_pushl(&cmd.args, "-F", defmsg, NULL);
if (cleanup_commit_message)
- argv_array_push(&array, "--cleanup=strip");
+ argv_array_push(&cmd.args, "--cleanup=strip");
if (edit)
- argv_array_push(&array, "-e");
+ argv_array_push(&cmd.args, "-e");
else if (!cleanup_commit_message &&
!opts->signoff && !opts->record_origin &&
git_config_get_value("commit.cleanup", &value))
- argv_array_push(&array, "--cleanup=verbatim");
+ argv_array_push(&cmd.args, "--cleanup=verbatim");
if (allow_empty)
- argv_array_push(&array, "--allow-empty");
+ argv_array_push(&cmd.args, "--allow-empty");
if (opts->allow_empty_message)
- argv_array_push(&array, "--allow-empty-message");
+ argv_array_push(&cmd.args, "--allow-empty-message");
- rc = run_command_v_opt_cd_env(array.argv, opt, NULL,
- (const char *const *)env);
- argv_array_clear(&array);
- free(env);
+ if (cmd.err == -1) {
+ /* hide stderr on success */
+ struct strbuf buf = STRBUF_INIT;
+ int rc = pipe_command(&cmd,
+ NULL, 0,
+ /* stdout is already redirected */
+ NULL, 0,
+ &buf, 0);
+ if (rc)
+ fputs(buf.buf, stderr);
+ strbuf_release(&buf);
+ return rc;
+ }
- return rc;
+ return run_command(&cmd);
}
static int is_original_commit_empty(struct commit *commit)
@@ -786,6 +783,11 @@ static const char *command_to_string(const enum todo_command command)
die("Unknown command: %d", command);
}
+static int is_noop(const enum todo_command command)
+{
+ return TODO_NOOP <= command;
+}
+
static int is_fixup(enum todo_command command)
{
return command == TODO_FIXUP || command == TODO_SQUASH;
@@ -799,36 +801,33 @@ static int update_squash_messages(enum todo_command command,
const char *message, *body;
if (file_exists(rebase_path_squash_msg())) {
- char *p, *p2;
+ struct strbuf header = STRBUF_INIT;
+ char *eol, *p;
if (strbuf_read_file(&buf, rebase_path_squash_msg(), 2048) <= 0)
return error(_("could not read '%s'"),
rebase_path_squash_msg());
+ p = buf.buf + 1;
+ eol = strchrnul(buf.buf, '\n');
if (buf.buf[0] != comment_line_char ||
- !skip_prefix(buf.buf + 1, " This is a combination of ",
- (const char **)&p))
+ (p += strcspn(p, "0123456789\n")) == eol)
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);
+ (int)(eol - buf.buf), buf.buf);
+ count = strtol(p, NULL, 10);
- if (count < 1 || *p2 != ' ')
+ if (count < 1)
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 {
+ (int)(eol - buf.buf), buf.buf);
+
+ strbuf_addf(&header, "%c ", comment_line_char);
+ strbuf_addf(&header,
+ _("This is a combination of %d commits."), ++count);
+ strbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len);
+ strbuf_release(&header);
+ } else {
unsigned char head[20];
struct commit *head_commit;
const char *head_message, *body;
@@ -840,20 +839,22 @@ static int update_squash_messages(enum todo_command command,
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);
+ find_commit_subject(head_message, &body);
if (write_message(body, strlen(body),
- rebase_path_fixup_msg(), 0))
+ rebase_path_fixup_msg(), 0)) {
+ unuse_commit_buffer(head_commit, head_message);
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);
+ strbuf_addf(&buf, "%c ", comment_line_char);
+ strbuf_addf(&buf, _("This is a combination of %d commits."),
+ count);
+ strbuf_addf(&buf, "\n%c ", comment_line_char);
+ strbuf_addstr(&buf, _("This is the 1st commit message:"));
+ strbuf_addstr(&buf, "\n\n");
+ strbuf_addstr(&buf, body);
unuse_commit_buffer(head_commit, head_message);
}
@@ -861,25 +862,21 @@ static int update_squash_messages(enum todo_command command,
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);
+ find_commit_subject(message, &body);
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_addf(&buf, "\n%c ", comment_line_char);
+ strbuf_addf(&buf, _("This is the commit message #%d:"), count);
+ strbuf_addstr(&buf, "\n\n");
+ strbuf_addstr(&buf, body);
+ } else if (command == TODO_FIXUP) {
+ strbuf_addf(&buf, "\n%c ", comment_line_char);
+ strbuf_addf(&buf, _("The commit message #%d will be skipped:"),
+ count);
+ strbuf_addstr(&buf, "\n\n");
strbuf_add_commented_lines(&buf, body, strlen(body));
- }
- else
+ } else
return error(_("unknown command: %d"), command);
unuse_commit_buffer(commit, message);
@@ -956,9 +953,8 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
}
discard_cache();
- if (!commit->parents) {
+ if (!commit->parents)
parent = NULL;
- }
else if (commit->parents->next) {
/* Reverting or cherry-picking a merge commit */
int cnt;
@@ -1036,14 +1032,9 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
next = commit;
next_label = msg.label;
- /*
- * Append the commit log message to msgbuf; it starts
- * after the tree, parent, author, committer
- * information followed by "\n\n".
- */
- p = strstr(msg.message, "\n\n");
- if (p)
- strbuf_addstr(&msgbuf, skip_blank_lines(p + 2));
+ /* Append the commit log message to msgbuf. */
+ if (find_commit_subject(msg.message, &p))
+ strbuf_addstr(&msgbuf, p);
if (opts->record_origin) {
if (!has_conforming_footer(&msgbuf, NULL, 0))
@@ -1065,8 +1056,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
else if (file_exists(rebase_path_fixup_msg())) {
cleanup_commit_message = 1;
msg_file = rebase_path_fixup_msg();
- }
- else {
+ } else {
const char *dest = git_path("SQUASH_MSG");
unlink(dest);
if (copy_file(dest, rebase_path_squash_msg(), 0666))
@@ -1241,8 +1231,7 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)
if (skip_prefix(bol, todo_command_info[i].str, &bol)) {
item->command = i;
break;
- }
- else if (bol[1] == ' ' && *bol == todo_command_info[i].c) {
+ } else if (bol[1] == ' ' && *bol == todo_command_info[i].c) {
bol++;
item->command = i;
break;
@@ -1312,7 +1301,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 (!is_noop(item->command))
fixup_okay = 1;
}
@@ -1533,8 +1522,7 @@ static int create_seq_dir(void)
error(_("a cherry-pick or revert is already in progress"));
advise(_("try \"git cherry-pick (--continue | --quit | --abort)\""));
return -1;
- }
- else if (mkdir(git_path_seq_dir(), 0777) < 0)
+ } else if (mkdir(git_path_seq_dir(), 0777) < 0)
return error_errno(_("could not create sequencer directory '%s'"),
git_path_seq_dir());
return 0;
@@ -1667,6 +1655,10 @@ 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;
+ /*
+ * rebase -i writes "git-rebase-todo" without the currently executing
+ * command, appending it to "done" instead.
+ */
if (is_rebase_i(opts))
next++;
@@ -1739,7 +1731,7 @@ 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, *p;
+ const char *subject, *p;
int res = 0;
p = short_commit_name(commit);
@@ -1766,6 +1758,7 @@ static int make_patch(struct commit *commit, struct replay_opts *opts)
strbuf_addf(&buf, "%s/message", get_dir(opts));
if (!file_exists(buf.buf)) {
+ const char *commit_buffer = get_commit_buffer(commit, NULL);
find_commit_subject(commit_buffer, &subject);
res |= write_message(subject, strlen(subject), buf.buf, 1);
unuse_commit_buffer(commit, commit_buffer);
@@ -1805,8 +1798,7 @@ static int error_with_patch(struct commit *commit,
"Once you are satisfied with your changes, run\n"
"\n"
" git rebase --continue\n", gpg_sign_opt_quoted(opts));
- }
- else if (exit_code)
+ } else if (exit_code)
fprintf(stderr, "Could not apply %s... %.*s\n",
short_commit_name(commit), subject_len, subject);
@@ -1854,8 +1846,7 @@ static int do_exec(const char *command_line)
if (status == 127)
/* command not found */
status = 1;
- }
- else if (dirty) {
+ } 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"
@@ -1878,7 +1869,7 @@ static int is_final_fixup(struct todo_list *todo_list)
while (++i < todo_list->nr)
if (is_fixup(todo_list->items[i].command))
return 0;
- else if (todo_list->items[i].command < TODO_NOOP)
+ else if (!is_noop(todo_list->items[i].command))
break;
return 1;
}
@@ -1888,7 +1879,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 (!is_noop(todo_list->items[i].command))
return todo_list->items[i].command;
return -1;
@@ -2021,21 +2012,18 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
intend_to_amend();
return error_failed_squash(item->commit, opts,
item->arg_len, item->arg);
- }
- else if (res && is_rebase_i(opts))
+ } else if (res && is_rebase_i(opts))
return res | error_with_patch(item->commit,
item->arg, item->arg_len, opts, res,
item->command == TODO_REWORD);
- }
- else if (item->command == TODO_EXEC) {
+ } 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)
+ } else if (!is_noop(item->command))
return error(_("unknown command %d"), item->command);
todo_list->current++;
@@ -2055,40 +2043,60 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
starts_with(head_ref.buf, "refs/")) {
const char *msg;
unsigned char head[20], orig[20];
-
- if (get_sha1("HEAD", head))
- return error(_("cannot read HEAD"));
+ int res;
+
+ if (get_sha1("HEAD", head)) {
+ res = error(_("cannot read HEAD"));
+cleanup_head_ref:
+ strbuf_release(&head_ref);
+ strbuf_release(&buf);
+ return res;
+ }
if (!read_oneliner(&buf, rebase_path_orig_head(), 0) ||
- get_sha1_hex(buf.buf, orig))
- return error(_("could not read orig-head"));
- if (!read_oneliner(&buf, rebase_path_onto(), 0))
- return error(_("could not read 'onto'"));
+ get_sha1_hex(buf.buf, orig)) {
+ res = error(_("could not read orig-head"));
+ goto cleanup_head_ref;
+ }
+ if (!read_oneliner(&buf, rebase_path_onto(), 0)) {
+ res = error(_("could not read 'onto'"));
+ goto cleanup_head_ref;
+ }
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"),
+ REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) {
+ res = error(_("could not update %s"),
head_ref.buf);
+ goto cleanup_head_ref;
+ }
msg = reflog_message(opts, "finish", "returning to %s",
head_ref.buf);
- if (create_symref("HEAD", head_ref.buf, msg))
- return error(_("could not update HEAD to %s"),
+ if (create_symref("HEAD", head_ref.buf, msg)) {
+ res = error(_("could not update HEAD to %s"),
head_ref.buf);
+ goto cleanup_head_ref;
+ }
strbuf_reset(&buf);
}
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);
+ struct rev_info log_tree_opt;
+ struct object_id orig, head;
+
+ memset(&log_tree_opt, 0, sizeof(log_tree_opt));
+ init_revisions(&log_tree_opt, NULL);
+ log_tree_opt.diff = 1;
+ log_tree_opt.diffopt.output_format =
+ DIFF_FORMAT_DIFFSTAT;
+ log_tree_opt.disable_stdin = 1;
+
+ if (read_oneliner(&buf, rebase_path_orig_head(), 0) &&
+ !get_sha1(buf.buf, orig.hash) &&
+ !get_sha1("HEAD", head.hash)) {
+ diff_tree_sha1(orig.hash, head.hash,
+ "", &log_tree_opt.diffopt);
+ log_tree_diff_flush(&log_tree_opt);
+ }
}
flush_rewritten_pending();
if (!stat(rebase_path_rewritten_list(), &st) &&
@@ -2195,8 +2203,7 @@ int sequencer_continue(struct replay_opts *opts)
if (is_rebase_i(opts)) {
if (commit_staged_changes(opts))
return -1;
- }
- else 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;
@@ -2216,8 +2223,7 @@ int sequencer_continue(struct replay_opts *opts)
goto release_todo_list;
}
todo_list.current++;
- }
- else if (file_exists(rebase_path_stopped_sha())) {
+ } else if (file_exists(rebase_path_stopped_sha())) {
struct strbuf buf = STRBUF_INIT;
struct object_id oid;
--
2.11.0.rc3.windows.1
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox