@ -13,6 +13,8 @@
@@ -13,6 +13,8 @@
#include "rerere.h"
#include "merge-recursive.h"
#include "refs.h"
#include "dir.h"
#include "sequencer.h"
/*
* This implements the builtins revert and cherry-pick.
@ -27,85 +29,189 @@
@@ -27,85 +29,189 @@
static const char * const revert_usage[] = {
"git revert [options] <commit-ish>",
"git revert <subcommand>",
NULL
};
static const char * const cherry_pick_usage[] = {
"git cherry-pick [options] <commit-ish>",
"git cherry-pick <subcommand>",
NULL
};
static int edit, no_replay, no_commit, mainline, signoff, allow_ff;
static enum { REVERT, CHERRY_PICK } action;
static struct commit *commit;
static int commit_argc;
static const char **commit_argv;
static int allow_rerere_auto;
static const char *me;
/* Merge strategy. */
static const char *strategy;
static const char **xopts;
static size_t xopts_nr, xopts_alloc;
enum replay_action { REVERT, CHERRY_PICK };
enum replay_subcommand { REPLAY_NONE, REPLAY_RESET, REPLAY_CONTINUE };
struct replay_opts {
enum replay_action action;
enum replay_subcommand subcommand;
/* Boolean options */
int edit;
int record_origin;
int no_commit;
int signoff;
int allow_ff;
int allow_rerere_auto;
int mainline;
int commit_argc;
const char **commit_argv;
/* Merge strategy */
const char *strategy;
const char **xopts;
size_t xopts_nr, xopts_alloc;
};
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
static const char *action_name(const struct replay_opts *opts)
{
return opts->action == REVERT ? "revert" : "cherry-pick";
}
static char *get_encoding(const char *message);
static const char * const *revert_or_cherry_pick_usage(void)
static const char * const *revert_or_cherry_pick_usage(struct replay_opts *opts)
{
return action == REVERT ? revert_usage : cherry_pick_usage;
return opts->action == REVERT ? revert_usage : cherry_pick_usage;
}
static int option_parse_x(const struct option *opt,
const char *arg, int unset)
{
struct replay_opts **opts_ptr = opt->value;
struct replay_opts *opts = *opts_ptr;
if (unset)
return 0;
ALLOC_GROW(xopts, xopts_nr + 1, xopts_alloc);
xopts[xopts_nr++] = xstrdup(arg);
ALLOC_GROW(opts->xopts, opts->xopts_nr + 1, opts->xopts_alloc);
opts->xopts[opts->xopts_nr++] = xstrdup(arg);
return 0;
}
static void parse_args(int argc, const char **argv)
static void verify_opt_compatible(const char *me, const char *base_opt, ...)
{
const char *this_opt;
va_list ap;
va_start(ap, base_opt);
while ((this_opt = va_arg(ap, const char *))) {
if (va_arg(ap, int))
break;
}
va_end(ap);
if (this_opt)
die(_("%s: %s cannot be used with %s"), me, this_opt, base_opt);
}
static void verify_opt_mutually_compatible(const char *me, ...)
{
const char *opt1, *opt2;
va_list ap;
va_start(ap, me);
while ((opt1 = va_arg(ap, const char *))) {
if (va_arg(ap, int))
break;
}
if (opt1) {
while ((opt2 = va_arg(ap, const char *))) {
if (va_arg(ap, int))
break;
}
}
if (opt1 && opt2)
die(_("%s: %s cannot be used with %s"), me, opt1, opt2);
}
static void parse_args(int argc, const char **argv, struct replay_opts *opts)
{
const char * const * usage_str = revert_or_cherry_pick_usage();
const char * const * usage_str = revert_or_cherry_pick_usage(opts);
const char *me = action_name(opts);
int noop;
int reset = 0;
int contin = 0;
struct option options[] = {
OPT_BOOLEAN('n', "no-commit", &no_commit, "don't automatically commit"),
OPT_BOOLEAN('e', "edit", &edit, "edit the commit message"),
OPT_BOOLEAN(0, "reset", &reset, "forget the current operation"),
OPT_BOOLEAN(0, "continue", &contin, "continue the current operation"),
OPT_BOOLEAN('n', "no-commit", &opts->no_commit, "don't automatically commit"),
OPT_BOOLEAN('e', "edit", &opts->edit, "edit the commit message"),
{ OPTION_BOOLEAN, 'r', NULL, &noop, NULL, "no-op (backward compatibility)",
PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 0 },
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_CALLBACK('X', "strategy-option", &xopts, "option",
OPT_BOOLEAN('s', "signoff", &opts->signoff, "add Signed-off-by:"),
OPT_INTEGER('m', "mainline", &opts->mainline, "parent number"),
OPT_RERERE_AUTOUPDATE(&opts->allow_rerere_auto),
OPT_STRING(0, "strategy", &opts->strategy, "strategy", "merge strategy"),
OPT_CALLBACK('X', "strategy-option", &opts, "option",
"option for merge strategy", option_parse_x),
OPT_END(),
OPT_END(),
OPT_END(),
};
if (action == CHERRY_PICK) {
if (opts->action == CHERRY_PICK) {
struct option cp_extra[] = {
OPT_BOOLEAN('x', NULL, &no_replay, "append commit name"),
OPT_BOOLEAN(0, "ff", &allow_ff, "allow fast-forward"),
OPT_BOOLEAN('x', NULL, &opts->record_origin, "append commit name"),
OPT_BOOLEAN(0, "ff", &opts->allow_ff, "allow fast-forward"),
OPT_END(),
};
if (parse_options_concat(options, ARRAY_SIZE(options), cp_extra))
die(_("program error"));
}
commit_argc = parse_options(argc, argv, NULL, options, usage_str,
PARSE_OPT_KEEP_ARGV0 |
PARSE_OPT_KEEP_UNKNOWN);
if (commit_argc < 2)
opts->commit_argc = parse_options(argc, argv, NULL, options, usage_str,
PARSE_OPT_KEEP_ARGV0 |
PARSE_OPT_KEEP_UNKNOWN);
/* Check for incompatible subcommands */
verify_opt_mutually_compatible(me,
"--reset", reset,
"--continue", contin,
NULL);
/* Set the subcommand */
if (reset)
opts->subcommand = REPLAY_RESET;
else if (contin)
opts->subcommand = REPLAY_CONTINUE;
else
opts->subcommand = REPLAY_NONE;
/* Check for incompatible command line arguments */
if (opts->subcommand != REPLAY_NONE) {
char *this_operation;
if (opts->subcommand == REPLAY_RESET)
this_operation = "--reset";
else
this_operation = "--continue";
verify_opt_compatible(me, this_operation,
"--no-commit", opts->no_commit,
"--signoff", opts->signoff,
"--mainline", opts->mainline,
"--strategy", opts->strategy ? 1 : 0,
"--strategy-option", opts->xopts ? 1 : 0,
"-x", opts->record_origin,
"--ff", opts->allow_ff,
NULL);
}
else if (opts->commit_argc < 2)
usage_with_options(usage_str, options);
commit_argv = argv;
if (opts->allow_ff)
verify_opt_compatible(me, "--ff",
"--signoff", opts->signoff,
"--no-commit", opts->no_commit,
"-x", opts->record_origin,
"--edit", opts->edit,
NULL);
opts->commit_argv = argv;
}
struct commit_message {
@ -116,25 +222,25 @@ struct commit_message {
@@ -116,25 +222,25 @@ struct commit_message {
const char *message;
};
static int get_message(const char *raw_message, struct commit_message *out)
static int get_message(struct commit *commit, struct commit_message *out)
{
const char *encoding;
const char *abbrev, *subject;
int abbrev_len, subject_len;
char *q;
if (!raw_message)
if (!commit->buffer)
return -1;
encoding = get_encoding(raw_message);
encoding = get_encoding(commit->buffer);
if (!encoding)
encoding = "UTF-8";
if (!git_commit_encoding)
git_commit_encoding = "UTF-8";
out->reencoded_message = NULL;
out->message = raw_message;
out->message = commit->buffer;
if (strcmp(encoding, git_commit_encoding))
out->reencoded_message = reencode_string(raw_message,
out->reencoded_message = reencode_string(commit->buffer,
git_commit_encoding, encoding);
if (out->reencoded_message)
out->message = out->reencoded_message;
@ -167,9 +273,6 @@ static char *get_encoding(const char *message)
@@ -167,9 +273,6 @@ static char *get_encoding(const char *message)
{
const char *p = message, *eol;
if (!p)
die (_("Could not read commit message of %s"),
sha1_to_hex(commit->object.sha1));
while (*p && *p != '\n') {
for (eol = p + 1; *eol && *eol != '\n'; eol++)
; /* do nothing */
@ -185,20 +288,7 @@ static char *get_encoding(const char *message)
@@ -185,20 +288,7 @@ static char *get_encoding(const char *message)
return NULL;
}
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)
strbuf_addstr(msgbuf, sha1_to_hex(commit->object.sha1));
p += 2;
strbuf_addstr(msgbuf, p);
}
static void write_cherry_pick_head(void)
static void write_cherry_pick_head(struct commit *commit)
{
int fd;
struct strbuf buf = STRBUF_INIT;
@ -214,15 +304,6 @@ static void write_cherry_pick_head(void)
@@ -214,15 +304,6 @@ static void write_cherry_pick_head(void)
strbuf_release(&buf);
}
static void advise(const char *advice, ...)
{
va_list params;
va_start(params, advice);
vreportf("hint: ", advice, params);
va_end(params);
}
static void print_advice(void)
{
char *msg = getenv("GIT_CHERRY_PICK_HELP");
@ -261,25 +342,20 @@ static struct tree *empty_tree(void)
@@ -261,25 +342,20 @@ static struct tree *empty_tree(void)
return lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN);
}
static NORETURN void die_dirty_index(const char *me)
static int error_dirty_index(struct replay_opts *opts)
{
if (read_cache_unmerged()) {
die_resolve_conflict(me);
} else {
if (advice_commit_before_merge) {
if (action == REVERT)
die(_("Your local changes would be overwritten by revert.\n"
"Please, commit your changes or stash them to proceed."));
else
die(_("Your local changes would be overwritten by cherry-pick.\n"
"Please, commit your changes or stash them to proceed."));
} else {
if (action == REVERT)
die(_("Your local changes would be overwritten by revert.\n"));
else
die(_("Your local changes would be overwritten by cherry-pick.\n"));
}
}
if (read_cache_unmerged())
return error_resolve_conflict(action_name(opts));
/* Different translation strings for cherry-pick and revert */
if (opts->action == CHERRY_PICK)
error(_("Your local changes would be overwritten by cherry-pick."));
else
error(_("Your local changes would be overwritten by revert."));
if (advice_commit_before_merge)
advise(_("Commit your changes or stash them to proceed."));
return -1;
}
static int fast_forward_to(const unsigned char *to, const unsigned char *from)
@ -295,7 +371,8 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from)
@@ -295,7 +371,8 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from)
static int do_recursive_merge(struct commit *base, struct commit *next,
const char *base_label, const char *next_label,
unsigned char *head, struct strbuf *msgbuf)
unsigned char *head, struct strbuf *msgbuf,
struct replay_opts *opts)
{
struct merge_options o;
struct tree *result, *next_tree, *base_tree, *head_tree;
@ -316,7 +393,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
@@ -316,7 +393,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
next_tree = next ? next->tree : empty_tree();
base_tree = base ? base->tree : empty_tree();
for (xopt = xopts; xopt != xopts + xopts_nr; xopt++)
for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++)
parse_merge_opt(&o, *xopt);
clean = merge_trees(&o,
@ -327,7 +404,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
@@ -327,7 +404,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
(write_cache(index_fd, active_cache, active_nr) ||
commit_locked_index(&index_lock)))
/* TRANSLATORS: %s will be "revert" or "cherry-pick" */
die(_("%s: Unable to write new index file"), me);
die(_("%s: Unable to write new index file"), action_name(opts));
rollback_lock_file(&index_lock);
if (!clean) {
@ -356,7 +433,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
@@ -356,7 +433,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
* 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.
*/
static int run_git_commit(const char *defmsg)
static int run_git_commit(const char *defmsg, struct replay_opts *opts)
{
/* 6 is max possible length of our args array including NULL */
const char *args[6];
@ -364,9 +441,9 @@ static int run_git_commit(const char *defmsg)
@@ -364,9 +441,9 @@ static int run_git_commit(const char *defmsg)
args[i++] = "commit";
args[i++] = "-n";
if (signoff)
if (opts->signoff)
args[i++] = "-s";
if (!edit) {
if (!opts->edit) {
args[i++] = "-F";
args[i++] = defmsg;
}
@ -375,7 +452,7 @@ static int run_git_commit(const char *defmsg)
@@ -375,7 +452,7 @@ static int run_git_commit(const char *defmsg)
return run_command_v_opt(args, RUN_GIT_CMD);
}
static int do_pick_commit(void)
static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
{
unsigned char head[20];
struct commit *base, *next, *parent;
@ -385,7 +462,7 @@ static int do_pick_commit(void)
@@ -385,7 +462,7 @@ static int do_pick_commit(void)
struct strbuf msgbuf = STRBUF_INIT;
int res;
if (no_commit) {
if (opts->no_commit) {
/*
* We do not intend to commit immediately. We just want to
* merge the differences in, so let's compute the tree
@ -396,9 +473,9 @@ static int do_pick_commit(void)
@@ -396,9 +473,9 @@ static int do_pick_commit(void)
die (_("Your index file is unmerged."));
} else {
if (get_sha1("HEAD", head))
die (_("You do not have a valid HEAD"));
return error(_("You do not have a valid HEAD"));
if (index_differs_from("HEAD", 0))
die_dirty_index(me);
return error_dirty_index(opts);
}
discard_cache();
@ -410,36 +487,36 @@ static int do_pick_commit(void)
@@ -410,36 +487,36 @@ static int do_pick_commit(void)
int cnt;
struct commit_list *p;
if (!mainline)
die(_("Commit %s is a merge but no -m option was given."),
sha1_to_hex(commit->object.sha1));
if (!opts->mainline)
return error(_("Commit %s is a merge but no -m option was given."),
sha1_to_hex(commit->object.sha1));
for (cnt = 1, p = commit->parents;
cnt != mainline && p;
cnt != opts->mainline && p;
cnt++)
p = p->next;
if (cnt != mainline || !p)
die(_("Commit %s does not have parent %d"),
sha1_to_hex(commit->object.sha1), mainline);
if (cnt != opts->mainline || !p)
return error(_("Commit %s does not have parent %d"),
sha1_to_hex(commit->object.sha1), opts->mainline);
parent = p->item;
} else if (0 < mainline)
die(_("Mainline was specified but commit %s is not a merge."),
sha1_to_hex(commit->object.sha1));
} else if (0 < opts->mainline)
return error(_("Mainline was specified but commit %s is not a merge."),
sha1_to_hex(commit->object.sha1));
else
parent = commit->parents->item;
if (allow_ff && parent && !hashcmp(parent->object.sha1, head))
if (opts->allow_ff && parent && !hashcmp(parent->object.sha1, head))
return fast_forward_to(commit->object.sha1, head);
if (parent && parse_commit(parent) < 0)
/* TRANSLATORS: The first %s will be "revert" or
"cherry-pick", the second %s a SHA1 */
die(_("%s: cannot parse parent commit %s"),
me, sha1_to_hex(parent->object.sha1));
return error(_("%s: cannot parse parent commit %s"),
action_name(opts), 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));
if (get_message(commit, &msg) != 0)
return error(_("Cannot get commit message for %s"),
sha1_to_hex(commit->object.sha1));
/*
* "commit" is an existing commit. We would want to apply
@ -450,7 +527,7 @@ static int do_pick_commit(void)
@@ -450,7 +527,7 @@ static int do_pick_commit(void)
defmsg = git_pathdup("MERGE_MSG");
if (action == REVERT) {
if (opts->action == REVERT) {
base = commit;
base_label = msg.label;
next = parent;
@ -466,23 +543,36 @@ static int do_pick_commit(void)
@@ -466,23 +543,36 @@ static int do_pick_commit(void)
}
strbuf_addstr(&msgbuf, ".\n");
} else {
const char *p;
base = parent;
base_label = msg.parent_label;
next = commit;
next_label = msg.label;
add_message_to_msg(&msgbuf, msg.message);
if (no_replay) {
/*
* Append the commit log message to msgbuf; it starts
* after the tree, parent, author, committer
* information followed by "\n\n".
*/
p = strstr(msg.message, "\n\n");
if (p) {
p += 2;
strbuf_addstr(&msgbuf, p);
}
if (opts->record_origin) {
strbuf_addstr(&msgbuf, "(cherry picked from commit ");
strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
strbuf_addstr(&msgbuf, ")\n");
}
if (!no_commit)
write_cherry_pick_head();
if (!opts->no_commit)
write_cherry_pick_head(commit);
}
if (!strategy || !strcmp(strategy, "recursive") || action == REVERT) {
if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REVERT) {
res = do_recursive_merge(base, next, base_label, next_label,
head, &msgbuf);
head, &msgbuf, opts);
write_message(&msgbuf, defmsg);
} else {
struct commit_list *common = NULL;
@ -492,23 +582,23 @@ static int do_pick_commit(void)
@@ -492,23 +582,23 @@ static int do_pick_commit(void)
commit_list_insert(base, &common);
commit_list_insert(next, &remotes);
res = try_merge_command(strategy, xopts_nr, xopts, common,
sha1_to_hex(head), remotes);
res = try_merge_command(opts->strategy, opts->xopts_nr, opts->xopts,
common, sha1_to_hex(head), remotes);
free_commit_list(common);
free_commit_list(remotes);
}
if (res) {
error(action == REVERT
error(opts->action == REVERT
? _("could not revert %s... %s")
: _("could not apply %s... %s"),
find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV),
msg.subject);
print_advice();
rerere(allow_rerere_auto);
rerere(opts->allow_rerere_auto);
} else {
if (!no_commit)
res = run_git_commit(defmsg);
if (!opts->no_commit)
res = run_git_commit(defmsg, opts);
}
free_message(&msg);
@ -517,18 +607,18 @@ static int do_pick_commit(void)
@@ -517,18 +607,18 @@ static int do_pick_commit(void)
return res;
}
static void prepare_revs(struct rev_info *revs)
static void prepare_revs(struct rev_info *revs, struct replay_opts *opts)
{
int argc;
init_revisions(revs, NULL);
revs->no_walk = 1;
if (action != REVERT)
if (opts->action != REVERT)
revs->reverse = 1;
argc = setup_revisions(commit_argc, commit_argv, revs, NULL);
argc = setup_revisions(opts->commit_argc, opts->commit_argv, revs, NULL);
if (argc > 1)
usage(*revert_or_cherry_pick_usage());
usage(*revert_or_cherry_pick_usage(opts));
if (prepare_revision_walk(revs))
die(_("revision walk setup failed"));
@ -537,64 +627,403 @@ static void prepare_revs(struct rev_info *revs)
@@ -537,64 +627,403 @@ static void prepare_revs(struct rev_info *revs)
die(_("empty commit set passed"));
}
static void read_and_refresh_cache(const char *me)
static void read_and_refresh_cache(struct replay_opts *opts)
{
static struct lock_file index_lock;
int index_fd = hold_locked_index(&index_lock, 0);
if (read_index_preload(&the_index, NULL) < 0)
die(_("git %s: failed to read the index"), me);
die(_("git %s: failed to read the index"), action_name(opts));
refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
if (the_index.cache_changed) {
if (write_index(&the_index, index_fd) ||
commit_locked_index(&index_lock))
die(_("git %s: failed to refresh the index"), me);
die(_("git %s: failed to refresh the index"), action_name(opts));
}
rollback_lock_file(&index_lock);
}
static int revert_or_cherry_pick(int argc, const char **argv)
/*
* Append a commit to the end of the commit_list.
*
* next starts by pointing to the variable that holds the head of an
* empty commit_list, and is updated to point to the "next" field of
* the last item on the list as new commits are appended.
*
* Usage example:
*
* struct commit_list *list;
* struct commit_list **next = &list;
*
* next = commit_list_append(c1, next);
* next = commit_list_append(c2, next);
* assert(commit_list_count(list) == 2);
* return list;
*/
static struct commit_list **commit_list_append(struct commit *commit,
struct commit_list **next)
{
struct commit_list *new = xmalloc(sizeof(struct commit_list));
new->item = commit;
*next = new;
new->next = NULL;
return &new->next;
}
static int format_todo(struct strbuf *buf, struct commit_list *todo_list,
struct replay_opts *opts)
{
struct commit_list *cur = NULL;
struct commit_message msg = { NULL, NULL, NULL, NULL, NULL };
const char *sha1_abbrev = NULL;
const char *action_str = opts->action == REVERT ? "revert" : "pick";
for (cur = todo_list; cur; cur = cur->next) {
sha1_abbrev = find_unique_abbrev(cur->item->object.sha1, DEFAULT_ABBREV);
if (get_message(cur->item, &msg))
return error(_("Cannot get commit message for %s"), sha1_abbrev);
strbuf_addf(buf, "%s %s %s\n", action_str, sha1_abbrev, msg.subject);
}
return 0;
}
static struct commit *parse_insn_line(char *start, struct replay_opts *opts)
{
unsigned char commit_sha1[20];
char sha1_abbrev[40];
enum replay_action action;
int insn_len = 0;
char *p, *q;
if (!prefixcmp(start, "pick ")) {
action = CHERRY_PICK;
insn_len = strlen("pick");
p = start + insn_len + 1;
} else if (!prefixcmp(start, "revert ")) {
action = REVERT;
insn_len = strlen("revert");
p = start + insn_len + 1;
} else
return NULL;
q = strchr(p, ' ');
if (!q)
return NULL;
q++;
strlcpy(sha1_abbrev, p, q - p);
/*
* Verify that the action matches up with the one in
* opts; we don't support arbitrary instructions
*/
if (action != opts->action) {
const char *action_str;
action_str = action == REVERT ? "revert" : "cherry-pick";
error(_("Cannot %s during a %s"), action_str, action_name(opts));
return NULL;
}
if (get_sha1(sha1_abbrev, commit_sha1) < 0)
return NULL;
return lookup_commit_reference(commit_sha1);
}
static int parse_insn_buffer(char *buf, struct commit_list **todo_list,
struct replay_opts *opts)
{
struct commit_list **next = todo_list;
struct commit *commit;
char *p = buf;
int i;
for (i = 1; *p; i++) {
commit = parse_insn_line(p, opts);
if (!commit)
return error(_("Could not parse line %d."), i);
next = commit_list_append(commit, next);
p = strchrnul(p, '\n');
if (*p)
p++;
}
if (!*todo_list)
return error(_("No commits parsed."));
return 0;
}
static void read_populate_todo(struct commit_list **todo_list,
struct replay_opts *opts)
{
const char *todo_file = git_path(SEQ_TODO_FILE);
struct strbuf buf = STRBUF_INIT;
int fd, res;
fd = open(todo_file, O_RDONLY);
if (fd < 0)
die_errno(_("Could not open %s."), todo_file);
if (strbuf_read(&buf, fd, 0) < 0) {
close(fd);
strbuf_release(&buf);
die(_("Could not read %s."), todo_file);
}
close(fd);
res = parse_insn_buffer(buf.buf, todo_list, opts);
strbuf_release(&buf);
if (res)
die(_("Unusable instruction sheet: %s"), todo_file);
}
static int populate_opts_cb(const char *key, const char *value, void *data)
{
struct replay_opts *opts = data;
int error_flag = 1;
if (!value)
error_flag = 0;
else if (!strcmp(key, "options.no-commit"))
opts->no_commit = git_config_bool_or_int(key, value, &error_flag);
else if (!strcmp(key, "options.edit"))
opts->edit = git_config_bool_or_int(key, value, &error_flag);
else if (!strcmp(key, "options.signoff"))
opts->signoff = git_config_bool_or_int(key, value, &error_flag);
else if (!strcmp(key, "options.record-origin"))
opts->record_origin = git_config_bool_or_int(key, value, &error_flag);
else if (!strcmp(key, "options.allow-ff"))
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"))
git_config_string(&opts->strategy, key, value);
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);
} else
return error(_("Invalid key: %s"), key);
if (!error_flag)
return error(_("Invalid value for %s: %s"), key, value);
return 0;
}
static void read_populate_opts(struct replay_opts **opts_ptr)
{
const char *opts_file = git_path(SEQ_OPTS_FILE);
if (!file_exists(opts_file))
return;
if (git_config_from_file(populate_opts_cb, opts_file, *opts_ptr) < 0)
die(_("Malformed options sheet: %s"), opts_file);
}
static void walk_revs_populate_todo(struct commit_list **todo_list,
struct replay_opts *opts)
{
struct rev_info revs;
struct commit *commit;
struct commit_list **next;
git_config(git_default_config, NULL);
me = action == REVERT ? "revert" : "cherry-pick";
setenv(GIT_REFLOG_ACTION, me, 0);
parse_args(argc, argv);
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"));
prepare_revs(&revs, opts);
next = todo_list;
while ((commit = get_revision(&revs)))
next = commit_list_append(commit, next);
}
static int create_seq_dir(void)
{
const char *seq_dir = git_path(SEQ_DIR);
if (file_exists(seq_dir))
return error(_("%s already exists."), seq_dir);
else if (mkdir(seq_dir, 0777) < 0)
die_errno(_("Could not create sequencer directory '%s'."), seq_dir);
return 0;
}
static void save_head(const char *head)
{
const char *head_file = git_path(SEQ_HEAD_FILE);
static struct lock_file head_lock;
struct strbuf buf = STRBUF_INIT;
int fd;
fd = hold_lock_file_for_update(&head_lock, head_file, LOCK_DIE_ON_ERROR);
strbuf_addf(&buf, "%s\n", head);
if (write_in_full(fd, buf.buf, buf.len) < 0)
die_errno(_("Could not write to %s."), head_file);
if (commit_lock_file(&head_lock) < 0)
die(_("Error wrapping up %s."), head_file);
}
static void save_todo(struct commit_list *todo_list, struct replay_opts *opts)
{
const char *todo_file = git_path(SEQ_TODO_FILE);
static struct lock_file todo_lock;
struct strbuf buf = STRBUF_INIT;
int fd;
fd = hold_lock_file_for_update(&todo_lock, todo_file, LOCK_DIE_ON_ERROR);
if (format_todo(&buf, todo_list, opts) < 0)
die(_("Could not format %s."), todo_file);
if (write_in_full(fd, buf.buf, buf.len) < 0) {
strbuf_release(&buf);
die_errno(_("Could not write to %s."), todo_file);
}
if (commit_lock_file(&todo_lock) < 0) {
strbuf_release(&buf);
die(_("Error wrapping up %s."), todo_file);
}
strbuf_release(&buf);
}
read_and_refresh_cache(me);
static void save_opts(struct replay_opts *opts)
{
const char *opts_file = git_path(SEQ_OPTS_FILE);
if (opts->no_commit)
git_config_set_in_file(opts_file, "options.no-commit", "true");
if (opts->edit)
git_config_set_in_file(opts_file, "options.edit", "true");
if (opts->signoff)
git_config_set_in_file(opts_file, "options.signoff", "true");
if (opts->record_origin)
git_config_set_in_file(opts_file, "options.record-origin", "true");
if (opts->allow_ff)
git_config_set_in_file(opts_file, "options.allow-ff", "true");
if (opts->mainline) {
struct strbuf buf = STRBUF_INIT;
strbuf_addf(&buf, "%d", opts->mainline);
git_config_set_in_file(opts_file, "options.mainline", buf.buf);
strbuf_release(&buf);
}
if (opts->strategy)
git_config_set_in_file(opts_file, "options.strategy", opts->strategy);
if (opts->xopts) {
int i;
for (i = 0; i < opts->xopts_nr; i++)
git_config_set_multivar_in_file(opts_file,
"options.strategy-option",
opts->xopts[i], "^$", 0);
}
}
prepare_revs(&revs);
static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts)
{
struct commit_list *cur;
int res;
while ((commit = get_revision(&revs))) {
int res = do_pick_commit();
if (res)
setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
if (opts->allow_ff)
assert(!(opts->signoff || opts->no_commit ||
opts->record_origin || opts->edit));
read_and_refresh_cache(opts);
for (cur = todo_list; cur; cur = cur->next) {
save_todo(cur, opts);
res = do_pick_commit(cur->item, opts);
if (res) {
if (!cur->next)
/*
* An error was encountered while
* picking the last commit; the
* sequencer state is useless now --
* the user simply needs to resolve
* the conflict and commit
*/
remove_sequencer_state(0);
return res;
}
}
/*
* Sequence of picks finished successfully; cleanup by
* removing the .git/sequencer directory
*/
remove_sequencer_state(1);
return 0;
}
static int pick_revisions(struct replay_opts *opts)
{
struct commit_list *todo_list = NULL;
unsigned char sha1[20];
read_and_refresh_cache(opts);
/*
* 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_RESET) {
remove_sequencer_state(1);
return 0;
} else if (opts->subcommand == REPLAY_CONTINUE) {
if (!file_exists(git_path(SEQ_TODO_FILE)))
goto error;
read_populate_opts(&opts);
read_populate_todo(&todo_list, opts);
/* Verify that the conflict has been resolved */
if (!index_differs_from("HEAD", 0))
todo_list = todo_list->next;
} else {
/*
* Start a new cherry-pick/ revert sequence; but
* first, make sure that an existing one isn't in
* progress
*/
walk_revs_populate_todo(&todo_list, opts);
if (create_seq_dir() < 0) {
error(_("A cherry-pick or revert is in progress."));
advise(_("Use --continue to continue the operation"));
advise(_("or --reset to forget about it"));
return -1;
}
if (get_sha1("HEAD", sha1)) {
if (opts->action == REVERT)
return error(_("Can't revert as initial commit"));
return error(_("Can't cherry-pick into empty head"));
}
save_head(sha1_to_hex(sha1));
save_opts(opts);
}
return pick_commits(todo_list, opts);
error:
return error(_("No %s in progress"), action_name(opts));
}
int cmd_revert(int argc, const char **argv, const char *prefix)
{
struct replay_opts opts;
int res;
memset(&opts, 0, sizeof(opts));
if (isatty(0))
edit = 1;
action = REVERT;
return revert_or_cherry_pick(argc, argv);
opts.edit = 1;
opts.action = REVERT;
git_config(git_default_config, NULL);
parse_args(argc, argv, &opts);
res = pick_revisions(&opts);
if (res < 0)
die(_("revert failed"));
return res;
}
int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
{
action = CHERRY_PICK;
return revert_or_cherry_pick(argc, argv);
struct replay_opts opts;
int res;
memset(&opts, 0, sizeof(opts));
opts.action = CHERRY_PICK;
git_config(git_default_config, NULL);
parse_args(argc, argv, &opts);
res = pick_revisions(&opts);
if (res < 0)
die(_("cherry-pick failed"));
return res;
}