Merge 'prepare-sequencer' into HEAD

This commit is contained in:
Johannes Schindelin
2016-10-04 11:41:29 +02:00
4 changed files with 469 additions and 240 deletions

View File

@@ -173,7 +173,7 @@ static void determine_whence(struct wt_status *s)
whence = FROM_MERGE;
else if (file_exists(git_path_cherry_pick_head())) {
whence = FROM_CHERRY_PICK;
if (file_exists(git_path(SEQ_DIR)))
if (file_exists(git_path_seq_dir()))
sequencer_in_use = 1;
}
else

View File

@@ -71,7 +71,7 @@ static void verify_opt_compatible(const char *me, const char *base_opt, ...)
die(_("%s: %s cannot be used with %s"), me, this_opt, base_opt);
}
static void parse_args(int argc, const char **argv, struct replay_opts *opts)
static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)
{
const char * const * usage_str = revert_or_cherry_pick_usage(opts);
const char *me = action_name(opts);
@@ -115,25 +115,15 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
if (opts->keep_redundant_commits)
opts->allow_empty = 1;
/* Set the subcommand */
if (cmd == 'q')
opts->subcommand = REPLAY_REMOVE_STATE;
else if (cmd == 'c')
opts->subcommand = REPLAY_CONTINUE;
else if (cmd == 'a')
opts->subcommand = REPLAY_ROLLBACK;
else
opts->subcommand = REPLAY_NONE;
/* Check for incompatible command line arguments */
if (opts->subcommand != REPLAY_NONE) {
if (cmd) {
char *this_operation;
if (opts->subcommand == REPLAY_REMOVE_STATE)
if (cmd == 'q')
this_operation = "--quit";
else if (opts->subcommand == REPLAY_CONTINUE)
else if (cmd == 'c')
this_operation = "--continue";
else {
assert(opts->subcommand == REPLAY_ROLLBACK);
assert(cmd == 'a');
this_operation = "--abort";
}
@@ -156,7 +146,7 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
"--edit", opts->edit,
NULL);
if (opts->subcommand != REPLAY_NONE) {
if (cmd) {
opts->revs = NULL;
} else {
struct setup_revision_opt s_r_opt;
@@ -174,20 +164,26 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
if (argc > 1)
usage_with_options(usage_str, options);
if (cmd == 'q')
return sequencer_remove_state(opts);
if (cmd == 'c')
return sequencer_continue(opts);
if (cmd == 'a')
return sequencer_rollback(opts);
return sequencer_pick_revisions(opts);
}
int cmd_revert(int argc, const char **argv, const char *prefix)
{
struct replay_opts opts;
struct replay_opts opts = REPLAY_OPTS_INIT;
int res;
memset(&opts, 0, sizeof(opts));
if (isatty(0))
opts.edit = 1;
opts.action = REPLAY_REVERT;
git_config(git_default_config, NULL);
parse_args(argc, argv, &opts);
res = sequencer_pick_revisions(&opts);
res = run_sequencer(argc, argv, &opts);
if (res < 0)
die(_("revert failed"));
return res;
@@ -195,14 +191,12 @@ int cmd_revert(int argc, const char **argv, const char *prefix)
int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
{
struct replay_opts opts;
struct replay_opts opts = REPLAY_OPTS_INIT;
int res;
memset(&opts, 0, sizeof(opts));
opts.action = REPLAY_PICK;
git_config(git_default_config, NULL);
parse_args(argc, argv, &opts);
res = sequencer_pick_revisions(&opts);
res = run_sequencer(argc, argv, &opts);
if (res < 0)
die(_("cherry-pick failed"));
return res;

View File

@@ -15,16 +15,46 @@
#include "merge-recursive.h"
#include "refs.h"
#include "argv-array.h"
#include "quote.h"
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
const char sign_off_header[] = "Signed-off-by: ";
static const char cherry_picked_prefix[] = "(cherry picked from commit ";
static GIT_PATH_FUNC(git_path_todo_file, SEQ_TODO_FILE)
static GIT_PATH_FUNC(git_path_opts_file, SEQ_OPTS_FILE)
static GIT_PATH_FUNC(git_path_seq_dir, SEQ_DIR)
static GIT_PATH_FUNC(git_path_head_file, SEQ_HEAD_FILE)
GIT_PATH_FUNC(git_path_seq_dir, "sequencer")
static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo")
static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts")
static GIT_PATH_FUNC(git_path_head_file, "sequencer/head")
/*
* A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
* GIT_AUTHOR_DATE that will be used for the commit that is currently
* being rebased.
*/
static GIT_PATH_FUNC(rebase_path_author_script, "rebase-merge/author-script")
/*
* The following files are written by git-rebase just after parsing the
* command-line (and are only consumed, not modified, by the sequencer).
*/
static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt")
/* We will introduce the 'interactive rebase' mode later */
static inline int is_rebase_i(const struct replay_opts *opts)
{
return 0;
}
static const char *get_dir(const struct replay_opts *opts)
{
return git_path_seq_dir();
}
static const char *get_todo_path(const struct replay_opts *opts)
{
return git_path_todo_file();
}
static int is_rfc2822_line(const char *buf, int len)
{
@@ -108,18 +138,45 @@ static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob,
return 1;
}
static void remove_sequencer_state(void)
static const char *gpg_sign_opt_quoted(struct replay_opts *opts)
{
struct strbuf seq_dir = STRBUF_INIT;
static struct strbuf buf = STRBUF_INIT;
strbuf_addstr(&seq_dir, git_path(SEQ_DIR));
remove_dir_recursively(&seq_dir, 0);
strbuf_release(&seq_dir);
strbuf_reset(&buf);
if (opts->gpg_sign)
sq_quotef(&buf, "-S%s", opts->gpg_sign);
return buf.buf;
}
void *sequencer_entrust(struct replay_opts *opts, void *to_free)
{
ALLOC_GROW(opts->owned, opts->owned_nr + 1, opts->owned_alloc);
opts->owned[opts->owned_nr++] = to_free;
return to_free;
}
int sequencer_remove_state(struct replay_opts *opts)
{
struct strbuf dir = STRBUF_INIT;
int i;
for (i = 0; i < opts->owned_nr; i++)
free(opts->owned[i]);
free(opts->owned);
free(opts->xopts);
strbuf_addf(&dir, "%s", get_dir(opts));
remove_dir_recursively(&dir, 0);
strbuf_release(&dir);
return 0;
}
static const char *action_name(const struct replay_opts *opts)
{
return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick";
return opts->action == REPLAY_REVERT ? N_("revert") : N_("cherry-pick");
}
struct commit_message {
@@ -129,13 +186,18 @@ struct commit_message {
const char *message;
};
static const char *short_commit_name(struct commit *commit)
{
return find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV);
}
static int get_message(struct commit *commit, struct commit_message *out)
{
const char *abbrev, *subject;
int subject_len;
out->message = logmsg_reencode(commit, NULL, get_commit_output_encoding());
abbrev = find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV);
abbrev = short_commit_name(commit);
subject_len = find_commit_subject(out->message, &subject);
@@ -180,22 +242,68 @@ static void print_advice(int show_hint, struct replay_opts *opts)
}
}
static int write_message(struct strbuf *msgbuf, const char *filename)
static int write_with_lock_file(const char *filename,
const void *buf, size_t len, int append_eol)
{
static struct lock_file msg_file;
int msg_fd = hold_lock_file_for_update(&msg_file, filename, 0);
if (msg_fd < 0)
return error_errno(_("Could not lock '%s'"), filename);
if (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0)
return error_errno(_("Could not write to %s"), filename);
strbuf_release(msgbuf);
if (write_in_full(msg_fd, buf, len) < 0)
return error_errno(_("Could not write to '%s'"), filename);
if (append_eol && write(msg_fd, "\n", 1) < 0)
return error_errno(_("Could not write eol to '%s"), filename);
if (commit_lock_file(&msg_file) < 0)
return error(_("Error wrapping up %s."), filename);
return error(_("Error wrapping up '%s'."), filename);
return 0;
}
static int write_message(struct strbuf *msgbuf, const char *filename)
{
int res = write_with_lock_file(filename, msgbuf->buf, msgbuf->len, 0);
strbuf_release(msgbuf);
return res;
}
static int write_file_gently(const char *filename,
const char *text, int append_eol)
{
return write_with_lock_file(filename, text, strlen(text), append_eol);
}
/*
* Reads a file that was presumably written by a shell script, i.e.
* with an end-of-line marker that needs to be stripped.
*
* Returns 1 if the file was read, 0 if it could not be read or does not exist.
*/
static int read_oneliner(struct strbuf *buf,
const char *path, int skip_if_empty)
{
int orig_len = buf->len;
if (!file_exists(path))
return 0;
if (strbuf_read_file(buf, path, 0) < 0) {
warning_errno("could not read '%s'", path);
return 0;
}
if (buf->len > orig_len && buf->buf[buf->len - 1] == '\n') {
if (--buf->len > orig_len && buf->buf[buf->len - 1] == '\r')
--buf->len;
buf->buf[buf->len] = '\0';
}
if (skip_if_empty && buf->len == orig_len)
return 0;
return 1;
}
static struct tree *empty_tree(void)
{
return lookup_tree(EMPTY_TREE_SHA1_BIN);
@@ -204,13 +312,10 @@ static struct tree *empty_tree(void)
static int error_dirty_index(struct replay_opts *opts)
{
if (read_cache_unmerged())
return error_resolve_conflict(action_name(opts));
return error_resolve_conflict(_(action_name(opts)));
/* Different translation strings for cherry-pick and revert */
if (opts->action == REPLAY_PICK)
error(_("Your local changes would be overwritten by cherry-pick."));
else
error(_("Your local changes would be overwritten by revert."));
error(_("Your local changes would be overwritten by %s."),
_(action_name(opts)));
if (advice_commit_before_merge)
advise(_("Commit your changes or stash them to proceed."));
@@ -228,7 +333,7 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from,
if (checkout_fast_forward(from, to, 1))
return -1; /* the callee should have complained already */
strbuf_addf(&sb, _("%s: fast-forward"), action_name(opts));
strbuf_addf(&sb, _("%s: fast-forward"), _(action_name(opts)));
transaction = ref_transaction_begin(&err);
if (!transaction ||
@@ -304,7 +409,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
write_locked_index(&the_index, &index_lock, COMMIT_LOCK))
/* TRANSLATORS: %s will be "revert" or "cherry-pick" */
return error(_("%s: Unable to write new index file"),
action_name(opts));
_(action_name(opts)));
rollback_lock_file(&index_lock);
if (opts->signoff)
@@ -347,36 +452,100 @@ static int is_index_unchanged(void)
return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.oid.hash);
}
static char **read_author_script(void)
{
struct strbuf script = STRBUF_INIT;
int i, count = 0;
char *p, *p2, **env;
size_t env_size;
if (strbuf_read_file(&script, rebase_path_author_script(), 256) <= 0)
return NULL;
for (p = script.buf; *p; p++)
if (skip_prefix(p, "'\\\\''", (const char **)&p2))
strbuf_splice(&script, p - script.buf, p2 - p, "'", 1);
else if (*p == '\'')
strbuf_splice(&script, p-- - script.buf, 1, "", 0);
else if (*p == '\n') {
*p = '\0';
count++;
}
env_size = (count + 1) * sizeof(*env);
strbuf_grow(&script, env_size);
memmove(script.buf + env_size, script.buf, script.len);
p = script.buf + env_size;
env = (char **)strbuf_detach(&script, NULL);
for (i = 0; i < count; i++) {
env[i] = p;
p += strlen(p) + 1;
}
env[count] = NULL;
return env;
}
/*
* If we are cherry-pick, and if the merge did not result in
* hand-editing, we will hit this commit and inherit the original
* author date and name.
*
* If we are revert, or if our cherry-pick results in a hand merge,
* we had better say that the current user is responsible for that.
*
* An exception is when sequencer_commit() is called during an
* interactive rebase: in that case, we will want to retain the
* author metadata.
*/
static int run_git_commit(const char *defmsg, struct replay_opts *opts,
int allow_empty)
int sequencer_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 rc;
const char *value;
if (is_rebase_i(opts)) {
env = read_author_script();
if (!env) {
const char *gpg_opt = gpg_sign_opt_quoted(opts);
return error("You have staged changes in your working "
"tree. If these changes are meant to be\n"
"squashed into the previous commit, run:\n\n"
" git commit --amend %s\n\n"
"If they are meant to go into a new commit, "
"run:\n\n"
" git commit %s\n\n"
"In both cases, once you're done, continue "
"with:\n\n"
" git rebase --continue\n", gpg_opt, gpg_opt);
}
}
argv_array_init(&array);
argv_array_push(&array, "commit");
argv_array_push(&array, "-n");
if (amend)
argv_array_push(&array, "--amend");
if (opts->gpg_sign)
argv_array_pushf(&array, "-S%s", opts->gpg_sign);
if (opts->signoff)
argv_array_push(&array, "-s");
if (!opts->edit) {
argv_array_push(&array, "-F");
argv_array_push(&array, defmsg);
if (!opts->signoff &&
!opts->record_origin &&
git_config_get_value("commit.cleanup", &value))
argv_array_push(&array, "--cleanup=verbatim");
}
if (defmsg)
argv_array_pushl(&array, "-F", defmsg, NULL);
if (cleanup_commit_message)
argv_array_push(&array, "--cleanup=strip");
if (edit)
argv_array_push(&array, "-e");
else if (!cleanup_commit_message &&
!opts->signoff && !opts->record_origin &&
git_config_get_value("commit.cleanup", &value))
argv_array_push(&array, "--cleanup=verbatim");
if (allow_empty)
argv_array_push(&array, "--allow-empty");
@@ -384,8 +553,11 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
if (opts->allow_empty_message)
argv_array_push(&array, "--allow-empty-message");
rc = run_command_v_opt(array.argv, RUN_GIT_CMD);
rc = run_command_v_opt_cd_env(array.argv, RUN_GIT_CMD, NULL,
(const char *const *)env);
argv_array_clear(&array);
free(env);
return rc;
}
@@ -447,14 +619,33 @@ static int allow_empty(struct replay_opts *opts, struct commit *commit)
return 1;
}
static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
enum todo_command {
TODO_PICK = 0,
TODO_REVERT
};
static const char *todo_command_strings[] = {
"pick",
"revert"
};
static const char *command_to_string(const enum todo_command command)
{
if (command < ARRAY_SIZE(todo_command_strings))
return todo_command_strings[command];
die("Unknown command: %d", command);
}
static int do_pick_commit(enum todo_command command, struct commit *commit,
struct replay_opts *opts)
{
unsigned char head[20];
struct commit *base, *next, *parent;
const char *base_label, *next_label;
struct commit_message msg = { NULL, NULL, NULL, NULL };
struct strbuf msgbuf = STRBUF_INIT;
int res, unborn = 0, allow;
int res = 0, unborn = 0, allow;
if (opts->no_commit) {
/*
@@ -506,10 +697,9 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
return fast_forward_to(commit->object.oid.hash, head, unborn, opts);
if (parent && parse_commit(parent) < 0)
/* TRANSLATORS: The first %s will be "revert" or
"cherry-pick", the second %s a SHA1 */
return error(_("%s: cannot parse parent commit %s"),
action_name(opts), oid_to_hex(&parent->object.oid));
command_to_string(command),
oid_to_hex(&parent->object.oid));
if (get_message(commit, &msg) != 0)
return error(_("Cannot get commit message for %s"),
@@ -522,7 +712,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
* reverse of it if we are revert.
*/
if (opts->action == REPLAY_REVERT) {
if (command == TODO_REVERT) {
base = commit;
base_label = msg.label;
next = parent;
@@ -563,8 +753,8 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
}
}
if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REPLAY_REVERT) {
res = do_recursive_merge(base, next, base_label, next_label,
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)
return res;
@@ -573,7 +763,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
struct commit_list *common = NULL;
struct commit_list *remotes = NULL;
res = write_message(&msgbuf, git_path_merge_msg());
res |= write_message(&msgbuf, git_path_merge_msg());
commit_list_insert(base, &common);
commit_list_insert(next, &remotes);
@@ -589,21 +779,20 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
* However, if the merge did not even start, then we don't want to
* write it at all.
*/
if (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1) &&
if (command == TODO_PICK && !opts->no_commit && (res == 0 || res == 1) &&
update_ref(NULL, "CHERRY_PICK_HEAD", commit->object.oid.hash, NULL,
REF_NODEREF, UPDATE_REFS_MSG_ON_ERR))
res = -1;
if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1) &&
if (command == TODO_REVERT && ((opts->no_commit && res == 0) || res == 1) &&
update_ref(NULL, "REVERT_HEAD", commit->object.oid.hash, NULL,
REF_NODEREF, UPDATE_REFS_MSG_ON_ERR))
res = -1;
if (res) {
error(opts->action == REPLAY_REVERT
error(command == TODO_REVERT
? _("could not revert %s... %s")
: _("could not apply %s... %s"),
find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV),
msg.subject);
short_commit_name(commit), msg.subject);
print_advice(res == 1, opts);
rerere(opts->allow_rerere_auto);
goto leave;
@@ -611,11 +800,13 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
allow = allow_empty(opts, commit);
if (allow < 0) {
res = allow;
res |= allow;
goto leave;
}
if (!opts->no_commit)
res = run_git_commit(git_path_merge_msg(), opts, allow);
res |= sequencer_commit(opts->edit ?
NULL : git_path_merge_msg(),
opts, allow, opts->edit, 0, 0);
leave:
free_message(commit, &msg);
@@ -647,133 +838,146 @@ static int read_and_refresh_cache(struct replay_opts *opts)
if (read_index_preload(&the_index, NULL) < 0) {
rollback_lock_file(&index_lock);
return error(_("git %s: failed to read the index"),
action_name(opts));
_(action_name(opts)));
}
refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
if (the_index.cache_changed && index_fd >= 0) {
if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) {
rollback_lock_file(&index_lock);
return error(_("git %s: failed to refresh the index"),
action_name(opts));
_(action_name(opts)));
}
}
rollback_lock_file(&index_lock);
return 0;
}
static int format_todo(struct strbuf *buf, struct commit_list *todo_list,
struct replay_opts *opts)
{
struct commit_list *cur = NULL;
const char *sha1_abbrev = NULL;
const char *action_str = opts->action == REPLAY_REVERT ? "revert" : "pick";
const char *subject;
int subject_len;
struct todo_item {
enum todo_command command;
struct commit *commit;
const char *arg;
int arg_len;
size_t offset_in_buf;
};
for (cur = todo_list; cur; cur = cur->next) {
const char *commit_buffer = get_commit_buffer(cur->item, NULL);
sha1_abbrev = find_unique_abbrev(cur->item->object.oid.hash, DEFAULT_ABBREV);
subject_len = find_commit_subject(commit_buffer, &subject);
strbuf_addf(buf, "%s %s %.*s\n", action_str, sha1_abbrev,
subject_len, subject);
unuse_commit_buffer(cur->item, commit_buffer);
}
return 0;
struct todo_list {
struct strbuf buf;
struct todo_item *items;
int nr, alloc, current;
};
#define TODO_LIST_INIT { STRBUF_INIT }
static void todo_list_release(struct todo_list *todo_list)
{
strbuf_release(&todo_list->buf);
free(todo_list->items);
todo_list->items = NULL;
todo_list->nr = todo_list->alloc = 0;
}
static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts *opts)
struct todo_item *append_new_todo(struct todo_list *todo_list)
{
ALLOC_GROW(todo_list->items, todo_list->nr + 1, todo_list->alloc);
return todo_list->items + todo_list->nr++;
}
static int parse_insn_line(struct todo_item *item, const char *bol, char *eol)
{
unsigned char commit_sha1[20];
enum replay_action action;
char *end_of_object_name;
int saved, status, padding;
int i, saved, status, padding;
if (starts_with(bol, "pick")) {
action = REPLAY_PICK;
bol += strlen("pick");
} else if (starts_with(bol, "revert")) {
action = REPLAY_REVERT;
bol += strlen("revert");
} else
return NULL;
/* left-trim */
bol += strspn(bol, " \t");
for (i = 0; i < ARRAY_SIZE(todo_command_strings); i++)
if (skip_prefix(bol, todo_command_strings[i], &bol)) {
item->command = i;
break;
}
if (i >= ARRAY_SIZE(todo_command_strings))
return -1;
/* Eat up extra spaces/ tabs before object name */
padding = strspn(bol, " \t");
if (!padding)
return NULL;
return -1;
bol += padding;
end_of_object_name = bol + strcspn(bol, " \t\n");
end_of_object_name = (char *) bol + strcspn(bol, " \t\n");
saved = *end_of_object_name;
*end_of_object_name = '\0';
status = get_sha1(bol, commit_sha1);
*end_of_object_name = saved;
/*
* Verify that the action matches up with the one in
* opts; we don't support arbitrary instructions
*/
if (action != opts->action) {
if (action == REPLAY_REVERT)
error((opts->action == REPLAY_REVERT)
? _("Cannot revert during another revert.")
: _("Cannot revert during a cherry-pick."));
else
error((opts->action == REPLAY_REVERT)
? _("Cannot cherry-pick during a revert.")
: _("Cannot cherry-pick during another cherry-pick."));
return NULL;
}
item->arg = end_of_object_name + strspn(end_of_object_name, " \t");
item->arg_len = (int)(eol - item->arg);
if (status < 0)
return NULL;
return -1;
return lookup_commit_reference(commit_sha1);
item->commit = lookup_commit_reference(commit_sha1);
return !item->commit;
}
static int parse_insn_buffer(char *buf, struct commit_list **todo_list,
struct replay_opts *opts)
static int parse_insn_buffer(char *buf, struct todo_list *todo_list)
{
struct commit_list **next = todo_list;
struct commit *commit;
struct todo_item *item;
char *p = buf;
int i;
int i, res = 0;
for (i = 1; *p; i++) {
char *eol = strchrnul(p, '\n');
commit = parse_insn_line(p, eol, opts);
if (!commit)
return error(_("Could not parse line %d."), i);
next = commit_list_append(commit, next);
item = append_new_todo(todo_list);
item->offset_in_buf = p - todo_list->buf.buf;
if (parse_insn_line(item, p, eol)) {
res |= error(_("Invalid line %d: %.*s"),
i, (int)(eol - p), p);
item->command = -1;
}
p = *eol ? eol + 1 : eol;
}
if (!*todo_list)
if (!todo_list->nr)
return error(_("No commits parsed."));
return 0;
return res;
}
static int read_populate_todo(struct commit_list **todo_list,
static int read_populate_todo(struct todo_list *todo_list,
struct replay_opts *opts)
{
struct strbuf buf = STRBUF_INIT;
const char *todo_file = get_todo_path(opts);
int fd, res;
fd = open(git_path_todo_file(), O_RDONLY);
strbuf_reset(&todo_list->buf);
fd = open(todo_file, O_RDONLY);
if (fd < 0)
return error_errno(_("Could not open %s"),
git_path_todo_file());
if (strbuf_read(&buf, fd, 0) < 0) {
return error_errno(_("Could not open '%s'"), todo_file);
if (strbuf_read(&todo_list->buf, fd, 0) < 0) {
close(fd);
strbuf_release(&buf);
return error(_("Could not read %s."), git_path_todo_file());
return error(_("Could not read '%s'."), todo_file);
}
close(fd);
res = parse_insn_buffer(buf.buf, todo_list, opts);
strbuf_release(&buf);
res = parse_insn_buffer(todo_list->buf.buf, todo_list);
if (res)
return error(_("Unusable instruction sheet: %s"),
git_path_todo_file());
return error(_("Unusable instruction sheet: '%s'"), todo_file);
if (!is_rebase_i(opts)) {
enum todo_command valid =
opts->action == REPLAY_PICK ? TODO_PICK : TODO_REVERT;
int i;
for (i = 0; i < todo_list->nr; i++)
if (valid == todo_list->items[i].command)
continue;
else if (valid == TODO_PICK)
return error(_("Cannot cherry-pick during a revert."));
else
return error(_("Cannot revert during a cherry-pick."));
}
return 0;
}
@@ -796,13 +1000,18 @@ static int populate_opts_cb(const char *key, const char *value, void *data)
opts->allow_ff = git_config_bool_or_int(key, value, &error_flag);
else if (!strcmp(key, "options.mainline"))
opts->mainline = git_config_int(key, value);
else if (!strcmp(key, "options.strategy"))
else if (!strcmp(key, "options.strategy")) {
git_config_string(&opts->strategy, key, value);
else if (!strcmp(key, "options.gpg-sign"))
sequencer_entrust(opts, (char *) opts->strategy);
}
else if (!strcmp(key, "options.gpg-sign")) {
git_config_string(&opts->gpg_sign, key, value);
sequencer_entrust(opts, (char *) opts->gpg_sign);
}
else if (!strcmp(key, "options.strategy-option")) {
ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc);
opts->xopts[opts->xopts_nr++] = xstrdup(value);
opts->xopts[opts->xopts_nr++] =
sequencer_entrust(opts, xstrdup(value));
} else
return error(_("Invalid key: %s"), key);
@@ -812,8 +1021,24 @@ static int populate_opts_cb(const char *key, const char *value, void *data)
return 0;
}
static int read_populate_opts(struct replay_opts **opts)
static int read_populate_opts(struct replay_opts *opts)
{
if (is_rebase_i(opts)) {
struct strbuf buf = STRBUF_INIT;
if (read_oneliner(&buf, rebase_path_gpg_sign_opt(), 1)) {
if (!starts_with(buf.buf, "-S"))
strbuf_reset(&buf);
else {
opts->gpg_sign = buf.buf + 2;
sequencer_entrust(opts,
strbuf_detach(&buf, NULL));
}
}
return 0;
}
if (!file_exists(git_path_opts_file()))
return 0;
/*
@@ -822,24 +1047,39 @@ static int read_populate_opts(struct replay_opts **opts)
* about this case, though, because we wrote that file ourselves, so we
* are pretty certain that it is syntactically correct.
*/
if (git_config_from_file(populate_opts_cb, git_path_opts_file(), *opts) < 0)
return error(_("Malformed options sheet: %s"),
if (git_config_from_file(populate_opts_cb, git_path_opts_file(), opts) < 0)
return error(_("Malformed options sheet: '%s'"),
git_path_opts_file());
return 0;
}
static int walk_revs_populate_todo(struct commit_list **todo_list,
static int walk_revs_populate_todo(struct todo_list *todo_list,
struct replay_opts *opts)
{
enum todo_command command = opts->action == REPLAY_PICK ?
TODO_PICK : TODO_REVERT;
const char *command_string = todo_command_strings[command];
struct commit *commit;
struct commit_list **next;
if (prepare_revs(opts))
return -1;
next = todo_list;
while ((commit = get_revision(opts->revs)))
next = commit_list_append(commit, next);
while ((commit = get_revision(opts->revs))) {
struct todo_item *item = append_new_todo(todo_list);
const char *commit_buffer = get_commit_buffer(commit, NULL);
const char *subject;
int subject_len;
item->command = command;
item->commit = commit;
item->arg = NULL;
item->arg_len = 0;
item->offset_in_buf = todo_list->buf.len;
subject_len = find_commit_subject(commit_buffer, &subject);
strbuf_addf(&todo_list->buf, "%s %s %.*s\n", command_string,
short_commit_name(commit), subject_len, subject);
unuse_commit_buffer(commit, commit_buffer);
}
return 0;
}
@@ -851,7 +1091,7 @@ static int create_seq_dir(void)
return -1;
}
else if (mkdir(git_path_seq_dir(), 0777) < 0)
return error_errno(_("Could not create sequencer directory %s"),
return error_errno(_("Could not create sequencer directory '%s'"),
git_path_seq_dir());
return 0;
}
@@ -870,12 +1110,12 @@ static int save_head(const char *head)
strbuf_addf(&buf, "%s\n", head);
if (write_in_full(fd, buf.buf, buf.len) < 0) {
rollback_lock_file(&head_lock);
return error_errno(_("Could not write to %s"),
return error_errno(_("Could not write to '%s'"),
git_path_head_file());
}
if (commit_lock_file(&head_lock) < 0) {
rollback_lock_file(&head_lock);
return error(_("Error wrapping up %s."), git_path_head_file());
return error(_("Error wrapping up '%s'."), git_path_head_file());
}
return 0;
}
@@ -904,7 +1144,7 @@ static int rollback_single_pick(void)
return reset_for_rollback(head_sha1);
}
static int sequencer_rollback(struct replay_opts *opts)
int sequencer_rollback(struct replay_opts *opts)
{
FILE *f;
unsigned char sha1[20];
@@ -920,9 +1160,9 @@ static int sequencer_rollback(struct replay_opts *opts)
return rollback_single_pick();
}
if (!f)
return error_errno(_("cannot open %s"), git_path_head_file());
return error_errno(_("cannot open '%s'"), git_path_head_file());
if (strbuf_getline_lf(&buf, f)) {
error(_("cannot read %s: %s"), git_path_head_file(),
error(_("cannot read '%s': %s"), git_path_head_file(),
ferror(f) ? strerror(errno) : _("unexpected end of file"));
fclose(f);
goto fail;
@@ -939,38 +1179,29 @@ static int sequencer_rollback(struct replay_opts *opts)
}
if (reset_for_rollback(sha1))
goto fail;
remove_sequencer_state();
strbuf_release(&buf);
return 0;
return sequencer_remove_state(opts);
fail:
strbuf_release(&buf);
return -1;
}
static int save_todo(struct commit_list *todo_list, struct replay_opts *opts)
static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)
{
static struct lock_file todo_lock;
struct strbuf buf = STRBUF_INIT;
int fd;
const char *todo_path = get_todo_path(opts);
int next = todo_list->current, offset, fd;
fd = hold_lock_file_for_update(&todo_lock, git_path_todo_file(), 0);
fd = hold_lock_file_for_update(&todo_lock, todo_path, 0);
if (fd < 0)
return error_errno(_("Could not lock '%s'"),
git_path_todo_file());
if (format_todo(&buf, todo_list, opts) < 0) {
strbuf_release(&buf);
return error(_("Could not format %s."), git_path_todo_file());
}
if (write_in_full(fd, buf.buf, buf.len) < 0) {
strbuf_release(&buf);
return error_errno(_("Could not write to %s"),
git_path_todo_file());
}
if (commit_lock_file(&todo_lock) < 0) {
strbuf_release(&buf);
return error(_("Error wrapping up %s."), git_path_todo_file());
}
strbuf_release(&buf);
return error_errno(_("Could not lock '%s'"), todo_path);
offset = next < todo_list->nr ?
todo_list->items[next].offset_in_buf : todo_list->buf.len;
if (write_in_full(fd, todo_list->buf.buf + offset,
todo_list->buf.len - offset) < 0)
return error_errno(_("Could not write to '%s'"), todo_path);
if (commit_lock_file(&todo_lock) < 0)
return error(_("Error wrapping up '%s'."), todo_path);
return 0;
}
@@ -1009,9 +1240,8 @@ static int save_opts(struct replay_opts *opts)
return res;
}
static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts)
static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
{
struct commit_list *cur;
int res;
setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
@@ -1021,10 +1251,12 @@ static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts)
if (read_and_refresh_cache(opts))
return -1;
for (cur = todo_list; cur; cur = cur->next) {
if (save_todo(cur, opts))
while (todo_list->current < todo_list->nr) {
struct todo_item *item = todo_list->items + todo_list->current;
if (save_todo(todo_list, opts))
return -1;
res = do_pick_commit(cur->item, opts);
res = do_pick_commit(item->command, item->commit, opts);
todo_list->current++;
if (res)
return res;
}
@@ -1033,8 +1265,7 @@ static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts)
* Sequence of picks finished successfully; cleanup by
* removing the .git/sequencer directory
*/
remove_sequencer_state();
return 0;
return sequencer_remove_state(opts);
}
static int continue_single_pick(void)
@@ -1047,61 +1278,56 @@ static int continue_single_pick(void)
return run_command_v_opt(argv, RUN_GIT_CMD);
}
static int sequencer_continue(struct replay_opts *opts)
int sequencer_continue(struct replay_opts *opts)
{
struct commit_list *todo_list = NULL;
struct todo_list todo_list = TODO_LIST_INIT;
int res;
if (!file_exists(git_path_todo_file()))
return continue_single_pick();
if (read_populate_opts(&opts) ||
read_populate_todo(&todo_list, opts))
if (read_and_refresh_cache(opts))
return -1;
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())) {
int ret = continue_single_pick();
if (ret)
return ret;
res = continue_single_pick();
if (res)
goto release_todo_list;
}
if (index_differs_from("HEAD", 0))
return error_dirty_index(opts);
todo_list = todo_list->next;
return pick_commits(todo_list, opts);
if (index_differs_from("HEAD", 0)) {
res = error_dirty_index(opts);
goto release_todo_list;
}
todo_list.current++;
res = pick_commits(&todo_list, opts);
release_todo_list:
todo_list_release(&todo_list);
return res;
}
static int single_pick(struct commit *cmit, struct replay_opts *opts)
{
setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
return do_pick_commit(cmit, opts);
return do_pick_commit(opts->action == REPLAY_PICK ?
TODO_PICK : TODO_REVERT, cmit, opts);
}
int sequencer_pick_revisions(struct replay_opts *opts)
{
struct commit_list *todo_list = NULL;
struct todo_list todo_list = TODO_LIST_INIT;
unsigned char sha1[20];
int i;
if (opts->subcommand == REPLAY_NONE)
assert(opts->revs);
int i, res;
assert(opts->revs);
if (read_and_refresh_cache(opts))
return -1;
/*
* Decide what to do depending on the arguments; a fresh
* cherry-pick should be handled differently from an existing
* one that is being continued
*/
if (opts->subcommand == REPLAY_REMOVE_STATE) {
remove_sequencer_state();
return 0;
}
if (opts->subcommand == REPLAY_ROLLBACK)
return sequencer_rollback(opts);
if (opts->subcommand == REPLAY_CONTINUE)
return sequencer_continue(opts);
for (i = 0; i < opts->revs->pending.nr; i++) {
unsigned char sha1[20];
const char *name = opts->revs->pending.objects[i].name;
@@ -1155,7 +1381,9 @@ int sequencer_pick_revisions(struct replay_opts *opts)
return -1;
if (save_opts(opts))
return -1;
return pick_commits(todo_list, opts);
res = pick_commits(&todo_list, opts);
todo_list_release(&todo_list);
return res;
}
void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)

View File

@@ -1,10 +1,7 @@
#ifndef SEQUENCER_H
#define SEQUENCER_H
#define SEQ_DIR "sequencer"
#define SEQ_HEAD_FILE "sequencer/head"
#define SEQ_TODO_FILE "sequencer/todo"
#define SEQ_OPTS_FILE "sequencer/opts"
const char *git_path_seq_dir(void);
#define APPEND_SIGNOFF_DEDUP (1u << 0)
@@ -13,16 +10,8 @@ enum replay_action {
REPLAY_PICK
};
enum replay_subcommand {
REPLAY_NONE,
REPLAY_REMOVE_STATE,
REPLAY_CONTINUE,
REPLAY_ROLLBACK
};
struct replay_opts {
enum replay_action action;
enum replay_subcommand subcommand;
/* Boolean options */
int edit;
@@ -46,9 +35,27 @@ struct replay_opts {
/* Only used by REPLAY_NONE */
struct rev_info *revs;
/* malloc()ed data entrusted to the sequencer */
void **owned;
int owned_nr, owned_alloc;
};
#define REPLAY_OPTS_INIT { -1 }
/*
* Make it the duty of sequencer_remove_state() to release the memory;
* For ease of use, return the same pointer.
*/
void *sequencer_entrust(struct replay_opts *opts, void *to_free);
int sequencer_pick_revisions(struct replay_opts *opts);
int sequencer_continue(struct replay_opts *opts);
int sequencer_rollback(struct replay_opts *opts);
int sequencer_remove_state(struct replay_opts *opts);
int sequencer_commit(const char *defmsg, struct replay_opts *opts,
int allow_empty, int edit, int amend,
int cleanup_commit_message);
extern const char sign_off_header[];