#include "config.h"
#include "lockfile.h"
#include "dir.h"
+#include "object-store.h"
#include "object.h"
#include "commit.h"
#include "sequencer.h"
#include "worktree.h"
#include "oidmap.h"
#include "oidset.h"
+#include "commit-slab.h"
#include "alias.h"
+#include "commit-reach.h"
+#include "rebase-interactive.h"
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
* 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")
+GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo")
+static GIT_PATH_FUNC(rebase_path_todo_backup,
+ "rebase-merge/git-rebase-todo.backup")
+
/*
* The rebase command lines that have already been processed. A line
* is moved here when it is first handled, before any associated user
* The file to keep track of how many commands were already processed (e.g.
* for the prompt).
*/
-static GIT_PATH_FUNC(rebase_path_msgnum, "rebase-merge/msgnum");
+static GIT_PATH_FUNC(rebase_path_msgnum, "rebase-merge/msgnum")
/*
* The file to keep track of how many commands are to be processed in total
* (e.g. for the prompt).
*/
-static GIT_PATH_FUNC(rebase_path_msgtotal, "rebase-merge/end");
+static GIT_PATH_FUNC(rebase_path_msgtotal, "rebase-merge/end")
/*
* The commit message that is planned to be used for any changes that
* need to be committed following a user interaction.
/*
* The following files are written by git-rebase just after parsing the
- * command-line (and are only consumed, not modified, by the sequencer).
+ * command-line.
*/
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_strategy, "rebase-merge/strategy")
static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts")
static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, "rebase-merge/allow_rerere_autoupdate")
+static GIT_PATH_FUNC(rebase_path_quiet, "rebase-merge/quiet")
static int git_sequencer_config(const char *k, const char *v, void *cb)
{
warning(_("invalid commit message cleanup mode '%s'"),
s);
+ free((char *)s);
return status;
}
* Returns 3 when sob exists within conforming footer as last entry
*/
static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob,
- int ignore_footer)
+ size_t ignore_footer)
{
+ struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;
struct trailer_info info;
- int i;
+ size_t i;
int found_sob = 0, found_sob_last = 0;
- trailer_info_get(&info, sb->buf);
+ opts.no_divider = 1;
+
+ trailer_info_get(&info, sb->buf, &opts);
if (info.trailer_start == info.trailer_end)
return 0;
case REPLAY_INTERACTIVE_REBASE:
return N_("rebase -i");
}
- die(_("Unknown action: %d"), opts->action);
+ die(_("unknown action: %d"), opts->action);
}
struct commit_message {
* (typically rebase --interactive) wants to take care
* of the commit itself so remove CHERRY_PICK_HEAD
*/
- unlink(git_path_cherry_pick_head());
+ unlink(git_path_cherry_pick_head(the_repository));
return;
}
}
}
-static int write_message(const void *buf, size_t len, const char *filename,
- int append_eol)
+int write_message(const void *buf, size_t len, const char *filename,
+ int append_eol)
{
struct lock_file msg_file = LOCK_INIT;
static struct tree *empty_tree(void)
{
- return lookup_tree(the_hash_algo->empty_tree);
+ return lookup_tree(the_repository, the_repository->hash_algo->empty_tree);
}
static int error_dirty_index(struct replay_opts *opts)
struct strbuf sb = STRBUF_INIT;
struct strbuf err = STRBUF_INIT;
- read_cache();
- if (checkout_fast_forward(from, to, 1))
+ read_index(&the_index);
+ if (checkout_fast_forward(the_repository, from, to, 1))
return -1; /* the callee should have complained already */
strbuf_addf(&sb, _("%s: fast-forward"), _(action_name(opts)));
if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL))
return error(_("could not resolve HEAD commit"));
- head_commit = lookup_commit(&head_oid);
+ head_commit = lookup_commit(the_repository, &head_oid);
/*
* If head_commit is NULL, check_commit, called from
if (!(cache_tree_oid = get_cache_tree_oid()))
return -1;
- return !oidcmp(cache_tree_oid, get_commit_tree_oid(head_commit));
+ return oideq(cache_tree_oid, get_commit_tree_oid(head_commit));
}
static int write_author_script(const char *message)
else if (*message != '\'')
strbuf_addch(&buf, *(message++));
else
- strbuf_addf(&buf, "'\\\\%c'", *(message++));
+ strbuf_addf(&buf, "'\\%c'", *(message++));
strbuf_addstr(&buf, "'\nGIT_AUTHOR_EMAIL='");
while (*message && *message != '\n' && *message != '\r')
if (skip_prefix(message, "> ", &message))
else if (*message != '\'')
strbuf_addch(&buf, *(message++));
else
- strbuf_addf(&buf, "'\\\\%c'", *(message++));
+ strbuf_addf(&buf, "'\\%c'", *(message++));
strbuf_addstr(&buf, "'\nGIT_AUTHOR_DATE='@");
while (*message && *message != '\n' && *message != '\r')
if (*message != '\'')
strbuf_addch(&buf, *(message++));
else
- strbuf_addf(&buf, "'\\\\%c'", *(message++));
+ strbuf_addf(&buf, "'\\%c'", *(message++));
+ strbuf_addch(&buf, '\'');
res = write_message(buf.buf, buf.len, rebase_path_author_script(), 1);
strbuf_release(&buf);
return res;
}
+/**
+ * Take a series of KEY='VALUE' lines where VALUE part is
+ * sq-quoted, and append <KEY, VALUE> at the end of the string list
+ */
+static int parse_key_value_squoted(char *buf, struct string_list *list)
+{
+ while (*buf) {
+ struct string_list_item *item;
+ char *np;
+ char *cp = strchr(buf, '=');
+ if (!cp) {
+ np = strchrnul(buf, '\n');
+ return error(_("no key present in '%.*s'"),
+ (int) (np - buf), buf);
+ }
+ np = strchrnul(cp, '\n');
+ *cp++ = '\0';
+ item = string_list_append(list, buf);
+
+ buf = np + (*np == '\n');
+ *np = '\0';
+ cp = sq_dequote(cp);
+ if (!cp)
+ return error(_("unable to dequote value of '%s'"),
+ item->string);
+ item->util = xstrdup(cp);
+ }
+ return 0;
+}
+
+/**
+ * Reads and parses the state directory's "author-script" file, and sets name,
+ * email and date accordingly.
+ * Returns 0 on success, -1 if the file could not be parsed.
+ *
+ * The author script is of the format:
+ *
+ * GIT_AUTHOR_NAME='$author_name'
+ * GIT_AUTHOR_EMAIL='$author_email'
+ * GIT_AUTHOR_DATE='$author_date'
+ *
+ * where $author_name, $author_email and $author_date are quoted. We are strict
+ * with our parsing, as the file was meant to be eval'd in the old
+ * git-am.sh/git-rebase--interactive.sh scripts, and thus if the file differs
+ * from what this function expects, it is better to bail out than to do
+ * something that the user does not expect.
+ */
+int read_author_script(const char *path, char **name, char **email, char **date,
+ int allow_missing)
+{
+ struct strbuf buf = STRBUF_INIT;
+ struct string_list kv = STRING_LIST_INIT_DUP;
+ int retval = -1; /* assume failure */
+ int i, name_i = -2, email_i = -2, date_i = -2, err = 0;
+
+ if (strbuf_read_file(&buf, path, 256) <= 0) {
+ strbuf_release(&buf);
+ if (errno == ENOENT && allow_missing)
+ return 0;
+ else
+ return error_errno(_("could not open '%s' for reading"),
+ path);
+ }
+
+ if (parse_key_value_squoted(buf.buf, &kv))
+ goto finish;
+
+ for (i = 0; i < kv.nr; i++) {
+ if (!strcmp(kv.items[i].string, "GIT_AUTHOR_NAME")) {
+ if (name_i != -2)
+ name_i = error(_("'GIT_AUTHOR_NAME' already given"));
+ else
+ name_i = i;
+ } else if (!strcmp(kv.items[i].string, "GIT_AUTHOR_EMAIL")) {
+ if (email_i != -2)
+ email_i = error(_("'GIT_AUTHOR_EMAIL' already given"));
+ else
+ email_i = i;
+ } else if (!strcmp(kv.items[i].string, "GIT_AUTHOR_DATE")) {
+ if (date_i != -2)
+ date_i = error(_("'GIT_AUTHOR_DATE' already given"));
+ else
+ date_i = i;
+ } else {
+ err = error(_("unknown variable '%s'"),
+ kv.items[i].string);
+ }
+ }
+ if (name_i == -2)
+ error(_("missing 'GIT_AUTHOR_NAME'"));
+ if (email_i == -2)
+ error(_("missing 'GIT_AUTHOR_EMAIL'"));
+ if (date_i == -2)
+ error(_("missing 'GIT_AUTHOR_DATE'"));
+ if (date_i < 0 || email_i < 0 || date_i < 0 || err)
+ goto finish;
+ *name = kv.items[name_i].util;
+ *email = kv.items[email_i].util;
+ *date = kv.items[date_i].util;
+ retval = 0;
+finish:
+ string_list_clear(&kv, !!retval);
+ strbuf_release(&buf);
+ return retval;
+}
+
/*
- * Read a list of environment variable assignments (such as the author-script
- * file) into an environment block. Returns -1 on error, 0 otherwise.
+ * Read a GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL AND GIT_AUTHOR_DATE from a
+ * file with shell quoting into struct argv_array. Returns -1 on
+ * error, 0 otherwise.
*/
static int read_env_script(struct argv_array *env)
{
- struct strbuf script = STRBUF_INIT;
- int i, count = 0;
- char *p, *p2;
+ char *name, *email, *date;
- if (strbuf_read_file(&script, rebase_path_author_script(), 256) <= 0)
+ if (read_author_script(rebase_path_author_script(),
+ &name, &email, &date, 0))
return -1;
- 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++;
- }
-
- for (i = 0, p = script.buf; i < count; i++) {
- argv_array_push(env, p);
- p += strlen(p) + 1;
- }
+ argv_array_pushf(env, "GIT_AUTHOR_NAME=%s", name);
+ argv_array_pushf(env, "GIT_AUTHOR_EMAIL=%s", email);
+ argv_array_pushf(env, "GIT_AUTHOR_DATE=%s", date);
+ free(name);
+ free(email);
+ free(date);
return 0;
}
/* Read author-script and return an ident line (author <email> timestamp) */
static const char *read_author_ident(struct strbuf *buf)
{
- const char *keys[] = {
- "GIT_AUTHOR_NAME=", "GIT_AUTHOR_EMAIL=", "GIT_AUTHOR_DATE="
- };
- char *in, *out, *eol;
- int i = 0, len;
+ struct strbuf out = STRBUF_INIT;
+ char *name, *email, *date;
- if (strbuf_read_file(buf, rebase_path_author_script(), 256) <= 0)
+ if (read_author_script(rebase_path_author_script(),
+ &name, &email, &date, 0))
return NULL;
- /* dequote values and construct ident line in-place */
- for (in = out = buf->buf; i < 3 && in - buf->buf < buf->len; i++) {
- if (!skip_prefix(in, keys[i], (const char **)&in)) {
- warning("could not parse '%s' (looking for '%s'",
- rebase_path_author_script(), keys[i]);
- return NULL;
- }
-
- eol = strchrnul(in, '\n');
- *eol = '\0';
- sq_dequote(in);
- len = strlen(in);
-
- if (i > 0) /* separate values by spaces */
- *(out++) = ' ';
- if (i == 1) /* email needs to be surrounded by <...> */
- *(out++) = '<';
- memmove(out, in, len);
- out += len;
- if (i == 1) /* email needs to be surrounded by <...> */
- *(out++) = '>';
- in = eol + 1;
- }
-
- if (i < 3) {
- warning("could not parse '%s' (looking for '%s')",
- rebase_path_author_script(), keys[i]);
+ /* validate date since fmt_ident() will die() on bad value */
+ if (parse_date(date, &out)){
+ warning(_("invalid date format '%s' in '%s'"),
+ date, rebase_path_author_script());
+ strbuf_release(&out);
return NULL;
}
- buf->len = out - buf->buf;
+ strbuf_reset(&out);
+ strbuf_addstr(&out, fmt_ident(name, email, date, 0));
+ strbuf_swap(buf, &out);
+ strbuf_release(&out);
+ free(name);
+ free(email);
+ free(date);
return buf->buf;
}
#define VERIFY_MSG (1<<4)
#define CREATE_ROOT_COMMIT (1<<5)
+static int run_command_silent_on_success(struct child_process *cmd)
+{
+ struct strbuf buf = STRBUF_INIT;
+ int rc;
+
+ cmd->stdout_to_stderr = 1;
+ rc = pipe_command(cmd,
+ NULL, 0,
+ NULL, 0,
+ &buf, 0);
+
+ if (rc)
+ fputs(buf.buf, stderr);
+ strbuf_release(&buf);
+ return rc;
+}
+
/*
* If we are cherry-pick, and if the merge did not result in
* hand-editing, we will hit this commit and inherit the original
if ((flags & CREATE_ROOT_COMMIT) && !(flags & AMEND_MSG)) {
struct strbuf msg = STRBUF_INIT, script = STRBUF_INIT;
- const char *author = is_rebase_i(opts) ?
- read_author_ident(&script) : NULL;
+ const char *author = NULL;
struct object_id root_commit, *cache_tree_oid;
int res = 0;
+ if (is_rebase_i(opts)) {
+ author = read_author_ident(&script);
+ if (!author) {
+ strbuf_release(&script);
+ return -1;
+ }
+ }
+
if (!defmsg)
BUG("root commit without message");
cmd.git_cmd = 1;
- if (is_rebase_i(opts)) {
- if (!(flags & EDIT_MSG)) {
- cmd.stdout_to_stderr = 1;
- cmd.err = -1;
- }
-
- if (read_env_script(&cmd.env_array)) {
- const char *gpg_opt = gpg_sign_opt_quoted(opts);
+ if (is_rebase_i(opts) && read_env_script(&cmd.env_array)) {
+ const char *gpg_opt = gpg_sign_opt_quoted(opts);
- return error(_(staged_changes_advice),
- gpg_opt, gpg_opt);
- }
+ return error(_(staged_changes_advice),
+ gpg_opt, gpg_opt);
}
argv_array_push(&cmd.args, "commit");
if ((flags & ALLOW_EMPTY))
argv_array_push(&cmd.args, "--allow-empty");
- if (opts->allow_empty_message)
+ if (!(flags & EDIT_MSG))
argv_array_push(&cmd.args, "--allow-empty-message");
- 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 run_command(&cmd);
+ if (is_rebase_i(opts) && !(flags & EDIT_MSG))
+ return run_command_silent_on_success(&cmd);
+ else
+ return run_command(&cmd);
}
static int rest_is_empty(const struct strbuf *sb, int start)
struct strbuf author_ident = STRBUF_INIT;
struct strbuf committer_ident = STRBUF_INIT;
- commit = lookup_commit(oid);
+ commit = lookup_commit(the_repository, oid);
if (!commit)
die(_("couldn't look up newly created commit"));
if (parse_commit(commit))
strbuf_release(&author_ident);
strbuf_release(&committer_ident);
- init_revisions(&rev, prefix);
+ repo_init_revisions(the_repository, &rev, prefix);
setup_revisions(0, NULL, &rev, NULL);
rev.diff = 1;
if (get_oid("HEAD", &oid)) {
current_head = NULL;
} else {
- current_head = lookup_commit_reference(&oid);
+ current_head = lookup_commit_reference(the_repository, &oid);
if (!current_head)
return error(_("could not parse HEAD"));
- if (oidcmp(&oid, ¤t_head->object.oid)) {
+ if (!oideq(&oid, ¤t_head->object.oid)) {
warning(_("HEAD %s is not a commit!"),
oid_to_hex(&oid));
}
commit_list_insert(current_head, &parents);
}
- if (write_cache_as_tree(&tree, 0, NULL)) {
+ if (write_index_as_tree(&tree, &the_index, get_index_file(), 0, NULL)) {
res = error(_("git write-tree failed to write a tree"));
goto out;
}
- if (!(flags & ALLOW_EMPTY) && !oidcmp(current_head ?
- get_commit_tree_oid(current_head) :
- the_hash_algo->empty_tree, &tree)) {
+ if (!(flags & ALLOW_EMPTY) && oideq(current_head ?
+ get_commit_tree_oid(current_head) :
+ the_hash_algo->empty_tree, &tree)) {
res = 1; /* run 'git commit' to display error message */
goto out;
}
if (cleanup != COMMIT_MSG_CLEANUP_NONE)
strbuf_stripspace(msg, cleanup == COMMIT_MSG_CLEANUP_ALL);
- if (!opts->allow_empty_message && message_is_empty(msg, cleanup)) {
+ if ((flags & EDIT_MSG) && message_is_empty(msg, cleanup)) {
res = 1; /* run 'git commit' to display error message */
goto out;
}
&oid);
strbuf_release(&sb);
if (!res) {
- unlink(git_path_cherry_pick_head());
- unlink(git_path_merge_msg());
+ unlink(git_path_cherry_pick_head(the_repository));
+ unlink(git_path_merge_msg(the_repository));
if (!is_rebase_i(opts))
print_commit_summary(NULL, &oid,
SUMMARY_SHOW_AUTHOR_DATE);
ptree_oid = the_hash_algo->empty_tree; /* commit is root */
}
- return !oidcmp(ptree_oid, get_commit_tree_oid(commit));
+ return oideq(ptree_oid, get_commit_tree_oid(commit));
}
/*
TODO_SQUASH,
/* commands that do something else than handling a single commit */
TODO_EXEC,
+ TODO_BREAK,
TODO_LABEL,
TODO_RESET,
TODO_MERGE,
{ 'f', "fixup" },
{ 's', "squash" },
{ 'x', "exec" },
+ { 'b', "break" },
{ 'l', "label" },
{ 't', "reset" },
{ 'm', "merge" },
{
if (command < TODO_COMMENT)
return todo_command_info[command].str;
- die("Unknown command: %d", command);
+ die(_("unknown command: %d"), command);
}
static char command_to_char(const enum todo_command command)
if (get_oid("HEAD", &head))
return error(_("need a HEAD to fixup"));
- if (!(head_commit = lookup_commit_reference(&head)))
+ if (!(head_commit = lookup_commit_reference(the_repository, &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"));
unlink(rebase_path_fixup_msg());
strbuf_addf(&buf, "\n%c ", comment_line_char);
strbuf_addf(&buf, _("This is the commit message #%d:"),
- ++opts->current_fixup_count);
+ ++opts->current_fixup_count + 1);
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:"),
- ++opts->current_fixup_count);
+ ++opts->current_fixup_count + 1);
strbuf_addstr(&buf, "\n\n");
strbuf_add_commented_lines(&buf, body, strlen(body));
} else
struct replay_opts *opts, int final_fixup)
{
unsigned int flags = opts->edit ? EDIT_MSG : 0;
- const char *msg_file = opts->edit ? NULL : git_path_merge_msg();
+ const char *msg_file = opts->edit ? NULL : git_path_merge_msg(the_repository);
struct object_id head;
struct commit *base, *next, *parent;
const char *base_label, *next_label;
* that represents the "current" state for merge-recursive
* to work on.
*/
- if (write_cache_as_tree(&head, 0, NULL))
+ if (write_index_as_tree(&head, &the_index, get_index_file(), 0, NULL))
return error(_("your index file is unmerged."));
} else {
unborn = get_oid("HEAD", &head);
/* Do we want to generate a root commit? */
if (is_pick_or_similar(command) && opts->have_squash_onto &&
- !oidcmp(&head, &opts->squash_onto)) {
+ oideq(&head, &opts->squash_onto)) {
if (is_fixup(command))
return error(_("cannot fixup root commit"));
flags |= CREATE_ROOT_COMMIT;
oid_to_hex(&commit->object.oid));
if (opts->allow_ff && !is_fixup(command) &&
- ((parent && !oidcmp(&parent->object.oid, &head)) ||
+ ((parent && oideq(&parent->object.oid, &head)) ||
(!parent && unborn))) {
if (is_rebase_i(opts))
write_author_script(msg.message);
flags |= CLEANUP_MSG;
msg_file = rebase_path_fixup_msg();
} else {
- const char *dest = git_path_squash_msg();
+ const char *dest = git_path_squash_msg(the_repository);
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());
+ unlink(git_path_merge_msg(the_repository));
msg_file = dest;
flags |= EDIT_MSG;
}
res = do_recursive_merge(base, next, base_label, next_label,
&head, &msgbuf, opts);
if (res < 0)
- return res;
+ goto leave;
+
res |= write_message(msgbuf.buf, msgbuf.len,
- git_path_merge_msg(), 0);
+ git_path_merge_msg(the_repository), 0);
} else {
struct commit_list *common = NULL;
struct commit_list *remotes = NULL;
res = write_message(msgbuf.buf, msgbuf.len,
- git_path_merge_msg(), 0);
+ git_path_merge_msg(the_repository), 0);
commit_list_insert(base, &common);
commit_list_insert(next, &remotes);
- res |= try_merge_command(opts->strategy,
+ res |= try_merge_command(the_repository, opts->strategy,
opts->xopts_nr, (const char **)opts->xopts,
common, oid_to_hex(&head), remotes);
free_commit_list(common);
: _("could not apply %s... %s"),
short_commit_name(commit), msg.subject);
print_advice(res == 1, opts);
- rerere(opts->allow_rerere_auto);
+ repo_rerere(the_repository, opts->allow_rerere_auto);
goto leave;
}
if (prepare_revision_walk(opts->revs))
return error(_("revision walk setup failed"));
- if (!opts->revs->commits)
- return error(_("empty commit set passed"));
return 0;
}
{
struct lock_file index_lock = LOCK_INIT;
int index_fd = hold_locked_index(&index_lock, 0);
- if (read_index_preload(&the_index, NULL) < 0) {
+ if (read_index(&the_index) < 0) {
rollback_lock_file(&index_lock);
return error(_("git %s: failed to read the index"),
_(action_name(opts)));
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 == eol || bol[1] == ' ') &&
+ *bol == todo_command_info[i].c) {
bol++;
item->command = i;
break;
padding = strspn(bol, " \t");
bol += padding;
- if (item->command == TODO_NOOP) {
+ if (item->command == TODO_NOOP || item->command == TODO_BREAK) {
if (bol != eol)
return error(_("%s does not accept arguments: '%s'"),
command_to_string(item->command), bol);
if (status < 0)
return -1;
- item->commit = lookup_commit_reference(&commit_oid);
+ item->commit = lookup_commit_reference(the_repository, &commit_oid);
return !item->commit;
}
return 0;
}
-static void read_strategy_opts(struct replay_opts *opts, struct strbuf *buf)
+void parse_strategy_opts(struct replay_opts *opts, char *raw_opts)
{
int i;
+ char *strategy_opts_string = raw_opts;
- strbuf_reset(buf);
- if (!read_oneliner(buf, rebase_path_strategy(), 0))
- return;
- opts->strategy = strbuf_detach(buf, NULL);
- if (!read_oneliner(buf, rebase_path_strategy_opts(), 0))
- return;
+ if (*strategy_opts_string == ' ')
+ strategy_opts_string++;
- opts->xopts_nr = split_cmdline(buf->buf, (const char ***)&opts->xopts);
+ opts->xopts_nr = split_cmdline(strategy_opts_string,
+ (const char ***)&opts->xopts);
for (i = 0; i < opts->xopts_nr; i++) {
const char *arg = opts->xopts[i];
}
}
+static void read_strategy_opts(struct replay_opts *opts, struct strbuf *buf)
+{
+ strbuf_reset(buf);
+ if (!read_oneliner(buf, rebase_path_strategy(), 0))
+ return;
+ opts->strategy = strbuf_detach(buf, NULL);
+ if (!read_oneliner(buf, rebase_path_strategy_opts(), 0))
+ return;
+
+ parse_strategy_opts(opts, buf->buf);
+}
+
static int read_populate_opts(struct replay_opts *opts)
{
if (is_rebase_i(opts)) {
return 0;
}
+static void write_strategy_opts(struct replay_opts *opts)
+{
+ int i;
+ struct strbuf buf = STRBUF_INIT;
+
+ for (i = 0; i < opts->xopts_nr; ++i)
+ strbuf_addf(&buf, " --%s", opts->xopts[i]);
+
+ write_file(rebase_path_strategy_opts(), "%s\n", buf.buf);
+ strbuf_release(&buf);
+}
+
+int write_basic_state(struct replay_opts *opts, const char *head_name,
+ const char *onto, const char *orig_head)
+{
+ const char *quiet = getenv("GIT_QUIET");
+
+ if (head_name)
+ write_file(rebase_path_head_name(), "%s\n", head_name);
+ if (onto)
+ write_file(rebase_path_onto(), "%s\n", onto);
+ if (orig_head)
+ write_file(rebase_path_orig_head(), "%s\n", orig_head);
+
+ if (quiet)
+ write_file(rebase_path_quiet(), "%s\n", quiet);
+ else
+ write_file(rebase_path_quiet(), "\n");
+
+ if (opts->verbose)
+ write_file(rebase_path_verbose(), "%s", "");
+ if (opts->strategy)
+ write_file(rebase_path_strategy(), "%s\n", opts->strategy);
+ if (opts->xopts_nr > 0)
+ write_strategy_opts(opts);
+
+ if (opts->allow_rerere_auto == RERERE_AUTOUPDATE)
+ write_file(rebase_path_allow_rerere_autoupdate(), "--rerere-autoupdate\n");
+ else if (opts->allow_rerere_auto == RERERE_NOAUTOUPDATE)
+ write_file(rebase_path_allow_rerere_autoupdate(), "--no-rerere-autoupdate\n");
+
+ if (opts->gpg_sign)
+ write_file(rebase_path_gpg_sign_opt(), "-S%s\n", opts->gpg_sign);
+ if (opts->signoff)
+ write_file(rebase_path_signoff(), "--signoff\n");
+
+ return 0;
+}
+
static int walk_revs_populate_todo(struct todo_list *todo_list,
struct replay_opts *opts)
{
short_commit_name(commit), subject_len, subject);
unuse_commit_buffer(commit, commit_buffer);
}
+
+ if (!todo_list->nr)
+ return error(_("empty commit set passed"));
+
return 0;
}
if (get_oid("HEAD", &actual_head))
oidclr(&actual_head);
- return !oidcmp(&actual_head, &expected_head);
+ return oideq(&actual_head, &expected_head);
}
static int reset_for_rollback(const struct object_id *oid)
{
struct object_id head_oid;
- if (!file_exists(git_path_cherry_pick_head()) &&
- !file_exists(git_path_revert_head()))
+ if (!file_exists(git_path_cherry_pick_head(the_repository)) &&
+ !file_exists(git_path_revert_head(the_repository)))
return error(_("no cherry-pick or revert in progress"));
if (read_ref_full("HEAD", 0, &head_oid, NULL))
return error(_("cannot resolve HEAD"));
strbuf_addf(&buf, "%s/patch", get_dir(opts));
memset(&log_tree_opt, 0, sizeof(log_tree_opt));
- init_revisions(&log_tree_opt, NULL);
+ repo_init_revisions(the_repository, &log_tree_opt, NULL);
log_tree_opt.abbrev = 0;
log_tree_opt.diff = 1;
log_tree_opt.diffopt.output_format = DIFF_FORMAT_PATCH;
const char *subject, int subject_len,
struct replay_opts *opts, int exit_code, int to_amend)
{
- if (make_patch(commit, opts))
- return -1;
+ if (commit) {
+ if (make_patch(commit, opts))
+ return -1;
+ } else if (copy_file(rebase_path_message(),
+ git_path_merge_msg(the_repository), 0666))
+ return error(_("unable to copy '%s' to '%s'"),
+ git_path_merge_msg(the_repository), rebase_path_message());
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);
+ 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) {
+ if (commit)
+ fprintf_ln(stderr, _("Could not apply %s... %.*s"),
+ short_commit_name(commit), subject_len, subject);
+ else
+ /*
+ * We don't have the hash of the parent so
+ * just print the line from the todo file.
+ */
+ fprintf_ln(stderr, _("Could not merge %.*s"),
+ subject_len, subject);
+ }
return exit_code;
}
if (copy_file(rebase_path_message(), rebase_path_squash_msg(), 0666))
return error(_("could not copy '%s' to '%s'"),
rebase_path_squash_msg(), rebase_path_message());
- unlink(git_path_merge_msg());
- if (copy_file(git_path_merge_msg(), rebase_path_message(), 0666))
+ unlink(git_path_merge_msg(the_repository));
+ if (copy_file(git_path_merge_msg(the_repository), rebase_path_message(), 0666))
return error(_("could not copy '%s' to '%s'"),
- rebase_path_message(), git_path_merge_msg());
+ rebase_path_message(),
+ git_path_merge_msg(the_repository));
return error_with_patch(commit, subject, subject_len, opts, 1, 0);
}
fprintf(stderr, "Executing: %s\n", command_line);
child_argv[0] = command_line;
argv_array_pushf(&child_env, "GIT_DIR=%s", absolute_path(get_git_dir()));
+ argv_array_pushf(&child_env, "GIT_WORK_TREE=%s",
+ absolute_path(get_git_work_tree()));
status = run_command_v_opt_cd_env(child_argv, RUN_USING_SHELL, NULL,
child_env.argv);
struct object_id head_oid;
if (len == 1 && *name == '#')
- return error("Illegal label name: '%.*s'", len, name);
+ return error(_("illegal label name: '%.*s'"), len, name);
strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name);
strbuf_addf(&msg, "rebase -i (label) '%.*s'", len, name);
struct tree_desc desc;
struct tree *tree;
struct unpack_trees_options unpack_tree_opts;
- int ret = 0, i;
+ int ret = 0;
if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)
return -1;
}
oidcpy(&oid, &opts->squash_onto);
} else {
+ int i;
+
/* Determine the length of the label */
for (i = 0; i < len; i++)
if (isspace(name[i]))
- len = i;
+ break;
+ len = i;
strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name);
if (get_oid(ref_name.buf, &oid) &&
return ret;
}
+static struct commit *lookup_label(const char *label, int len,
+ struct strbuf *buf)
+{
+ struct commit *commit;
+
+ strbuf_reset(buf);
+ strbuf_addf(buf, "refs/rewritten/%.*s", len, label);
+ commit = lookup_commit_reference_by_name(buf->buf);
+ if (!commit) {
+ /* fall back to non-rewritten ref or commit */
+ strbuf_splice(buf, 0, strlen("refs/rewritten/"), "", 0);
+ commit = lookup_commit_reference_by_name(buf->buf);
+ }
+
+ if (!commit)
+ error(_("could not resolve '%s'"), buf->buf);
+
+ return commit;
+}
+
static int do_merge(struct commit *commit, const char *arg, int arg_len,
int flags, struct replay_opts *opts)
{
struct strbuf ref_name = STRBUF_INIT;
struct commit *head_commit, *merge_commit, *i;
struct commit_list *bases, *j, *reversed = NULL;
+ struct commit_list *to_merge = NULL, **tail = &to_merge;
struct merge_options o;
- int merge_arg_len, oneline_offset, can_fast_forward, ret;
+ int merge_arg_len, oneline_offset, can_fast_forward, ret, k;
static struct lock_file lock;
const char *p;
goto leave_merge;
}
- oneline_offset = arg_len;
- merge_arg_len = strcspn(arg, " \t\n");
- p = arg + merge_arg_len;
- p += strspn(p, " \t\n");
- if (*p == '#' && (!p[1] || isspace(p[1]))) {
- p += 1 + strspn(p + 1, " \t\n");
- oneline_offset = p - arg;
- } else if (p - arg < arg_len)
- BUG("octopus merges are not supported yet: '%s'", p);
-
- strbuf_addf(&ref_name, "refs/rewritten/%.*s", merge_arg_len, arg);
- merge_commit = lookup_commit_reference_by_name(ref_name.buf);
- if (!merge_commit) {
- /* fall back to non-rewritten ref or commit */
- strbuf_splice(&ref_name, 0, strlen("refs/rewritten/"), "", 0);
- merge_commit = lookup_commit_reference_by_name(ref_name.buf);
+ /*
+ * For octopus merges, the arg starts with the list of revisions to be
+ * merged. The list is optionally followed by '#' and the oneline.
+ */
+ merge_arg_len = oneline_offset = arg_len;
+ for (p = arg; p - arg < arg_len; p += strspn(p, " \t\n")) {
+ if (!*p)
+ break;
+ if (*p == '#' && (!p[1] || isspace(p[1]))) {
+ p += 1 + strspn(p + 1, " \t\n");
+ oneline_offset = p - arg;
+ break;
+ }
+ k = strcspn(p, " \t\n");
+ if (!k)
+ continue;
+ merge_commit = lookup_label(p, k, &ref_name);
+ if (!merge_commit) {
+ ret = error(_("unable to parse '%.*s'"), k, p);
+ goto leave_merge;
+ }
+ tail = &commit_list_insert(merge_commit, tail)->next;
+ p += k;
+ merge_arg_len = p - arg;
}
- if (!merge_commit) {
- ret = error(_("could not resolve '%s'"), ref_name.buf);
+ if (!to_merge) {
+ ret = error(_("nothing to merge: '%.*s'"), arg_len, arg);
goto leave_merge;
}
if (opts->have_squash_onto &&
- !oidcmp(&head_commit->object.oid, &opts->squash_onto)) {
+ oideq(&head_commit->object.oid, &opts->squash_onto)) {
/*
* When the user tells us to "merge" something into a
* "[new root]", let's simply fast-forward to the merge head.
*/
rollback_lock_file(&lock);
- ret = fast_forward_to(&merge_commit->object.oid,
- &head_commit->object.oid, 0, opts);
+ if (to_merge->next)
+ ret = error(_("octopus merge cannot be executed on "
+ "top of a [new root]"));
+ else
+ ret = fast_forward_to(&to_merge->item->object.oid,
+ &head_commit->object.oid, 0,
+ opts);
goto leave_merge;
}
write_author_script(message);
find_commit_subject(message, &body);
len = strlen(body);
- ret = write_message(body, len, git_path_merge_msg(), 0);
+ ret = write_message(body, len, git_path_merge_msg(the_repository), 0);
unuse_commit_buffer(commit, message);
if (ret) {
error_errno(_("could not write '%s'"),
- git_path_merge_msg());
+ git_path_merge_msg(the_repository));
goto leave_merge;
}
} else {
p = arg + oneline_offset;
len = arg_len - oneline_offset;
} else {
- strbuf_addf(&buf, "Merge branch '%.*s'",
+ strbuf_addf(&buf, "Merge %s '%.*s'",
+ to_merge->next ? "branches" : "branch",
merge_arg_len, arg);
p = buf.buf;
len = buf.len;
}
- ret = write_message(p, len, git_path_merge_msg(), 0);
+ ret = write_message(p, len, git_path_merge_msg(the_repository), 0);
strbuf_release(&buf);
if (ret) {
error_errno(_("could not write '%s'"),
- git_path_merge_msg());
+ git_path_merge_msg(the_repository));
goto leave_merge;
}
}
* commit, we cannot fast-forward.
*/
can_fast_forward = opts->allow_ff && commit && commit->parents &&
- !oidcmp(&commit->parents->item->object.oid,
- &head_commit->object.oid);
+ oideq(&commit->parents->item->object.oid,
+ &head_commit->object.oid);
/*
- * If the merge head is different from the original one, we cannot
+ * If any merge head is different from the original one, we cannot
* fast-forward.
*/
if (can_fast_forward) {
- struct commit_list *second_parent = commit->parents->next;
+ struct commit_list *p = commit->parents->next;
- if (second_parent && !second_parent->next &&
- oidcmp(&merge_commit->object.oid,
- &second_parent->item->object.oid))
+ for (j = to_merge; j && p; j = j->next, p = p->next)
+ if (!oideq(&j->item->object.oid,
+ &p->item->object.oid)) {
+ can_fast_forward = 0;
+ break;
+ }
+ /*
+ * If the number of merge heads differs from the original merge
+ * commit, we cannot fast-forward.
+ */
+ if (j || p)
can_fast_forward = 0;
}
- if (can_fast_forward && commit->parents->next &&
- !commit->parents->next->next &&
- !oidcmp(&commit->parents->next->item->object.oid,
- &merge_commit->object.oid)) {
+ if (can_fast_forward) {
rollback_lock_file(&lock);
ret = fast_forward_to(&commit->object.oid,
&head_commit->object.oid, 0, opts);
goto leave_merge;
}
- write_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,
- git_path_merge_head(), 0);
- write_message("no-ff", 5, git_path_merge_mode(), 0);
+ if (to_merge->next) {
+ /* Octopus merge */
+ struct child_process cmd = CHILD_PROCESS_INIT;
+ if (read_env_script(&cmd.env_array)) {
+ const char *gpg_opt = gpg_sign_opt_quoted(opts);
+
+ ret = error(_(staged_changes_advice), gpg_opt, gpg_opt);
+ goto leave_merge;
+ }
+
+ cmd.git_cmd = 1;
+ argv_array_push(&cmd.args, "merge");
+ argv_array_push(&cmd.args, "-s");
+ argv_array_push(&cmd.args, "octopus");
+ argv_array_push(&cmd.args, "--no-edit");
+ argv_array_push(&cmd.args, "--no-ff");
+ argv_array_push(&cmd.args, "--no-log");
+ argv_array_push(&cmd.args, "--no-stat");
+ argv_array_push(&cmd.args, "-F");
+ argv_array_push(&cmd.args, git_path_merge_msg(the_repository));
+ if (opts->gpg_sign)
+ argv_array_push(&cmd.args, opts->gpg_sign);
+
+ /* Add the tips to be merged */
+ for (j = to_merge; j; j = j->next)
+ argv_array_push(&cmd.args,
+ oid_to_hex(&j->item->object.oid));
+
+ strbuf_release(&ref_name);
+ unlink(git_path_cherry_pick_head(the_repository));
+ rollback_lock_file(&lock);
+
+ rollback_lock_file(&lock);
+ ret = run_command(&cmd);
+
+ /* force re-reading of the cache */
+ if (!ret && (discard_cache() < 0 || read_cache() < 0))
+ ret = error(_("could not read index"));
+ goto leave_merge;
+ }
+
+ merge_commit = to_merge->item;
bases = get_merge_bases(head_commit, merge_commit);
- if (bases && !oidcmp(&merge_commit->object.oid,
- &bases->item->object.oid)) {
+ if (bases && oideq(&merge_commit->object.oid,
+ &bases->item->object.oid)) {
ret = 0;
/* skip merging an ancestor of HEAD */
goto leave_merge;
}
+ write_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ,
+ git_path_merge_head(the_repository), 0);
+ write_message("no-ff", 5, git_path_merge_mode(the_repository), 0);
+
for (j = bases; j; j = j->next)
commit_list_insert(j->item, &reversed);
free_commit_list(bases);
rollback_lock_file(&lock);
if (ret)
- rerere(opts->allow_rerere_auto);
+ repo_rerere(the_repository, opts->allow_rerere_auto);
else
/*
* In case of problems, we now want to return a positive
* value (a negative one would indicate that the `merge`
* command needs to be rescheduled).
*/
- ret = !!run_git_commit(git_path_merge_msg(), opts,
+ ret = !!run_git_commit(git_path_merge_msg(the_repository), opts,
run_commit_flags);
leave_merge:
strbuf_release(&ref_name);
rollback_lock_file(&lock);
+ free_commit_list(to_merge);
return ret;
}
return buf.buf;
}
+static int run_git_checkout(struct replay_opts *opts, const char *commit,
+ const char *action)
+{
+ struct child_process cmd = CHILD_PROCESS_INIT;
+
+ cmd.git_cmd = 1;
+
+ argv_array_push(&cmd.args, "checkout");
+ argv_array_push(&cmd.args, commit);
+ argv_array_pushf(&cmd.env_array, GIT_REFLOG_ACTION "=%s", action);
+
+ if (opts->verbose)
+ return run_command(&cmd);
+ else
+ return run_command_silent_on_success(&cmd);
+}
+
+int prepare_branch_to_be_rebased(struct replay_opts *opts, const char *commit)
+{
+ const char *action;
+
+ if (commit && *commit) {
+ action = reflog_message(opts, "start", "checkout %s", commit);
+ if (run_git_checkout(opts, commit, action))
+ return error(_("could not checkout %s"), commit);
+ }
+
+ return 0;
+}
+
+static int checkout_onto(struct replay_opts *opts,
+ const char *onto_name, const char *onto,
+ const char *orig_head)
+{
+ struct object_id oid;
+ const char *action = reflog_message(opts, "start", "checkout %s", onto_name);
+
+ if (get_oid(orig_head, &oid))
+ return error(_("%s: not a valid OID"), orig_head);
+
+ if (run_git_checkout(opts, onto, action)) {
+ apply_autostash(opts);
+ sequencer_remove_state(opts);
+ return error(_("could not detach HEAD"));
+ }
+
+ return update_ref(NULL, "ORIG_HEAD", &oid, NULL, 0, UPDATE_REFS_MSG_ON_ERR);
+}
+
+static int stopped_at_head(void)
+{
+ struct object_id head;
+ struct commit *commit;
+ struct commit_message message;
+
+ if (get_oid("HEAD", &head) ||
+ !(commit = lookup_commit(the_repository, &head)) ||
+ parse_commit(commit) || get_message(commit, &message))
+ fprintf(stderr, _("Stopped at HEAD\n"));
+ else {
+ fprintf(stderr, _("Stopped at %s\n"), message.label);
+ free_message(commit, &message);
+ }
+ return 0;
+
+}
+
static const char rescheduled_advice[] =
N_("Could not execute the todo command\n"
"\n"
unlink(rebase_path_author_script());
unlink(rebase_path_stopped_sha());
unlink(rebase_path_amend());
+ unlink(git_path_merge_head(the_repository));
delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
+
+ if (item->command == TODO_BREAK)
+ return stopped_at_head();
}
if (item->command <= TODO_SQUASH) {
if (is_rebase_i(opts))
intend_to_amend();
return error_failed_squash(item->commit, opts,
item->arg_len, item->arg);
- } else if (res && is_rebase_i(opts) && item->commit)
+ } else if (res && is_rebase_i(opts) && item->commit) {
+ int to_amend = 0;
+ struct object_id oid;
+
+ /*
+ * If we are rewording and have either
+ * fast-forwarded already, or are about to
+ * create a new root commit, we want to amend,
+ * otherwise we do not.
+ */
+ if (item->command == TODO_REWORD &&
+ !get_oid("HEAD", &oid) &&
+ (oideq(&item->commit->object.oid, &oid) ||
+ (opts->have_squash_onto &&
+ oideq(&opts->squash_onto, &oid))))
+ to_amend = 1;
+
return res | error_with_patch(item->commit,
- item->arg, item->arg_len, opts, res,
- item->command == TODO_REWORD);
+ item->arg, item->arg_len, opts,
+ res, to_amend);
+ }
} else if (item->command == TODO_EXEC) {
char *end_of_arg = (char *)(item->arg + item->arg_len);
int saved = *end_of_arg;
struct object_id orig, head;
memset(&log_tree_opt, 0, sizeof(log_tree_opt));
- init_revisions(&log_tree_opt, NULL);
+ repo_init_revisions(the_repository, &log_tree_opt, NULL);
log_tree_opt.diff = 1;
log_tree_opt.diffopt.output_format =
DIFF_FORMAT_DIFFSTAT;
{
const char *argv[] = { "commit", NULL };
- if (!file_exists(git_path_cherry_pick_head()) &&
- !file_exists(git_path_revert_head()))
+ if (!file_exists(git_path_cherry_pick_head(the_repository)) &&
+ !file_exists(git_path_revert_head(the_repository)))
return error(_("no cherry-pick or revert in progress"));
return run_command_v_opt(argv, RUN_GIT_CMD);
}
if (get_oid_hex(rev.buf, &to_amend))
return error(_("invalid contents: '%s'"),
rebase_path_amend());
- if (!is_clean && oidcmp(&head, &to_amend))
+ if (!is_clean && !oideq(&head, &to_amend))
return error(_("\nYou have uncommitted changes in your "
"working tree. Please, commit them\n"
"first and then run 'git rebase "
* the commit message and if there was a squash, let the user
* edit it.
*/
- if (is_clean && !oidcmp(&head, &to_amend) &&
- opts->current_fixup_count > 0 &&
- file_exists(rebase_path_stopped_sha())) {
+ if (!is_clean || !opts->current_fixup_count)
+ ; /* this is not the final fixup */
+ else if (!oideq(&head, &to_amend) ||
+ !file_exists(rebase_path_stopped_sha())) {
+ /* was a final fixup or squash done manually? */
+ if (!is_fixup(peek_command(todo_list, 0))) {
+ unlink(rebase_path_fixup_msg());
+ unlink(rebase_path_squash_msg());
+ unlink(rebase_path_current_fixups());
+ strbuf_reset(&opts->current_fixups);
+ opts->current_fixup_count = 0;
+ }
+ } else {
+ /* we are in a fixup/squash chain */
const char *p = opts->current_fixups.buf;
int len = opts->current_fixups.len;
}
if (is_clean) {
- const char *cherry_pick_head = git_path_cherry_pick_head();
+ const char *cherry_pick_head = git_path_cherry_pick_head(the_repository);
if (file_exists(cherry_pick_head) && unlink(cherry_pick_head))
return error(_("could not remove CHERRY_PICK_HEAD"));
opts, flags))
return error(_("could not commit staged changes."));
unlink(rebase_path_amend());
+ unlink(git_path_merge_head(the_repository));
if (final_fixup) {
unlink(rebase_path_fixup_msg());
unlink(rebase_path_squash_msg());
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())) {
+ if (file_exists(git_path_cherry_pick_head(the_repository)) ||
+ file_exists(git_path_revert_head(the_repository))) {
res = continue_single_pick();
if (res)
goto release_todo_list;
continue;
if (!get_oid(name, &oid)) {
- if (!lookup_commit_reference_gently(&oid, 1)) {
+ if (!lookup_commit_reference_gently(the_repository, &oid, 1)) {
enum object_type type = oid_object_info(the_repository,
&oid,
NULL);
if (prepare_revision_walk(opts->revs))
return error(_("revision walk setup failed"));
cmit = get_revision(opts->revs);
- if (!cmit || get_revision(opts->revs))
- return error("BUG: expected exactly one commit from walk");
+ if (!cmit)
+ return error(_("empty commit set passed"));
+ if (get_revision(opts->revs))
+ BUG("unexpected extra commit from walk");
return single_pick(cmit, opts);
}
return res;
}
-void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)
+void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag)
{
unsigned no_dup_sob = flag & APPEND_SIGNOFF_DEDUP;
struct strbuf sob = STRBUF_INIT;
*/
while ((commit = get_revision(revs))) {
struct commit_list *to_merge;
- int is_octopus;
const char *p1, *p2;
struct object_id *oid;
int is_empty;
continue;
}
- is_octopus = to_merge && to_merge->next;
-
- if (is_octopus)
- BUG("Octopus merges not yet supported");
-
/* Create a label */
strbuf_reset(&label);
if (skip_prefix(oneline.buf, "Merge ", &p1) &&
strbuf_addf(&buf, "%s -C %s",
cmd_merge, oid_to_hex(&commit->object.oid));
- /* label the tip of merged branch */
- oid = &to_merge->item->object.oid;
- strbuf_addch(&buf, ' ');
+ /* label the tips of merged branches */
+ for (; to_merge; to_merge = to_merge->next) {
+ oid = &to_merge->item->object.oid;
+ strbuf_addch(&buf, ' ');
+
+ if (!oidset_contains(&interesting, oid)) {
+ strbuf_addstr(&buf, label_oid(oid, NULL,
+ &state));
+ continue;
+ }
- if (!oidset_contains(&interesting, oid))
- strbuf_addstr(&buf, label_oid(oid, NULL, &state));
- else {
tips_tail = &commit_list_insert(to_merge->item,
tips_tail)->next;
struct object_id *oid = &parent->item->object.oid;
if (!oidset_contains(&interesting, oid))
continue;
- if (!oidset_contains(&child_seen, oid))
- oidset_insert(&child_seen, oid);
- else
+ if (oidset_insert(&child_seen, oid))
label_oid(oid, "branch-point", &state);
}
entry = oidmap_get(&state.commit2label, &commit->object.oid);
if (entry)
- fprintf(out, "\n# Branch %s\n", entry->string);
+ fprintf(out, "\n%c Branch %s\n", comment_line_char, entry->string);
else
fprintf(out, "\n");
const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick";
int rebase_merges = flags & TODO_LIST_REBASE_MERGES;
- init_revisions(&revs, NULL);
+ repo_init_revisions(the_repository, &revs, NULL);
revs.verbose_header = 1;
if (!rebase_merges)
revs.max_parents = 1;
{
const char *todo_file = rebase_path_todo();
struct todo_list todo_list = TODO_LIST_INIT;
- struct todo_item *item;
struct strbuf *buf = &todo_list.buf;
size_t offset = 0, commands_len = strlen(commands);
- int i, first;
+ int i, insert;
if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0)
return error(_("could not read '%s'."), todo_file);
return error(_("unusable todo list: '%s'"), todo_file);
}
- first = 1;
- /* insert <commands> before every pick except the first one */
- for (item = todo_list.items, i = 0; i < todo_list.nr; i++, item++) {
- if (item->command == TODO_PICK && !first) {
- strbuf_insert(buf, item->offset_in_buf + offset,
- commands, commands_len);
+ /*
+ * Insert <commands> after every pick. Here, fixup/squash chains
+ * are considered part of the pick, so we insert the commands *after*
+ * those chains if there are any.
+ */
+ insert = -1;
+ for (i = 0; i < todo_list.nr; i++) {
+ enum todo_command command = todo_list.items[i].command;
+
+ if (insert >= 0) {
+ /* skip fixup/squash chains */
+ if (command == TODO_COMMENT)
+ continue;
+ else if (is_fixup(command)) {
+ insert = i + 1;
+ continue;
+ }
+ strbuf_insert(buf,
+ todo_list.items[insert].offset_in_buf +
+ offset, commands, commands_len);
offset += commands_len;
+ insert = -1;
}
- first = 0;
+
+ if (command == TODO_PICK || command == TODO_MERGE)
+ insert = i + 1;
}
- /* append final <commands> */
- strbuf_add(buf, commands, commands_len);
+ /* insert or append final <commands> */
+ if (insert >= 0 && insert < todo_list.nr)
+ strbuf_insert(buf, todo_list.items[insert].offset_in_buf +
+ offset, commands, commands_len);
+ else if (insert >= 0 || !offset)
+ strbuf_add(buf, commands, commands_len);
i = write_message(buf->buf, buf->len, todo_file, 0);
todo_list_release(&todo_list);
return i;
}
-enum check_level {
- CHECK_IGNORE = 0, CHECK_WARN, CHECK_ERROR
-};
-
-static enum check_level get_missing_commit_check_level(void)
+enum missing_commit_check_level get_missing_commit_check_level(void)
{
const char *value;
if (git_config_get_value("rebase.missingcommitscheck", &value) ||
!strcasecmp("ignore", value))
- return CHECK_IGNORE;
+ return MISSING_COMMIT_CHECK_IGNORE;
if (!strcasecmp("warn", value))
- return CHECK_WARN;
+ return MISSING_COMMIT_CHECK_WARN;
if (!strcasecmp("error", value))
- return CHECK_ERROR;
+ return MISSING_COMMIT_CHECK_ERROR;
warning(_("unrecognized setting %s for option "
"rebase.missingCommitsCheck. Ignoring."), value);
- return CHECK_IGNORE;
+ return MISSING_COMMIT_CHECK_IGNORE;
}
+define_commit_slab(commit_seen, unsigned char);
/*
* Check if the user dropped some commits by mistake
* Behaviour determined by rebase.missingCommitsCheck.
*/
int check_todo_list(void)
{
- enum check_level check_level = get_missing_commit_check_level();
+ enum missing_commit_check_level check_level = get_missing_commit_check_level();
struct strbuf todo_file = STRBUF_INIT;
struct todo_list todo_list = TODO_LIST_INIT;
struct strbuf missing = STRBUF_INIT;
int advise_to_edit_todo = 0, res = 0, i;
+ struct commit_seen commit_seen;
+
+ init_commit_seen(&commit_seen);
strbuf_addstr(&todo_file, rebase_path_todo());
if (strbuf_read_file_or_whine(&todo_list.buf, todo_file.buf) < 0) {
advise_to_edit_todo = res =
parse_insn_buffer(todo_list.buf.buf, &todo_list);
- if (res || check_level == CHECK_IGNORE)
+ if (res || check_level == MISSING_COMMIT_CHECK_IGNORE)
goto leave_check;
/* Mark the commits in git-rebase-todo as seen */
for (i = 0; i < todo_list.nr; i++) {
struct commit *commit = todo_list.items[i].commit;
if (commit)
- commit->util = (void *)1;
+ *commit_seen_at(&commit_seen, commit) = 1;
}
todo_list_release(&todo_list);
for (i = todo_list.nr - 1; i >= 0; i--) {
struct todo_item *item = todo_list.items + i;
struct commit *commit = item->commit;
- if (commit && !commit->util) {
+ if (commit && !*commit_seen_at(&commit_seen, commit)) {
strbuf_addf(&missing, " - %s %.*s\n",
short_commit_name(commit),
item->arg_len, item->arg);
- commit->util = (void *)1;
+ *commit_seen_at(&commit_seen, commit) = 1;
}
}
if (!missing.len)
goto leave_check;
- if (check_level == CHECK_ERROR)
+ if (check_level == MISSING_COMMIT_CHECK_ERROR)
advise_to_edit_todo = res = 1;
fprintf(stderr,
"The possible behaviours are: ignore, warn, error.\n\n"));
leave_check:
+ clear_commit_seen(&commit_seen);
strbuf_release(&todo_file);
todo_list_release(&todo_list);
}
/* skip picking commits whose parents are unchanged */
-int skip_unnecessary_picks(void)
+static int skip_unnecessary_picks(struct object_id *output_oid)
{
const char *todo_file = rebase_path_todo();
struct strbuf buf = STRBUF_INIT;
struct todo_list todo_list = TODO_LIST_INIT;
- struct object_id onto_oid, *oid = &onto_oid, *parent_oid;
+ struct object_id *parent_oid;
int fd, i;
if (!read_oneliner(&buf, rebase_path_onto(), 0))
return error(_("could not read 'onto'"));
- if (get_oid(buf.buf, &onto_oid)) {
+ if (get_oid(buf.buf, output_oid)) {
strbuf_release(&buf);
return error(_("need a HEAD to fixup"));
}
if (item->commit->parents->next)
break; /* merge commit */
parent_oid = &item->commit->parents->item->object.oid;
- if (hashcmp(parent_oid->hash, oid->hash))
+ if (!oideq(parent_oid, output_oid))
break;
- oid = &item->commit->object.oid;
+ oidcpy(output_oid, &item->commit->object.oid);
}
if (i > 0) {
int offset = get_item_line_offset(&todo_list, i);
todo_list.current = i;
if (is_fixup(peek_command(&todo_list, 0)))
- record_in_rewritten(oid, peek_command(&todo_list, 0));
+ record_in_rewritten(output_oid, peek_command(&todo_list, 0));
}
todo_list_release(&todo_list);
- printf("%s\n", oid_to_hex(oid));
return 0;
}
+int complete_action(struct replay_opts *opts, unsigned flags,
+ const char *shortrevisions, const char *onto_name,
+ const char *onto, const char *orig_head, const char *cmd,
+ unsigned autosquash)
+{
+ const char *shortonto, *todo_file = rebase_path_todo();
+ struct todo_list todo_list = TODO_LIST_INIT;
+ struct strbuf *buf = &(todo_list.buf);
+ struct object_id oid;
+ struct stat st;
+
+ get_oid(onto, &oid);
+ shortonto = find_unique_abbrev(&oid, DEFAULT_ABBREV);
+
+ if (!lstat(todo_file, &st) && st.st_size == 0 &&
+ write_message("noop\n", 5, todo_file, 0))
+ return -1;
+
+ if (autosquash && rearrange_squash())
+ return -1;
+
+ if (cmd && *cmd)
+ sequencer_add_exec_commands(cmd);
+
+ if (strbuf_read_file(buf, todo_file, 0) < 0)
+ return error_errno(_("could not read '%s'."), todo_file);
+
+ if (parse_insn_buffer(buf->buf, &todo_list)) {
+ todo_list_release(&todo_list);
+ return error(_("unusable todo list: '%s'"), todo_file);
+ }
+
+ if (count_commands(&todo_list) == 0) {
+ apply_autostash(opts);
+ sequencer_remove_state(opts);
+ todo_list_release(&todo_list);
+
+ return error(_("nothing to do"));
+ }
+
+ strbuf_addch(buf, '\n');
+ strbuf_commented_addf(buf, Q_("Rebase %s onto %s (%d command)",
+ "Rebase %s onto %s (%d commands)",
+ count_commands(&todo_list)),
+ shortrevisions, shortonto, count_commands(&todo_list));
+ append_todo_help(0, flags & TODO_LIST_KEEP_EMPTY, buf);
+
+ if (write_message(buf->buf, buf->len, todo_file, 0)) {
+ todo_list_release(&todo_list);
+ return -1;
+ }
+
+ if (copy_file(rebase_path_todo_backup(), todo_file, 0666))
+ return error(_("could not copy '%s' to '%s'."), todo_file,
+ rebase_path_todo_backup());
+
+ if (transform_todos(flags | TODO_LIST_SHORTEN_IDS))
+ return error(_("could not transform the todo list"));
+
+ strbuf_reset(buf);
+
+ if (launch_sequence_editor(todo_file, buf, NULL)) {
+ apply_autostash(opts);
+ sequencer_remove_state(opts);
+ todo_list_release(&todo_list);
+
+ return -1;
+ }
+
+ strbuf_stripspace(buf, 1);
+ if (buf->len == 0) {
+ apply_autostash(opts);
+ sequencer_remove_state(opts);
+ todo_list_release(&todo_list);
+
+ return error(_("nothing to do"));
+ }
+
+ todo_list_release(&todo_list);
+
+ if (check_todo_list()) {
+ checkout_onto(opts, onto_name, onto, orig_head);
+ return -1;
+ }
+
+ if (transform_todos(flags & ~(TODO_LIST_SHORTEN_IDS)))
+ return error(_("could not transform the todo list"));
+
+ if (opts->allow_ff && skip_unnecessary_picks(&oid))
+ return error(_("could not skip unnecessary pick commands"));
+
+ if (checkout_onto(opts, onto_name, oid_to_hex(&oid), orig_head))
+ return -1;
+
+ if (require_clean_work_tree("rebase", "", 1, 1))
+ return -1;
+
+ return sequencer_continue(opts);
+}
+
struct subject2item_entry {
struct hashmap_entry entry;
int i;
return key ? strcmp(a->subject, key) : strcmp(a->subject, b->subject);
}
+define_commit_slab(commit_todo_item, struct todo_item *);
+
/*
* Rearrange the todo list that has both "pick commit-id msg" and "pick
* commit-id fixup!/squash! msg" in it so that the latter is put immediately
struct hashmap subject2item;
int res = 0, rearranged = 0, *next, *tail, i;
char **subjects;
+ struct commit_todo_item commit_todo;
if (strbuf_read_file_or_whine(&todo_list.buf, todo_file) < 0)
return -1;
return -1;
}
+ init_commit_todo_item(&commit_todo);
/*
* The hashmap maps onelines to the respective todo list index.
*
if (is_fixup(item->command)) {
todo_list_release(&todo_list);
+ clear_commit_todo_item(&commit_todo);
return error(_("the script was already rearranged."));
}
- item->commit->util = item;
+ *commit_todo_item_at(&commit_todo, item->commit) = item;
parse_commit(item->commit);
commit_buffer = get_commit_buffer(item->commit, NULL);
else if (!strchr(p, ' ') &&
(commit2 =
lookup_commit_reference_by_name(p)) &&
- commit2->util)
+ *commit_todo_item_at(&commit_todo, commit2))
/* found by commit name */
- i2 = (struct todo_item *)commit2->util
+ i2 = *commit_todo_item_at(&commit_todo, commit2)
- todo_list.items;
else {
/* copy can be a prefix of the commit subject */
hashmap_free(&subject2item, 1);
todo_list_release(&todo_list);
+ clear_commit_todo_item(&commit_todo);
return res;
}