diff options
Diffstat (limited to 'builtin/revert.c')
| -rw-r--r-- | builtin/revert.c | 319 |
1 files changed, 209 insertions, 110 deletions
diff --git a/builtin/revert.c b/builtin/revert.c index eff52687a8..7976b5a329 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -13,6 +13,7 @@ #include "revision.h" #include "rerere.h" #include "merge-recursive.h" +#include "refs.h" /* * This implements the builtins revert and cherry-pick. @@ -35,16 +36,19 @@ static const char * const cherry_pick_usage[] = { NULL }; -static int edit, no_replay, no_commit, mainline, signoff; +static int edit, no_replay, no_commit, mainline, signoff, allow_ff; static enum { REVERT, CHERRY_PICK } action; static struct commit *commit; static const char *commit_name; static int allow_rerere_auto; static const char *me; +static const char *strategy; #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" +static char *get_encoding(const char *message); + static void parse_args(int argc, const char **argv) { const char * const * usage_str = @@ -59,9 +63,21 @@ static void parse_args(int argc, const char **argv) OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"), OPT_INTEGER('m', "mainline", &mainline, "parent number"), OPT_RERERE_AUTOUPDATE(&allow_rerere_auto), + OPT_STRING(0, "strategy", &strategy, "strategy", "merge strategy"), + OPT_END(), + OPT_END(), OPT_END(), }; + if (action == CHERRY_PICK) { + struct option cp_extra[] = { + OPT_BOOLEAN(0, "ff", &allow_ff, "allow fast-forward"), + OPT_END(), + }; + if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra)) + die("program error"); + } + if (parse_options(argc, argv, NULL, options, usage_str, 0) != 1) usage_with_options(usage_str, options); @@ -73,33 +89,69 @@ static void parse_args(int argc, const char **argv) exit(1); } -static char *get_oneline(const char *message) +struct commit_message { + char *parent_label; + const char *label; + const char *subject; + char *reencoded_message; + const char *message; +}; + +static int get_message(const char *raw_message, struct commit_message *out) { - char *result; - const char *p = message, *abbrev, *eol; + const char *encoding; + const char *p, *abbrev, *eol; + char *q; int abbrev_len, oneline_len; - if (!p) - die ("Could not read commit message of %s", - sha1_to_hex(commit->object.sha1)); + if (!raw_message) + return -1; + encoding = get_encoding(raw_message); + if (!encoding) + encoding = "UTF-8"; + if (!git_commit_encoding) + git_commit_encoding = "UTF-8"; + + out->reencoded_message = NULL; + out->message = raw_message; + if (strcmp(encoding, git_commit_encoding)) + out->reencoded_message = reencode_string(raw_message, + git_commit_encoding, encoding); + if (out->reencoded_message) + out->message = out->reencoded_message; + + abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV); + abbrev_len = strlen(abbrev); + + /* Find beginning and end of commit subject. */ + p = out->message; while (*p && (*p != '\n' || p[1] != '\n')) p++; - if (*p) { p += 2; for (eol = p + 1; *eol && *eol != '\n'; eol++) ; /* do nothing */ } else eol = p; - abbrev = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV); - abbrev_len = strlen(abbrev); oneline_len = eol - p; - result = xmalloc(abbrev_len + 5 + oneline_len); - memcpy(result, abbrev, abbrev_len); - memcpy(result + abbrev_len, "... ", 4); - memcpy(result + abbrev_len + 4, p, oneline_len); - result[abbrev_len + 4 + oneline_len] = '\0'; - return result; + + out->parent_label = xmalloc(strlen("parent of ") + abbrev_len + + strlen("... ") + oneline_len + 1); + q = out->parent_label; + q = mempcpy(q, "parent of ", strlen("parent of ")); + out->label = q; + q = mempcpy(q, abbrev, abbrev_len); + q = mempcpy(q, "... ", strlen("... ")); + out->subject = q; + q = mempcpy(q, p, oneline_len); + *q = '\0'; + return 0; +} + +static void free_message(struct commit_message *msg) +{ + free(msg->parent_label); + free(msg->reencoded_message); } static char *get_encoding(const char *message) @@ -124,28 +176,17 @@ static char *get_encoding(const char *message) return NULL; } -static struct lock_file msg_file; -static int msg_fd; - -static void add_to_msg(const char *string) -{ - int len = strlen(string); - if (write_in_full(msg_fd, string, len) < 0) - die_errno ("Could not write to MERGE_MSG"); -} - -static void add_message_to_msg(const char *message) +static void add_message_to_msg(struct strbuf *msgbuf, const char *message) { const char *p = message; while (*p && (*p != '\n' || p[1] != '\n')) p++; if (!*p) - add_to_msg(sha1_to_hex(commit->object.sha1)); + strbuf_addstr(msgbuf, sha1_to_hex(commit->object.sha1)); p += 2; - add_to_msg(p); - return; + strbuf_addstr(msgbuf, p); } static void set_author_ident_env(const char *message) @@ -221,6 +262,19 @@ static char *help_msg(const char *name) return strbuf_detach(&helpbuf, NULL); } +static void write_message(struct strbuf *msgbuf, const char *filename) +{ + static struct lock_file msg_file; + + int msg_fd = hold_lock_file_for_update(&msg_file, filename, + LOCK_DIE_ON_ERROR); + if (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0) + die_errno("Could not write to %s.", filename); + strbuf_release(msgbuf); + if (commit_lock_file(&msg_file) < 0) + die("Error wrapping up %s", filename); +} + static struct tree *empty_tree(void) { struct tree *tree = xcalloc(1, sizeof(struct tree)); @@ -244,18 +298,82 @@ static NORETURN void die_dirty_index(const char *me) } } -static int revert_or_cherry_pick(int argc, const char **argv) +static int fast_forward_to(const unsigned char *to, const unsigned char *from) +{ + struct ref_lock *ref_lock; + + read_cache(); + if (checkout_fast_forward(from, to)) + exit(1); /* the callee should have complained already */ + ref_lock = lock_any_ref_for_update("HEAD", from, 0); + return write_ref_sha1(ref_lock, to, "cherry-pick"); +} + +static void do_recursive_merge(struct commit *base, struct commit *next, + const char *base_label, const char *next_label, + unsigned char *head, struct strbuf *msgbuf, + char *defmsg) { - unsigned char head[20]; - struct commit *base, *next, *parent; - int i, index_fd, clean; - char *oneline, *reencoded_message = NULL; - const char *message, *encoding; - char *defmsg = git_pathdup("MERGE_MSG"); struct merge_options o; struct tree *result, *next_tree, *base_tree, *head_tree; + int clean, index_fd; static struct lock_file index_lock; + index_fd = hold_locked_index(&index_lock, 1); + + read_cache(); + init_merge_options(&o); + o.ancestor = base ? base_label : "(empty tree)"; + o.branch1 = "HEAD"; + o.branch2 = next ? next_label : "(empty tree)"; + + head_tree = parse_tree_indirect(head); + next_tree = next ? next->tree : empty_tree(); + base_tree = base ? base->tree : empty_tree(); + + clean = merge_trees(&o, + head_tree, + next_tree, base_tree, &result); + + if (active_cache_changed && + (write_cache(index_fd, active_cache, active_nr) || + commit_locked_index(&index_lock))) + die("%s: Unable to write new index file", me); + rollback_lock_file(&index_lock); + + if (!clean) { + int i; + strbuf_addstr(msgbuf, "\nConflicts:\n\n"); + for (i = 0; i < active_nr;) { + struct cache_entry *ce = active_cache[i++]; + if (ce_stage(ce)) { + strbuf_addch(msgbuf, '\t'); + strbuf_addstr(msgbuf, ce->name); + strbuf_addch(msgbuf, '\n'); + while (i < active_nr && !strcmp(ce->name, + active_cache[i]->name)) + i++; + } + } + write_message(msgbuf, defmsg); + fprintf(stderr, "Automatic %s failed.%s\n", + me, help_msg(commit_name)); + rerere(allow_rerere_auto); + exit(1); + } + write_message(msgbuf, defmsg); + fprintf(stderr, "Finished one %s.\n", me); +} + +static int revert_or_cherry_pick(int argc, const char **argv) +{ + unsigned char head[20]; + struct commit *base, *next, *parent; + const char *base_label, *next_label; + struct commit_message msg = { NULL, NULL, NULL, NULL, NULL }; + char *defmsg = NULL; + struct strbuf msgbuf = STRBUF_INIT; + git_config(git_default_config, NULL); me = action == REVERT ? "revert" : "cherry-pick"; setenv(GIT_REFLOG_ACTION, me, 0); @@ -265,6 +383,17 @@ static int revert_or_cherry_pick(int argc, const char **argv) if (action == REVERT && !no_replay) die("revert is incompatible with replay"); + if (allow_ff) { + if (signoff) + die("cherry-pick --ff cannot be used with --signoff"); + if (no_commit) + die("cherry-pick --ff cannot be used with --no-commit"); + if (no_replay) + die("cherry-pick --ff cannot be used with -x"); + if (edit) + die("cherry-pick --ff cannot be used with --edit"); + } + if (read_cache() < 0) die("git %s: failed to read the index", me); if (no_commit) { @@ -284,8 +413,6 @@ static int revert_or_cherry_pick(int argc, const char **argv) } discard_cache(); - index_fd = hold_locked_index(&index_lock, 1); - if (!commit->parents) { if (action == REVERT) die ("Cannot revert a root commit"); @@ -314,14 +441,17 @@ static int revert_or_cherry_pick(int argc, const char **argv) else parent = commit->parents->item; - if (!(message = commit->buffer)) - die ("Cannot get commit message for %s", - sha1_to_hex(commit->object.sha1)); + if (allow_ff && !hashcmp(parent->object.sha1, head)) + return fast_forward_to(commit->object.sha1, head); if (parent && parse_commit(parent) < 0) die("%s: cannot parse parent commit %s", me, sha1_to_hex(parent->object.sha1)); + if (get_message(commit->buffer, &msg) != 0) + die("Cannot get commit message for %s", + sha1_to_hex(commit->object.sha1)); + /* * "commit" is an existing commit. We would want to apply * the difference it introduces since its first parent "prev" @@ -329,89 +459,58 @@ static int revert_or_cherry_pick(int argc, const char **argv) * reverse of it if we are revert. */ - msg_fd = hold_lock_file_for_update(&msg_file, defmsg, - LOCK_DIE_ON_ERROR); - - encoding = get_encoding(message); - if (!encoding) - encoding = "UTF-8"; - if (!git_commit_encoding) - git_commit_encoding = "UTF-8"; - if ((reencoded_message = reencode_string(message, - git_commit_encoding, encoding))) - message = reencoded_message; - - oneline = get_oneline(message); + defmsg = git_pathdup("MERGE_MSG"); if (action == REVERT) { - char *oneline_body = strchr(oneline, ' '); - base = commit; + base_label = msg.label; next = parent; - add_to_msg("Revert \""); - add_to_msg(oneline_body + 1); - add_to_msg("\"\n\nThis reverts commit "); - add_to_msg(sha1_to_hex(commit->object.sha1)); + next_label = msg.parent_label; + strbuf_addstr(&msgbuf, "Revert \""); + strbuf_addstr(&msgbuf, msg.subject); + strbuf_addstr(&msgbuf, "\"\n\nThis reverts commit "); + strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1)); if (commit->parents->next) { - add_to_msg(", reversing\nchanges made to "); - add_to_msg(sha1_to_hex(parent->object.sha1)); + strbuf_addstr(&msgbuf, ", reversing\nchanges made to "); + strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1)); } - add_to_msg(".\n"); + strbuf_addstr(&msgbuf, ".\n"); } else { base = parent; + base_label = msg.parent_label; next = commit; - set_author_ident_env(message); - add_message_to_msg(message); + next_label = msg.label; + set_author_ident_env(msg.message); + add_message_to_msg(&msgbuf, msg.message); if (no_replay) { - add_to_msg("(cherry picked from commit "); - add_to_msg(sha1_to_hex(commit->object.sha1)); - add_to_msg(")\n"); + strbuf_addstr(&msgbuf, "(cherry picked from commit "); + strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1)); + strbuf_addstr(&msgbuf, ")\n"); } } - read_cache(); - init_merge_options(&o); - o.branch1 = "HEAD"; - o.branch2 = oneline; - - head_tree = parse_tree_indirect(head); - next_tree = next ? next->tree : empty_tree(); - base_tree = base ? base->tree : empty_tree(); - - clean = merge_trees(&o, - head_tree, - next_tree, base_tree, &result); - - if (active_cache_changed && - (write_cache(index_fd, active_cache, active_nr) || - commit_locked_index(&index_lock))) - die("%s: Unable to write new index file", me); - rollback_lock_file(&index_lock); - - if (!clean) { - add_to_msg("\nConflicts:\n\n"); - for (i = 0; i < active_nr;) { - struct cache_entry *ce = active_cache[i++]; - if (ce_stage(ce)) { - add_to_msg("\t"); - add_to_msg(ce->name); - add_to_msg("\n"); - while (i < active_nr && !strcmp(ce->name, - active_cache[i]->name)) - i++; - } + if (!strategy || !strcmp(strategy, "recursive") || action == REVERT) + do_recursive_merge(base, next, base_label, next_label, + head, &msgbuf, defmsg); + else { + int res; + struct commit_list *common = NULL; + struct commit_list *remotes = NULL; + write_message(&msgbuf, defmsg); + commit_list_insert(base, &common); + commit_list_insert(next, &remotes); + res = try_merge_command(strategy, common, + sha1_to_hex(head), remotes); + free_commit_list(common); + free_commit_list(remotes); + if (res) { + fprintf(stderr, "Automatic %s with strategy %s failed.%s\n", + me, strategy, help_msg(commit_name)); + rerere(allow_rerere_auto); + exit(1); } - if (commit_lock_file(&msg_file) < 0) - die ("Error wrapping up %s", defmsg); - fprintf(stderr, "Automatic %s failed.%s\n", - me, help_msg(commit_name)); - rerere(allow_rerere_auto); - exit(1); } - if (commit_lock_file(&msg_file) < 0) - die ("Error wrapping up %s", defmsg); - fprintf(stderr, "Finished one %s.\n", me); /* * @@ -437,7 +536,7 @@ static int revert_or_cherry_pick(int argc, const char **argv) args[i] = NULL; return execv_git_cmd(args); } - free(reencoded_message); + free_message(&msg); free(defmsg); return 0; |
