зеркало из https://github.com/microsoft/git.git
Merge branch 'rr/revert-cherry-pick-continue'
* rr/revert-cherry-pick-continue: builtin/revert.c: make commit_list_append() static revert: Propagate errors upwards from do_pick_commit revert: Introduce --continue to continue the operation revert: Don't implicitly stomp pending sequencer operation revert: Remove sequencer state when no commits are pending reset: Make reset remove the sequencer state revert: Introduce --reset to remove sequencer state revert: Make pick_commits functionally act on a commit list revert: Save command-line options for continuing operation revert: Save data for continuing after conflict resolution revert: Don't create invalid replay_opts in parse_args revert: Separate cmdline parsing from functional code revert: Introduce struct to keep command-line options revert: Eliminate global "commit" variable revert: Rename no_replay to record_origin revert: Don't check lone argument in get_encoding revert: Simplify and inline add_message_to_msg config: Introduce functions to write non-standard file advice: Introduce error_resolve_conflict
This commit is contained in:
Коммит
cd4093b603
|
@ -9,6 +9,8 @@ SYNOPSIS
|
|||
--------
|
||||
[verse]
|
||||
'git cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] [--ff] <commit>...
|
||||
'git cherry-pick' --reset
|
||||
'git cherry-pick' --continue
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
|
@ -110,6 +112,10 @@ effect to your index in a row.
|
|||
Pass the merge strategy-specific option through to the
|
||||
merge strategy. See linkgit:git-merge[1] for details.
|
||||
|
||||
SEQUENCER SUBCOMMANDS
|
||||
---------------------
|
||||
include::sequencer.txt[]
|
||||
|
||||
EXAMPLES
|
||||
--------
|
||||
`git cherry-pick master`::
|
||||
|
|
|
@ -9,6 +9,8 @@ SYNOPSIS
|
|||
--------
|
||||
[verse]
|
||||
'git revert' [--edit | --no-edit] [-n] [-m parent-number] [-s] <commit>...
|
||||
'git revert' --reset
|
||||
'git revert' --continue
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
|
@ -91,6 +93,10 @@ effect to your index in a row.
|
|||
Pass the merge strategy-specific option through to the
|
||||
merge strategy. See linkgit:git-merge[1] for details.
|
||||
|
||||
SEQUENCER SUBCOMMANDS
|
||||
---------------------
|
||||
include::sequencer.txt[]
|
||||
|
||||
EXAMPLES
|
||||
--------
|
||||
`git revert HEAD~3`::
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
--reset::
|
||||
Forget about the current operation in progress. Can be used
|
||||
to clear the sequencer state after a failed cherry-pick or
|
||||
revert.
|
||||
|
||||
--continue::
|
||||
Continue the operation in progress using the information in
|
||||
'.git/sequencer'. Can be used to continue after resolving
|
||||
conflicts in a failed cherry-pick or revert.
|
2
Makefile
2
Makefile
|
@ -557,6 +557,7 @@ LIB_H += rerere.h
|
|||
LIB_H += resolve-undo.h
|
||||
LIB_H += revision.h
|
||||
LIB_H += run-command.h
|
||||
LIB_H += sequencer.h
|
||||
LIB_H += sha1-array.h
|
||||
LIB_H += sha1-lookup.h
|
||||
LIB_H += sideband.h
|
||||
|
@ -664,6 +665,7 @@ LIB_OBJS += revision.o
|
|||
LIB_OBJS += run-command.o
|
||||
LIB_OBJS += server-info.o
|
||||
LIB_OBJS += setup.o
|
||||
LIB_OBJS += sequencer.o
|
||||
LIB_OBJS += sha1-array.o
|
||||
LIB_OBJS += sha1-lookup.o
|
||||
LIB_OBJS += sha1_file.o
|
||||
|
|
31
advice.c
31
advice.c
|
@ -19,6 +19,15 @@ static struct {
|
|||
{ "detachedhead", &advice_detached_head },
|
||||
};
|
||||
|
||||
void advise(const char *advice, ...)
|
||||
{
|
||||
va_list params;
|
||||
|
||||
va_start(params, advice);
|
||||
vreportf("hint: ", advice, params);
|
||||
va_end(params);
|
||||
}
|
||||
|
||||
int git_default_advice_config(const char *var, const char *value)
|
||||
{
|
||||
const char *k = skip_prefix(var, "advice.");
|
||||
|
@ -34,16 +43,24 @@ int git_default_advice_config(const char *var, const char *value)
|
|||
return 0;
|
||||
}
|
||||
|
||||
void NORETURN die_resolve_conflict(const char *me)
|
||||
int error_resolve_conflict(const char *me)
|
||||
{
|
||||
if (advice_resolve_conflict)
|
||||
error("'%s' is not possible because you have unmerged files.", me);
|
||||
if (advice_resolve_conflict) {
|
||||
/*
|
||||
* Message used both when 'git commit' fails and when
|
||||
* other commands doing a merge do.
|
||||
*/
|
||||
die("'%s' is not possible because you have unmerged files.\n"
|
||||
"Please, fix them up in the work tree, and then use 'git add/rm <file>' as\n"
|
||||
"appropriate to mark resolution and make a commit, or use 'git commit -a'.", me);
|
||||
else
|
||||
die("'%s' is not possible because you have unmerged files.", me);
|
||||
advise("Fix them up in the work tree,");
|
||||
advise("and then use 'git add/rm <file>' as");
|
||||
advise("appropriate to mark resolution and make a commit,");
|
||||
advise("or use 'git commit -a'.");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void NORETURN die_resolve_conflict(const char *me)
|
||||
{
|
||||
error_resolve_conflict(me);
|
||||
die("Exiting because of an unresolved conflict.");
|
||||
}
|
||||
|
|
3
advice.h
3
advice.h
|
@ -11,7 +11,8 @@ extern int advice_implicit_identity;
|
|||
extern int advice_detached_head;
|
||||
|
||||
int git_default_advice_config(const char *var, const char *value);
|
||||
|
||||
void advise(const char *advice, ...);
|
||||
int error_resolve_conflict(const char *me);
|
||||
extern void NORETURN die_resolve_conflict(const char *me);
|
||||
|
||||
#endif /* ADVICE_H */
|
||||
|
|
2
branch.c
2
branch.c
|
@ -3,6 +3,7 @@
|
|||
#include "refs.h"
|
||||
#include "remote.h"
|
||||
#include "commit.h"
|
||||
#include "sequencer.h"
|
||||
|
||||
struct tracking {
|
||||
struct refspec spec;
|
||||
|
@ -245,4 +246,5 @@ void remove_branch_state(void)
|
|||
unlink(git_path("MERGE_MSG"));
|
||||
unlink(git_path("MERGE_MODE"));
|
||||
unlink(git_path("SQUASH_MSG"));
|
||||
remove_sequencer_state(0);
|
||||
}
|
||||
|
|
735
builtin/revert.c
735
builtin/revert.c
|
@ -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 @@
|
|||
|
||||
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;
|
||||
enum replay_action { REVERT, CHERRY_PICK };
|
||||
enum replay_subcommand { REPLAY_NONE, REPLAY_RESET, REPLAY_CONTINUE };
|
||||
|
||||
static const char *me;
|
||||
struct replay_opts {
|
||||
enum replay_action action;
|
||||
enum replay_subcommand subcommand;
|
||||
|
||||
/* Merge strategy. */
|
||||
static const char *strategy;
|
||||
static const char **xopts;
|
||||
static size_t xopts_nr, xopts_alloc;
|
||||
/* 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 * const * usage_str = revert_or_cherry_pick_usage();
|
||||
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(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 {
|
|||
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)
|
|||
{
|
||||
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)
|
|||
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)
|
|||
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)
|
|||
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)
|
|||
|
||||
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,
|
|||
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,
|
|||
(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,
|
|||
* 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)
|
|||
|
||||
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)
|
|||
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)
|
|||
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)
|
|||
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)
|
|||
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)
|
|||
|
||||
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)
|
|||
}
|
||||
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)
|
|||
|
||||
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)
|
|||
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)
|
|||
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 rev_info revs;
|
||||
struct commit_list *new = xmalloc(sizeof(struct commit_list));
|
||||
new->item = commit;
|
||||
*next = new;
|
||||
new->next = NULL;
|
||||
return &new->next;
|
||||
}
|
||||
|
||||
git_config(git_default_config, NULL);
|
||||
me = action == REVERT ? "revert" : "cherry-pick";
|
||||
setenv(GIT_REFLOG_ACTION, me, 0);
|
||||
parse_args(argc, argv);
|
||||
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";
|
||||
|
||||
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"));
|
||||
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;
|
||||
}
|
||||
|
||||
read_and_refresh_cache(me);
|
||||
if (get_sha1(sha1_abbrev, commit_sha1) < 0)
|
||||
return NULL;
|
||||
|
||||
prepare_revs(&revs);
|
||||
return lookup_commit_reference(commit_sha1);
|
||||
}
|
||||
|
||||
while ((commit = get_revision(&revs))) {
|
||||
int res = do_pick_commit();
|
||||
if (res)
|
||||
return res;
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts)
|
||||
{
|
||||
struct commit_list *cur;
|
||||
int 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;
|
||||
}
|
||||
|
|
2
cache.h
2
cache.h
|
@ -1076,9 +1076,11 @@ extern int git_config_bool(const char *, const char *);
|
|||
extern int git_config_maybe_bool(const char *, const char *);
|
||||
extern int git_config_string(const char **, const char *, const char *);
|
||||
extern int git_config_pathname(const char **, const char *, const char *);
|
||||
extern int git_config_set_in_file(const char *, const char *, const char *);
|
||||
extern int git_config_set(const char *, const char *);
|
||||
extern int git_config_parse_key(const char *, char **, int *);
|
||||
extern int git_config_set_multivar(const char *, const char *, const char *, int);
|
||||
extern int git_config_set_multivar_in_file(const char *, const char *, const char *, const char *, int);
|
||||
extern int git_config_rename_section(const char *, const char *);
|
||||
extern const char *git_etc_gitconfig(void);
|
||||
extern int check_repository_format_version(const char *var, const char *value, void *cb);
|
||||
|
|
36
config.c
36
config.c
|
@ -1095,6 +1095,12 @@ contline:
|
|||
return offset;
|
||||
}
|
||||
|
||||
int git_config_set_in_file(const char *config_filename,
|
||||
const char *key, const char *value)
|
||||
{
|
||||
return git_config_set_multivar_in_file(config_filename, key, value, NULL, 0);
|
||||
}
|
||||
|
||||
int git_config_set(const char *key, const char *value)
|
||||
{
|
||||
return git_config_set_multivar(key, value, NULL, 0);
|
||||
|
@ -1192,19 +1198,14 @@ out_free_ret_1:
|
|||
* - the config file is removed and the lock file rename()d to it.
|
||||
*
|
||||
*/
|
||||
int git_config_set_multivar(const char *key, const char *value,
|
||||
const char *value_regex, int multi_replace)
|
||||
int git_config_set_multivar_in_file(const char *config_filename,
|
||||
const char *key, const char *value,
|
||||
const char *value_regex, int multi_replace)
|
||||
{
|
||||
int fd = -1, in_fd;
|
||||
int ret;
|
||||
char *config_filename;
|
||||
struct lock_file *lock = NULL;
|
||||
|
||||
if (config_exclusive_filename)
|
||||
config_filename = xstrdup(config_exclusive_filename);
|
||||
else
|
||||
config_filename = git_pathdup("config");
|
||||
|
||||
/* parse-key returns negative; flip the sign to feed exit(3) */
|
||||
ret = 0 - git_config_parse_key(key, &store.key, &store.baselen);
|
||||
if (ret)
|
||||
|
@ -1381,7 +1382,6 @@ int git_config_set_multivar(const char *key, const char *value,
|
|||
out_free:
|
||||
if (lock)
|
||||
rollback_lock_file(lock);
|
||||
free(config_filename);
|
||||
return ret;
|
||||
|
||||
write_err_out:
|
||||
|
@ -1390,6 +1390,24 @@ write_err_out:
|
|||
|
||||
}
|
||||
|
||||
int git_config_set_multivar(const char *key, const char *value,
|
||||
const char *value_regex, int multi_replace)
|
||||
{
|
||||
const char *config_filename;
|
||||
char *buf = NULL;
|
||||
int ret;
|
||||
|
||||
if (config_exclusive_filename)
|
||||
config_filename = config_exclusive_filename;
|
||||
else
|
||||
config_filename = buf = git_pathdup("config");
|
||||
|
||||
ret = git_config_set_multivar_in_file(config_filename, key, value,
|
||||
value_regex, multi_replace);
|
||||
free(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int section_name_match (const char *buf, const char *name)
|
||||
{
|
||||
int i = 0, j = 0, dot = 0;
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
#include "cache.h"
|
||||
#include "sequencer.h"
|
||||
#include "strbuf.h"
|
||||
#include "dir.h"
|
||||
|
||||
void remove_sequencer_state(int aggressive)
|
||||
{
|
||||
struct strbuf seq_dir = STRBUF_INIT;
|
||||
struct strbuf seq_old_dir = STRBUF_INIT;
|
||||
|
||||
strbuf_addf(&seq_dir, "%s", git_path(SEQ_DIR));
|
||||
strbuf_addf(&seq_old_dir, "%s", git_path(SEQ_OLD_DIR));
|
||||
remove_dir_recursively(&seq_old_dir, 0);
|
||||
rename(git_path(SEQ_DIR), git_path(SEQ_OLD_DIR));
|
||||
if (aggressive)
|
||||
remove_dir_recursively(&seq_old_dir, 0);
|
||||
strbuf_release(&seq_dir);
|
||||
strbuf_release(&seq_old_dir);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
#ifndef SEQUENCER_H
|
||||
#define SEQUENCER_H
|
||||
|
||||
#define SEQ_DIR "sequencer"
|
||||
#define SEQ_OLD_DIR "sequencer-old"
|
||||
#define SEQ_HEAD_FILE "sequencer/head"
|
||||
#define SEQ_TODO_FILE "sequencer/todo"
|
||||
#define SEQ_OPTS_FILE "sequencer/opts"
|
||||
|
||||
/*
|
||||
* Removes SEQ_OLD_DIR and renames SEQ_DIR to SEQ_OLD_DIR, ignoring
|
||||
* any errors. Intended to be used by 'git reset'.
|
||||
*
|
||||
* With the aggressive flag, it additionally removes SEQ_OLD_DIR,
|
||||
* ignoring any errors. Inteded to be used by the sequencer's
|
||||
* '--reset' subcommand.
|
||||
*/
|
||||
void remove_sequencer_state(int aggressive);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,214 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='Test cherry-pick continuation features
|
||||
|
||||
+ anotherpick: rewrites foo to d
|
||||
+ picked: rewrites foo to c
|
||||
+ unrelatedpick: rewrites unrelated to reallyunrelated
|
||||
+ base: rewrites foo to b
|
||||
+ initial: writes foo as a, unrelated as unrelated
|
||||
|
||||
'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
pristine_detach () {
|
||||
git cherry-pick --reset &&
|
||||
git checkout -f "$1^0" &&
|
||||
git read-tree -u --reset HEAD &&
|
||||
git clean -d -f -f -q -x
|
||||
}
|
||||
|
||||
test_expect_success setup '
|
||||
echo unrelated >unrelated &&
|
||||
git add unrelated &&
|
||||
test_commit initial foo a &&
|
||||
test_commit base foo b &&
|
||||
test_commit unrelatedpick unrelated reallyunrelated &&
|
||||
test_commit picked foo c &&
|
||||
test_commit anotherpick foo d &&
|
||||
git config advice.detachedhead false
|
||||
|
||||
'
|
||||
|
||||
test_expect_success 'cherry-pick persists data on failure' '
|
||||
pristine_detach initial &&
|
||||
test_must_fail git cherry-pick -s base..anotherpick &&
|
||||
test_path_is_dir .git/sequencer &&
|
||||
test_path_is_file .git/sequencer/head &&
|
||||
test_path_is_file .git/sequencer/todo &&
|
||||
test_path_is_file .git/sequencer/opts
|
||||
'
|
||||
|
||||
test_expect_success 'cherry-pick persists opts correctly' '
|
||||
pristine_detach initial &&
|
||||
test_must_fail git cherry-pick -s -m 1 --strategy=recursive -X patience -X ours base..anotherpick &&
|
||||
test_path_is_dir .git/sequencer &&
|
||||
test_path_is_file .git/sequencer/head &&
|
||||
test_path_is_file .git/sequencer/todo &&
|
||||
test_path_is_file .git/sequencer/opts &&
|
||||
echo "true" >expect &&
|
||||
git config --file=.git/sequencer/opts --get-all options.signoff >actual &&
|
||||
test_cmp expect actual &&
|
||||
echo "1" >expect &&
|
||||
git config --file=.git/sequencer/opts --get-all options.mainline >actual &&
|
||||
test_cmp expect actual &&
|
||||
echo "recursive" >expect &&
|
||||
git config --file=.git/sequencer/opts --get-all options.strategy >actual &&
|
||||
test_cmp expect actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
patience
|
||||
ours
|
||||
EOF
|
||||
git config --file=.git/sequencer/opts --get-all options.strategy-option >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'cherry-pick cleans up sequencer state upon success' '
|
||||
pristine_detach initial &&
|
||||
git cherry-pick initial..picked &&
|
||||
test_path_is_missing .git/sequencer
|
||||
'
|
||||
|
||||
test_expect_success '--reset does not complain when no cherry-pick is in progress' '
|
||||
pristine_detach initial &&
|
||||
git cherry-pick --reset
|
||||
'
|
||||
|
||||
test_expect_success '--reset cleans up sequencer state' '
|
||||
pristine_detach initial &&
|
||||
test_must_fail git cherry-pick base..picked &&
|
||||
git cherry-pick --reset &&
|
||||
test_path_is_missing .git/sequencer
|
||||
'
|
||||
|
||||
test_expect_success 'cherry-pick cleans up sequencer state when one commit is left' '
|
||||
pristine_detach initial &&
|
||||
test_must_fail git cherry-pick base..picked &&
|
||||
test_path_is_missing .git/sequencer &&
|
||||
echo "resolved" >foo &&
|
||||
git add foo &&
|
||||
git commit &&
|
||||
{
|
||||
git rev-list HEAD |
|
||||
git diff-tree --root --stdin |
|
||||
sed "s/$_x40/OBJID/g"
|
||||
} >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
OBJID
|
||||
:100644 100644 OBJID OBJID M foo
|
||||
OBJID
|
||||
:100644 100644 OBJID OBJID M unrelated
|
||||
OBJID
|
||||
:000000 100644 OBJID OBJID A foo
|
||||
:000000 100644 OBJID OBJID A unrelated
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'cherry-pick does not implicitly stomp an existing operation' '
|
||||
pristine_detach initial &&
|
||||
test_must_fail git cherry-pick base..anotherpick &&
|
||||
test-chmtime -v +0 .git/sequencer >expect &&
|
||||
test_must_fail git cherry-pick unrelatedpick &&
|
||||
test-chmtime -v +0 .git/sequencer >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success '--continue complains when no cherry-pick is in progress' '
|
||||
pristine_detach initial &&
|
||||
test_must_fail git cherry-pick --continue
|
||||
'
|
||||
|
||||
test_expect_success '--continue complains when there are unresolved conflicts' '
|
||||
pristine_detach initial &&
|
||||
test_must_fail git cherry-pick base..anotherpick &&
|
||||
test_must_fail git cherry-pick --continue
|
||||
'
|
||||
|
||||
test_expect_success '--continue continues after conflicts are resolved' '
|
||||
pristine_detach initial &&
|
||||
test_must_fail git cherry-pick base..anotherpick &&
|
||||
echo "c" >foo &&
|
||||
git add foo &&
|
||||
git commit &&
|
||||
git cherry-pick --continue &&
|
||||
test_path_is_missing .git/sequencer &&
|
||||
{
|
||||
git rev-list HEAD |
|
||||
git diff-tree --root --stdin |
|
||||
sed "s/$_x40/OBJID/g"
|
||||
} >actual &&
|
||||
cat >expect <<-\EOF &&
|
||||
OBJID
|
||||
:100644 100644 OBJID OBJID M foo
|
||||
OBJID
|
||||
:100644 100644 OBJID OBJID M foo
|
||||
OBJID
|
||||
:100644 100644 OBJID OBJID M unrelated
|
||||
OBJID
|
||||
:000000 100644 OBJID OBJID A foo
|
||||
:000000 100644 OBJID OBJID A unrelated
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success '--continue respects opts' '
|
||||
pristine_detach initial &&
|
||||
test_must_fail git cherry-pick -x base..anotherpick &&
|
||||
echo "c" >foo &&
|
||||
git add foo &&
|
||||
git commit &&
|
||||
git cherry-pick --continue &&
|
||||
test_path_is_missing .git/sequencer &&
|
||||
git cat-file commit HEAD >anotherpick_msg &&
|
||||
git cat-file commit HEAD~1 >picked_msg &&
|
||||
git cat-file commit HEAD~2 >unrelatedpick_msg &&
|
||||
git cat-file commit HEAD~3 >initial_msg &&
|
||||
test_must_fail grep "cherry picked from" initial_msg &&
|
||||
grep "cherry picked from" unrelatedpick_msg &&
|
||||
grep "cherry picked from" picked_msg &&
|
||||
grep "cherry picked from" anotherpick_msg
|
||||
'
|
||||
|
||||
test_expect_success '--signoff is not automatically propagated to resolved conflict' '
|
||||
pristine_detach initial &&
|
||||
test_must_fail git cherry-pick --signoff base..anotherpick &&
|
||||
echo "c" >foo &&
|
||||
git add foo &&
|
||||
git commit &&
|
||||
git cherry-pick --continue &&
|
||||
test_path_is_missing .git/sequencer &&
|
||||
git cat-file commit HEAD >anotherpick_msg &&
|
||||
git cat-file commit HEAD~1 >picked_msg &&
|
||||
git cat-file commit HEAD~2 >unrelatedpick_msg &&
|
||||
git cat-file commit HEAD~3 >initial_msg &&
|
||||
test_must_fail grep "Signed-off-by:" initial_msg &&
|
||||
grep "Signed-off-by:" unrelatedpick_msg &&
|
||||
test_must_fail grep "Signed-off-by:" picked_msg &&
|
||||
grep "Signed-off-by:" anotherpick_msg
|
||||
'
|
||||
|
||||
test_expect_success 'malformed instruction sheet 1' '
|
||||
pristine_detach initial &&
|
||||
test_must_fail git cherry-pick base..anotherpick &&
|
||||
echo "resolved" >foo &&
|
||||
git add foo &&
|
||||
git commit &&
|
||||
sed "s/pick /pick/" .git/sequencer/todo >new_sheet &&
|
||||
cp new_sheet .git/sequencer/todo &&
|
||||
test_must_fail git cherry-pick --continue
|
||||
'
|
||||
|
||||
test_expect_success 'malformed instruction sheet 2' '
|
||||
pristine_detach initial &&
|
||||
test_must_fail git cherry-pick base..anotherpick &&
|
||||
echo "resolved" >foo &&
|
||||
git add foo &&
|
||||
git commit &&
|
||||
sed "s/pick/revert/" .git/sequencer/todo >new_sheet &&
|
||||
cp new_sheet .git/sequencer/todo &&
|
||||
test_must_fail git cherry-pick --continue
|
||||
'
|
||||
|
||||
test_done
|
|
@ -0,0 +1,44 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='Test interaction of reset --hard with sequencer
|
||||
|
||||
+ anotherpick: rewrites foo to d
|
||||
+ picked: rewrites foo to c
|
||||
+ unrelatedpick: rewrites unrelated to reallyunrelated
|
||||
+ base: rewrites foo to b
|
||||
+ initial: writes foo as a, unrelated as unrelated
|
||||
'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
pristine_detach () {
|
||||
git cherry-pick --reset &&
|
||||
git checkout -f "$1^0" &&
|
||||
git read-tree -u --reset HEAD &&
|
||||
git clean -d -f -f -q -x
|
||||
}
|
||||
|
||||
test_expect_success setup '
|
||||
echo unrelated >unrelated &&
|
||||
git add unrelated &&
|
||||
test_commit initial foo a &&
|
||||
test_commit base foo b &&
|
||||
test_commit unrelatedpick unrelated reallyunrelated &&
|
||||
test_commit picked foo c &&
|
||||
test_commit anotherpick foo d &&
|
||||
git config advice.detachedhead false
|
||||
|
||||
'
|
||||
|
||||
test_expect_success 'reset --hard cleans up sequencer state, providing one-level undo' '
|
||||
pristine_detach initial &&
|
||||
test_must_fail git cherry-pick base..anotherpick &&
|
||||
test_path_is_dir .git/sequencer &&
|
||||
git reset --hard &&
|
||||
test_path_is_missing .git/sequencer &&
|
||||
test_path_is_dir .git/sequencer-old &&
|
||||
git reset --hard &&
|
||||
test_path_is_missing .git/sequencer-old
|
||||
'
|
||||
|
||||
test_done
|
Загрузка…
Ссылка в новой задаче