* [PATCH 0/6] The move to sequencer.c @ 2012-01-08 12:27 Ramkumar Ramachandra 2012-01-08 12:27 ` [PATCH 1/6] revert: move replay_action, replay_subcommand to header Ramkumar Ramachandra ` (7 more replies) 0 siblings, 8 replies; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-08 12:27 UTC (permalink / raw) To: Git List; +Cc: Junio C Hamano, Jonathan Nieder Hi, I've tried a slightly different approach: the objective of the patches seem to be much clearer this time. [1/6] revert: move replay_action, replay_subcommand to header [2/6] revert: decouple sequencer actions from builtin commands [3/6] revert: don't let revert continue a cherry-pick [4/6] revert: allow mixing "pick" and "revert" actions [5/6] revert: report fine-grained error messages from insn parser [6/6] sequencer: factor code out of revert builtin [1/6] first moves out a couple of data structures to the header [2/6] decouples "actions" from a "commands" completely. Although this sort of separation might not be necessary at this stage (because we just have a couple of actions that directly correspond to builtin commands), I think it makes [4/6] much easier to read. [3/6] mainly exists so that [4/6] doesn't allow 'git revert --continue' to continue a 'git cherry-pick' and viceversa. Note that a 'git revert --continue' can execute an instruction sheet with "pick" instructions and viceversa after [4/6]. [4/6] should be very clear this time: do_pick_commit() takes an extra argument "action", and checks that instead of the "opts->command" everywhere. The parser is also updated to parse into (commit, action) pairs. [5/6] is fairly straightforward. [6/6] makes the final move. This is something I've been pushing for quite some time: exciting things like 'git continue' will follow this. Cheers! builtin/revert.c | 959 +------------------------------------- sequencer.c | 987 ++++++++++++++++++++++++++++++++++++++- sequencer.h | 49 ++ t/t3510-cherry-pick-sequence.sh | 57 ++- 4 files changed, 1088 insertions(+), 964 deletions(-) -- 1.7.8.2 ^ permalink raw reply [flat|nested] 64+ messages in thread
* [PATCH 1/6] revert: move replay_action, replay_subcommand to header 2012-01-08 12:27 [PATCH 0/6] The move to sequencer.c Ramkumar Ramachandra @ 2012-01-08 12:27 ` Ramkumar Ramachandra 2012-01-08 19:31 ` Jonathan Nieder 2012-01-08 12:27 ` [PATCH 2/6] revert: decouple sequencer actions from builtin commands Ramkumar Ramachandra ` (6 subsequent siblings) 7 siblings, 1 reply; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-08 12:27 UTC (permalink / raw) To: Git List; +Cc: Junio C Hamano, Jonathan Nieder Our plan to build a sequencer involves leaving the revert builtin with just argument parsing work. Since the enums replay_action and replay_subcommand have nothing to do with this argument parsing, move them to sequencer.h in advance. "REVERT" and "CHERRY_PICK" are unsuitable variable names for exposing publicly, as their purpose is unclear in the global context: rename them to "REPLAY_REVERT" and "REPLAY_PICK" respectively. Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com> --- builtin/revert.c | 42 +++++++++++++++++------------------------- sequencer.h | 12 ++++++++++++ 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/builtin/revert.c b/builtin/revert.c index 0d8020c..c804045 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -39,14 +39,6 @@ static const char * const cherry_pick_usage[] = { NULL }; -enum replay_action { REVERT, CHERRY_PICK }; -enum replay_subcommand { - REPLAY_NONE, - REPLAY_REMOVE_STATE, - REPLAY_CONTINUE, - REPLAY_ROLLBACK -}; - struct replay_opts { enum replay_action action; enum replay_subcommand subcommand; @@ -74,14 +66,14 @@ struct replay_opts { static const char *action_name(const struct replay_opts *opts) { - return opts->action == REVERT ? "revert" : "cherry-pick"; + return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick"; } static char *get_encoding(const char *message); static const char * const *revert_or_cherry_pick_usage(struct replay_opts *opts) { - return opts->action == REVERT ? revert_usage : cherry_pick_usage; + return opts->action == REPLAY_REVERT ? revert_usage : cherry_pick_usage; } static int option_parse_x(const struct option *opt, @@ -160,7 +152,7 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) OPT_END(), }; - if (opts->action == CHERRY_PICK) { + if (opts->action == REPLAY_PICK) { struct option cp_extra[] = { OPT_BOOLEAN('x', NULL, &opts->record_origin, "append commit name"), OPT_BOOLEAN(0, "ff", &opts->allow_ff, "allow fast-forward"), @@ -374,7 +366,7 @@ static int error_dirty_index(struct replay_opts *opts) return error_resolve_conflict(action_name(opts)); /* Different translation strings for cherry-pick and revert */ - if (opts->action == CHERRY_PICK) + if (opts->action == REPLAY_PICK) error(_("Your local changes would be overwritten by cherry-pick.")); else error(_("Your local changes would be overwritten by revert.")); @@ -553,7 +545,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) defmsg = git_pathdup("MERGE_MSG"); - if (opts->action == REVERT) { + if (opts->action == REPLAY_REVERT) { base = commit; base_label = msg.label; next = parent; @@ -594,7 +586,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) } } - if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REVERT) { + if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REPLAY_REVERT) { res = do_recursive_merge(base, next, base_label, next_label, head, &msgbuf, opts); write_message(&msgbuf, defmsg); @@ -618,13 +610,13 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) * However, if the merge did not even start, then we don't want to * write it at all. */ - if (opts->action == CHERRY_PICK && !opts->no_commit && (res == 0 || res == 1)) + if (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1)) write_cherry_pick_head(commit, "CHERRY_PICK_HEAD"); - if (opts->action == REVERT && ((opts->no_commit && res == 0) || res == 1)) + if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1)) write_cherry_pick_head(commit, "REVERT_HEAD"); if (res) { - error(opts->action == REVERT + error(opts->action == REPLAY_REVERT ? _("could not revert %s... %s") : _("could not apply %s... %s"), find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), @@ -644,7 +636,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) static void prepare_revs(struct replay_opts *opts) { - if (opts->action != REVERT) + if (opts->action != REPLAY_REVERT) opts->revs->reverse ^= 1; if (prepare_revision_walk(opts->revs)) @@ -701,7 +693,7 @@ static int format_todo(struct strbuf *buf, struct commit_list *todo_list, { struct commit_list *cur = NULL; const char *sha1_abbrev = NULL; - const char *action_str = opts->action == REVERT ? "revert" : "pick"; + const char *action_str = opts->action == REPLAY_REVERT ? "revert" : "pick"; const char *subject; int subject_len; @@ -722,10 +714,10 @@ static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts * int saved, status, padding; if (!prefixcmp(bol, "pick")) { - action = CHERRY_PICK; + action = REPLAY_PICK; bol += strlen("pick"); } else if (!prefixcmp(bol, "revert")) { - action = REVERT; + action = REPLAY_REVERT; bol += strlen("revert"); } else return NULL; @@ -748,7 +740,7 @@ static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts * */ if (action != opts->action) { const char *action_str; - action_str = action == REVERT ? "revert" : "cherry-pick"; + action_str = action == REPLAY_REVERT ? "revert" : "cherry-pick"; error(_("Cannot %s during a %s"), action_str, action_name(opts)); return NULL; } @@ -1124,7 +1116,7 @@ static int pick_revisions(struct replay_opts *opts) if (create_seq_dir() < 0) return -1; if (get_sha1("HEAD", sha1)) { - if (opts->action == REVERT) + if (opts->action == REPLAY_REVERT) return error(_("Can't revert as initial commit")); return error(_("Can't cherry-pick into empty head")); } @@ -1141,7 +1133,7 @@ int cmd_revert(int argc, const char **argv, const char *prefix) memset(&opts, 0, sizeof(opts)); if (isatty(0)) opts.edit = 1; - opts.action = REVERT; + opts.action = REPLAY_REVERT; git_config(git_default_config, NULL); parse_args(argc, argv, &opts); res = pick_revisions(&opts); @@ -1156,7 +1148,7 @@ int cmd_cherry_pick(int argc, const char **argv, const char *prefix) int res; memset(&opts, 0, sizeof(opts)); - opts.action = CHERRY_PICK; + opts.action = REPLAY_PICK; git_config(git_default_config, NULL); parse_args(argc, argv, &opts); res = pick_revisions(&opts); diff --git a/sequencer.h b/sequencer.h index 2d4528f..1d9fcec 100644 --- a/sequencer.h +++ b/sequencer.h @@ -6,6 +6,18 @@ #define SEQ_TODO_FILE "sequencer/todo" #define SEQ_OPTS_FILE "sequencer/opts" +enum replay_action { + REPLAY_REVERT, + REPLAY_PICK +}; + +enum replay_subcommand { + REPLAY_NONE, + REPLAY_REMOVE_STATE, + REPLAY_CONTINUE, + REPLAY_ROLLBACK +}; + /* Removes SEQ_DIR. */ extern void remove_sequencer_state(void); -- 1.7.8.2 ^ permalink raw reply related [flat|nested] 64+ messages in thread
* Re: [PATCH 1/6] revert: move replay_action, replay_subcommand to header 2012-01-08 12:27 ` [PATCH 1/6] revert: move replay_action, replay_subcommand to header Ramkumar Ramachandra @ 2012-01-08 19:31 ` Jonathan Nieder 0 siblings, 0 replies; 64+ messages in thread From: Jonathan Nieder @ 2012-01-08 19:31 UTC (permalink / raw) To: Ramkumar Ramachandra; +Cc: Git List, Junio C Hamano Ramkumar Ramachandra wrote: > Our plan to build a sequencer involves leaving the revert builtin with > just argument parsing work. Since the enums replay_action and > replay_subcommand have nothing to do with this argument parsing, move > them to sequencer.h in advance. > > "REVERT" and "CHERRY_PICK" are unsuitable variable names for exposing > publicly, as their purpose is unclear in the global context: rename > them to "REPLAY_REVERT" and "REPLAY_PICK" respectively. My first reaction: this probably would be more self-explanatory if squashed with the patch the moves the rest of the code to sequencer.[ch]. Second reaction: ah, but the s/REVERT/REPLAY_REVERT/ and s/CHERRY_PICK/REPLAY_PICK/ changes would be lost in the noise of the move. Maybe it would be possible to make those changes but keep the enum in builtin/revert.c, explaining that this is a preparatory step and they will be moving in a few moments? Hope that helps, Jonathan ^ permalink raw reply [flat|nested] 64+ messages in thread
* [PATCH 2/6] revert: decouple sequencer actions from builtin commands 2012-01-08 12:27 [PATCH 0/6] The move to sequencer.c Ramkumar Ramachandra 2012-01-08 12:27 ` [PATCH 1/6] revert: move replay_action, replay_subcommand to header Ramkumar Ramachandra @ 2012-01-08 12:27 ` Ramkumar Ramachandra 2012-01-08 19:34 ` Jonathan Nieder 2012-01-08 12:27 ` [PATCH 3/6] revert: don't let revert continue a cherry-pick Ramkumar Ramachandra ` (5 subsequent siblings) 7 siblings, 1 reply; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-08 12:27 UTC (permalink / raw) To: Git List; +Cc: Junio C Hamano, Jonathan Nieder Currently, 'git cherry-pick' fills up the '.git/sequencer/todo' instruction sheet with "pick" actions, while 'git revert' fills it up with "revert" actions. Inspired by the way 'rebase -i' works, we would like to permit mixing arbitrary actions in the same instruction sheet. To do this, we first have to decouple the notion of an action in the instruction sheet from builtin commands. So, while a future instruction sheet would look like: pick next~4 action3 b74fea revert rr/moo^2~34 the actions "pick", "action3" and "revert" need not necessarily correspond to the specific builtins. Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com> --- builtin/revert.c | 62 +++++++++++++++++++++++++++++------------------------ sequencer.h | 5 ++++ 2 files changed, 39 insertions(+), 28 deletions(-) diff --git a/builtin/revert.c b/builtin/revert.c index c804045..3ac6da0 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -40,7 +40,7 @@ static const char * const cherry_pick_usage[] = { }; struct replay_opts { - enum replay_action action; + enum replay_command command; enum replay_subcommand subcommand; /* Boolean options */ @@ -64,16 +64,21 @@ struct replay_opts { #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" -static const char *action_name(const struct replay_opts *opts) +static const char *command_name(struct replay_opts *opts) +{ + return opts->command == REPLAY_CMD_REVERT ? "revert" : "cherry-pick"; +} + +static const char *action_name(enum replay_action action) { - return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick"; + return action == REPLAY_REVERT ? "revert" : "pick"; } static char *get_encoding(const char *message); static const char * const *revert_or_cherry_pick_usage(struct replay_opts *opts) { - return opts->action == REPLAY_REVERT ? revert_usage : cherry_pick_usage; + return opts->command == REPLAY_CMD_REVERT ? revert_usage : cherry_pick_usage; } static int option_parse_x(const struct option *opt, @@ -130,7 +135,7 @@ static void verify_opt_mutually_compatible(const char *me, ...) 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); + const char *me = command_name(opts); int remove_state = 0; int contin = 0; int rollback = 0; @@ -152,7 +157,7 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) OPT_END(), }; - if (opts->action == REPLAY_PICK) { + if (opts->command == REPLAY_CMD_CHERRY_PICK) { struct option cp_extra[] = { OPT_BOOLEAN('x', NULL, &opts->record_origin, "append commit name"), OPT_BOOLEAN(0, "ff", &opts->allow_ff, "allow fast-forward"), @@ -363,10 +368,10 @@ static struct tree *empty_tree(void) static int error_dirty_index(struct replay_opts *opts) { if (read_cache_unmerged()) - return error_resolve_conflict(action_name(opts)); + return error_resolve_conflict(command_name(opts)); /* Different translation strings for cherry-pick and revert */ - if (opts->action == REPLAY_PICK) + if (opts->command == REPLAY_CMD_CHERRY_PICK) error(_("Your local changes would be overwritten by cherry-pick.")); else error(_("Your local changes would be overwritten by revert.")); @@ -422,7 +427,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"), action_name(opts)); + die(_("%s: Unable to write new index file"), command_name(opts)); rollback_lock_file(&index_lock); if (!clean) { @@ -530,7 +535,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) /* TRANSLATORS: The first %s will be "revert" or "cherry-pick", the second %s a SHA1 */ return error(_("%s: cannot parse parent commit %s"), - action_name(opts), sha1_to_hex(parent->object.sha1)); + command_name(opts), sha1_to_hex(parent->object.sha1)); if (get_message(commit, &msg) != 0) return error(_("Cannot get commit message for %s"), @@ -545,7 +550,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) defmsg = git_pathdup("MERGE_MSG"); - if (opts->action == REPLAY_REVERT) { + if (opts->command == REPLAY_CMD_REVERT) { base = commit; base_label = msg.label; next = parent; @@ -586,7 +591,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) } } - if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REPLAY_REVERT) { + if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->command == REPLAY_CMD_REVERT) { res = do_recursive_merge(base, next, base_label, next_label, head, &msgbuf, opts); write_message(&msgbuf, defmsg); @@ -610,13 +615,13 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) * However, if the merge did not even start, then we don't want to * write it at all. */ - if (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1)) + if (opts->command == REPLAY_CMD_CHERRY_PICK && !opts->no_commit && (res == 0 || res == 1)) write_cherry_pick_head(commit, "CHERRY_PICK_HEAD"); - if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1)) + if (opts->command == REPLAY_CMD_REVERT && ((opts->no_commit && res == 0) || res == 1)) write_cherry_pick_head(commit, "REVERT_HEAD"); if (res) { - error(opts->action == REPLAY_REVERT + error(opts->command == REPLAY_CMD_REVERT ? _("could not revert %s... %s") : _("could not apply %s... %s"), find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), @@ -636,7 +641,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) static void prepare_revs(struct replay_opts *opts) { - if (opts->action != REPLAY_REVERT) + if (opts->command != REPLAY_CMD_REVERT) opts->revs->reverse ^= 1; if (prepare_revision_walk(opts->revs)) @@ -651,12 +656,13 @@ 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"), action_name(opts)); + die(_("git %s: failed to read the index"), command_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"), action_name(opts)); + die(_("git %s: failed to refresh the index"), + command_name(opts)); } rollback_lock_file(&index_lock); } @@ -693,7 +699,7 @@ static int format_todo(struct strbuf *buf, struct commit_list *todo_list, { struct commit_list *cur = NULL; const char *sha1_abbrev = NULL; - const char *action_str = opts->action == REPLAY_REVERT ? "revert" : "pick"; + const char *action_str = opts->command == REPLAY_CMD_REVERT ? "revert" : "pick"; const char *subject; int subject_len; @@ -738,10 +744,10 @@ static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts * * 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 == REPLAY_REVERT ? "revert" : "cherry-pick"; - error(_("Cannot %s during a %s"), action_str, action_name(opts)); + if ((action == REPLAY_PICK && opts->command == REPLAY_CMD_REVERT) || + (action == REPLAY_REVERT && opts->command == REPLAY_CMD_CHERRY_PICK)) { + error(_("Cannot %s during a %s"), action_name(action), + command_name(opts)); return NULL; } @@ -1003,7 +1009,7 @@ 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); + setenv(GIT_REFLOG_ACTION, command_name(opts), 0); if (opts->allow_ff) assert(!(opts->signoff || opts->no_commit || opts->record_origin || opts->edit)); @@ -1058,7 +1064,7 @@ static int sequencer_continue(struct replay_opts *opts) static int single_pick(struct commit *cmit, struct replay_opts *opts) { - setenv(GIT_REFLOG_ACTION, action_name(opts), 0); + setenv(GIT_REFLOG_ACTION, command_name(opts), 0); return do_pick_commit(cmit, opts); } @@ -1116,7 +1122,7 @@ static int pick_revisions(struct replay_opts *opts) if (create_seq_dir() < 0) return -1; if (get_sha1("HEAD", sha1)) { - if (opts->action == REPLAY_REVERT) + if (opts->command == REPLAY_CMD_REVERT) return error(_("Can't revert as initial commit")); return error(_("Can't cherry-pick into empty head")); } @@ -1133,7 +1139,7 @@ int cmd_revert(int argc, const char **argv, const char *prefix) memset(&opts, 0, sizeof(opts)); if (isatty(0)) opts.edit = 1; - opts.action = REPLAY_REVERT; + opts.command = REPLAY_CMD_REVERT; git_config(git_default_config, NULL); parse_args(argc, argv, &opts); res = pick_revisions(&opts); @@ -1148,7 +1154,7 @@ int cmd_cherry_pick(int argc, const char **argv, const char *prefix) int res; memset(&opts, 0, sizeof(opts)); - opts.action = REPLAY_PICK; + opts.command = REPLAY_CMD_CHERRY_PICK; git_config(git_default_config, NULL); parse_args(argc, argv, &opts); res = pick_revisions(&opts); diff --git a/sequencer.h b/sequencer.h index 1d9fcec..07e0639 100644 --- a/sequencer.h +++ b/sequencer.h @@ -11,6 +11,11 @@ enum replay_action { REPLAY_PICK }; +enum replay_command { + REPLAY_CMD_REVERT, + REPLAY_CMD_CHERRY_PICK +}; + enum replay_subcommand { REPLAY_NONE, REPLAY_REMOVE_STATE, -- 1.7.8.2 ^ permalink raw reply related [flat|nested] 64+ messages in thread
* Re: [PATCH 2/6] revert: decouple sequencer actions from builtin commands 2012-01-08 12:27 ` [PATCH 2/6] revert: decouple sequencer actions from builtin commands Ramkumar Ramachandra @ 2012-01-08 19:34 ` Jonathan Nieder 2012-01-08 19:53 ` Ramkumar Ramachandra 0 siblings, 1 reply; 64+ messages in thread From: Jonathan Nieder @ 2012-01-08 19:34 UTC (permalink / raw) To: Ramkumar Ramachandra; +Cc: Git List, Junio C Hamano Ramkumar Ramachandra wrote: > So, while a future > instruction sheet would look like: > > pick next~4 > action3 b74fea > revert rr/moo^2~34 > > the actions "pick", "action3" and "revert" need not necessarily > correspond to the specific builtins. So what change does the patch actually make? Is this a renaming? [...] > --- a/builtin/revert.c > +++ b/builtin/revert.c [...] > @@ -64,16 +64,21 @@ struct replay_opts { > > #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" > > -static const char *action_name(const struct replay_opts *opts) > +static const char *command_name(struct replay_opts *opts) Why is the const being dropped? I'm lost, so not reading further. ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 2/6] revert: decouple sequencer actions from builtin commands 2012-01-08 19:34 ` Jonathan Nieder @ 2012-01-08 19:53 ` Ramkumar Ramachandra 2012-01-08 20:09 ` Jonathan Nieder 0 siblings, 1 reply; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-08 19:53 UTC (permalink / raw) To: Jonathan Nieder; +Cc: Git List, Junio C Hamano Jonathan Nieder wrote: > Ramkumar Ramachandra wrote: > >> So, while a future >> instruction sheet would look like: >> >> pick next~4 >> action3 b74fea >> revert rr/moo^2~34 >> >> the actions "pick", "action3" and "revert" need not necessarily >> correspond to the specific builtins. > > So what change does the patch actually make? Is this a renaming? Yes, it renames "action" to "command" where appropriate. > [...] >> --- a/builtin/revert.c >> +++ b/builtin/revert.c > [...] >> @@ -64,16 +64,21 @@ struct replay_opts { >> >> #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" >> >> -static const char *action_name(const struct replay_opts *opts) >> +static const char *command_name(struct replay_opts *opts) > > Why is the const being dropped? I'm lost, so not reading further. Minor error. The rest of the patch should be fine. -- Ram ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 2/6] revert: decouple sequencer actions from builtin commands 2012-01-08 19:53 ` Ramkumar Ramachandra @ 2012-01-08 20:09 ` Jonathan Nieder 2012-01-08 20:07 ` Ramkumar Ramachandra 0 siblings, 1 reply; 64+ messages in thread From: Jonathan Nieder @ 2012-01-08 20:09 UTC (permalink / raw) To: Ramkumar Ramachandra; +Cc: Git List, Junio C Hamano Ramkumar Ramachandra wrote: > Jonathan Nieder wrote: >> So what change does the patch actually make? Is this a renaming? > > Yes, it renames "action" to "command" where appropriate. Wouldn't a simple renaming have a diffstat with the same number of added and removed lines? ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 2/6] revert: decouple sequencer actions from builtin commands 2012-01-08 20:09 ` Jonathan Nieder @ 2012-01-08 20:07 ` Ramkumar Ramachandra 2012-01-08 20:48 ` Jonathan Nieder 0 siblings, 1 reply; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-08 20:07 UTC (permalink / raw) To: Jonathan Nieder; +Cc: Git List, Junio C Hamano Hi, Jonathan Nieder wrote: > Ramkumar Ramachandra wrote: >> Jonathan Nieder wrote: > >>> So what change does the patch actually make? Is this a renaming? >> >> Yes, it renames "action" to "command" where appropriate. > > Wouldn't a simple renaming have a diffstat with the same number of added > and removed lines? Yes, almost. A few extra lines added because I needed a new data enum for the "command"; also added a convenience function: action_name(). -- Ram ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 2/6] revert: decouple sequencer actions from builtin commands 2012-01-08 20:07 ` Ramkumar Ramachandra @ 2012-01-08 20:48 ` Jonathan Nieder 0 siblings, 0 replies; 64+ messages in thread From: Jonathan Nieder @ 2012-01-08 20:48 UTC (permalink / raw) To: Ramkumar Ramachandra; +Cc: Git List, Junio C Hamano Ramkumar Ramachandra wrote: > Jonathan Nieder wrote: >> Ramkumar Ramachandra wrote: >>> Jonathan Nieder wrote: >>>> So what change does the patch actually make? Is this a renaming? >>> >>> Yes, it renames "action" to "command" where appropriate. >> >> Wouldn't a simple renaming have a diffstat with the same number of added >> and removed lines? > > Yes, almost. A few extra lines added because I needed a new data enum > for the "command"; also added a convenience function: action_name(). It's not a simple renaming, then. What user-visible effect will this have, if any? What programmer-visible effect will it have, if any? I _really_ should not have to read the patch to learn the impact of a patch. ^ permalink raw reply [flat|nested] 64+ messages in thread
* [PATCH 3/6] revert: don't let revert continue a cherry-pick 2012-01-08 12:27 [PATCH 0/6] The move to sequencer.c Ramkumar Ramachandra 2012-01-08 12:27 ` [PATCH 1/6] revert: move replay_action, replay_subcommand to header Ramkumar Ramachandra 2012-01-08 12:27 ` [PATCH 2/6] revert: decouple sequencer actions from builtin commands Ramkumar Ramachandra @ 2012-01-08 12:27 ` Ramkumar Ramachandra 2012-01-08 19:37 ` Jonathan Nieder 2012-01-08 12:27 ` [PATCH 4/6] revert: allow mixing "pick" and "revert" actions Ramkumar Ramachandra ` (4 subsequent siblings) 7 siblings, 1 reply; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-08 12:27 UTC (permalink / raw) To: Git List; +Cc: Junio C Hamano, Jonathan Nieder When we allow mixing "revert" and "pick" instructions in the same sheet in the next patch, the following workflow would be perfectly valid: $ git cherry-pick base..latercommit [conflict occurs] $ edit problematicfile $ git add problematicfile $ git revert --continue [finishes successfully] This is confusing to the operator, because the sequencer is an implementation detail hidden behind the 'git cherry-pick' and 'git revert' builtins. So, disallow this workflow by keeping track of the builtin command executed (either "revert" or "cherry-pick") in '.git/sequencer/cmd'. Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com> --- builtin/revert.c | 57 +++++++++++++++++++++++++++++++++++++++ sequencer.h | 1 + t/t3510-cherry-pick-sequence.sh | 11 +++++++ 3 files changed, 69 insertions(+), 0 deletions(-) diff --git a/builtin/revert.c b/builtin/revert.c index 3ac6da0..52fa115 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -871,6 +871,50 @@ static int create_seq_dir(void) return 0; } +static enum replay_command read_cmd(void) +{ + const char *cmd_file = git_path(SEQ_CMD_FILE); + struct strbuf buf = STRBUF_INIT; + enum replay_command res; + int fd; + + fd = open(cmd_file, O_RDONLY); + if (fd < 0) + die_errno(_("Could not open %s"), cmd_file); + if (strbuf_read(&buf, fd, 0) < 0) { + close(fd); + strbuf_release(&buf); + die(_("Could not read %s."), cmd_file); + } + close(fd); + + if (!strcmp(buf.buf, "revert\n")) + res = REPLAY_CMD_REVERT; + else if (!strcmp(buf.buf, "cherry-pick\n")) + res = REPLAY_CMD_CHERRY_PICK; + else { + strbuf_release(&buf); + die(_("Malformed command file: %s"), cmd_file); + } + strbuf_release(&buf); + return res; +} + +static void save_cmd(struct replay_opts *opts) +{ + const char *cmd_file = git_path(SEQ_CMD_FILE); + static struct lock_file cmd_lock; + struct strbuf buf = STRBUF_INIT; + int fd; + + fd = hold_lock_file_for_update(&cmd_lock, cmd_file, LOCK_DIE_ON_ERROR); + strbuf_addf(&buf, "%s\n", command_name(opts)); + if (write_in_full(fd, buf.buf, buf.len) < 0) + die_errno(_("Could not write to %s"), cmd_file); + if (commit_lock_file(&cmd_lock) < 0) + die(_("Error wrapping up %s."), cmd_file); +} + static void save_head(const char *head) { const char *head_file = git_path(SEQ_HEAD_FILE); @@ -1043,9 +1087,21 @@ static int continue_single_pick(void) static int sequencer_continue(struct replay_opts *opts) { struct commit_list *todo_list = NULL; + enum replay_command cmd; if (!file_exists(git_path(SEQ_TODO_FILE))) return continue_single_pick(); + + /* + * Disallow continuing a cherry-pick with 'git revert + * --continue' and viceversa + */ + cmd = read_cmd(); + if (cmd != opts->command) + return error(_("cannot %s: a %s is in progress."), + command_name(opts), + cmd == REPLAY_CMD_REVERT ? "revert" : "cherry-pick"); + read_populate_opts(&opts); read_populate_todo(&todo_list, opts); @@ -1126,6 +1182,7 @@ static int pick_revisions(struct replay_opts *opts) return error(_("Can't revert as initial commit")); return error(_("Can't cherry-pick into empty head")); } + save_cmd(opts); save_head(sha1_to_hex(sha1)); save_opts(opts); return pick_commits(todo_list, opts); diff --git a/sequencer.h b/sequencer.h index 07e0639..00ab685 100644 --- a/sequencer.h +++ b/sequencer.h @@ -2,6 +2,7 @@ #define SEQUENCER_H #define SEQ_DIR "sequencer" +#define SEQ_CMD_FILE "sequencer/cmd" #define SEQ_HEAD_FILE "sequencer/head" #define SEQ_TODO_FILE "sequencer/todo" #define SEQ_OPTS_FILE "sequencer/opts" diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh index 97f3710..73298cf 100755 --- a/t/t3510-cherry-pick-sequence.sh +++ b/t/t3510-cherry-pick-sequence.sh @@ -48,6 +48,7 @@ test_expect_success 'cherry-pick persists data on failure' ' pristine_detach initial && test_expect_code 1 git cherry-pick -s base..anotherpick && test_path_is_dir .git/sequencer && + test_path_is_file .git/sequencer/cmd && test_path_is_file .git/sequencer/head && test_path_is_file .git/sequencer/todo && test_path_is_file .git/sequencer/opts @@ -69,6 +70,7 @@ test_expect_success 'cherry-pick persists opts correctly' ' pristine_detach initial && test_expect_code 128 git cherry-pick -s -m 1 --strategy=recursive -X patience -X ours initial..anotherpick && test_path_is_dir .git/sequencer && + test_path_is_file .git/sequencer/cmd && test_path_is_file .git/sequencer/head && test_path_is_file .git/sequencer/todo && test_path_is_file .git/sequencer/opts && @@ -517,4 +519,13 @@ test_expect_success 'commit descriptions in insn sheet are optional' ' test_line_count = 4 commits ' +test_expect_success 'revert --continue refuses to follow cherry-pick' ' + pristine_detach initial && + test_expect_code 1 git cherry-pick base..anotherpick && + echo "c" >foo && + git add foo && + git commit && + test_expect_code 128 git revert --continue +' + test_done -- 1.7.8.2 ^ permalink raw reply related [flat|nested] 64+ messages in thread
* Re: [PATCH 3/6] revert: don't let revert continue a cherry-pick 2012-01-08 12:27 ` [PATCH 3/6] revert: don't let revert continue a cherry-pick Ramkumar Ramachandra @ 2012-01-08 19:37 ` Jonathan Nieder 2012-01-08 20:03 ` Ramkumar Ramachandra 0 siblings, 1 reply; 64+ messages in thread From: Jonathan Nieder @ 2012-01-08 19:37 UTC (permalink / raw) To: Ramkumar Ramachandra; +Cc: Git List, Junio C Hamano Ramkumar Ramachandra wrote: > When we allow mixing "revert" and "pick" instructions in the same > sheet in the next patch, the following workflow would be perfectly > valid: > > $ git cherry-pick base..latercommit > [conflict occurs] > $ edit problematicfile > $ git add problematicfile > $ git revert --continue > [finishes successfully] Does "workflow" mean "sequence of commands"? > This is confusing to the operator, because the sequencer is an > implementation detail hidden behind the 'git cherry-pick' and 'git > revert' builtins. I don't know --- it's not confusing to me. Could you explain further what harm the current behavior does? E.g., could it cause me to misunderstand some basic concepts, or could it lead me to run commands that cause me to scratch my head or lose data? > builtin/revert.c | 57 +++++++++++++++++++++++++++++++++++++++ > sequencer.h | 1 + > t/t3510-cherry-pick-sequence.sh | 11 +++++++ > 3 files changed, 69 insertions(+), 0 deletions(-) I haven't read the patch, but based on the above rationale and this diffstat, it doesn't look good. ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 3/6] revert: don't let revert continue a cherry-pick 2012-01-08 19:37 ` Jonathan Nieder @ 2012-01-08 20:03 ` Ramkumar Ramachandra 2012-01-08 20:22 ` Jonathan Nieder 0 siblings, 1 reply; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-08 20:03 UTC (permalink / raw) To: Jonathan Nieder; +Cc: Git List, Junio C Hamano Hi, Jonathan Nieder wrote: > Ramkumar Ramachandra wrote: > >> When we allow mixing "revert" and "pick" instructions in the same >> sheet in the next patch, the following workflow would be perfectly >> valid: >> >> $ git cherry-pick base..latercommit >> [conflict occurs] >> $ edit problematicfile >> $ git add problematicfile >> $ git revert --continue >> [finishes successfully] > > Does "workflow" mean "sequence of commands"? Yes. Clarified wording. >> This is confusing to the operator, because the sequencer is an >> implementation detail hidden behind the 'git cherry-pick' and 'git >> revert' builtins. > > I don't know --- it's not confusing to me. Could you explain further > what harm the current behavior does? E.g., could it cause me to > misunderstand some basic concepts, or could it lead me to run commands > that cause me to scratch my head or lose data? Junio explained this to me in [1]. It's very unnatural for a user to want to execute "git cherry-pick --continue" when the previous command was a "git revert": it probably means that she forgot about the in-progress "git revert". The problem becomes more serious when the sequencer grows more capabilities: a "git merge --continue" to continue a "git am" sounds much more absurd. Ofcourse, we will provide a way to continue any sequencer operation in the future: "git continue" seems to be a good candidate. [1]: http://thread.gmane.org/gmane.comp.version-control.git/185355 Thanks. -- Ram ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 3/6] revert: don't let revert continue a cherry-pick 2012-01-08 20:03 ` Ramkumar Ramachandra @ 2012-01-08 20:22 ` Jonathan Nieder 2012-01-08 20:28 ` Ramkumar Ramachandra 0 siblings, 1 reply; 64+ messages in thread From: Jonathan Nieder @ 2012-01-08 20:22 UTC (permalink / raw) To: Ramkumar Ramachandra; +Cc: Git List, Junio C Hamano Ramkumar Ramachandra wrote: > Jonathan Nieder wrote: >> I don't know --- it's not confusing to me. Could you explain further >> what harm the current behavior does? E.g., could it cause me to >> misunderstand some basic concepts, or could it lead me to run commands >> that cause me to scratch my head or lose data? > > Junio explained this to me in [1]. It's very unnatural for a user to > want to execute "git cherry-pick --continue" when the previous command > was a "git revert": it probably means that she forgot about the > in-progress "git revert". [...] > [1]: http://thread.gmane.org/gmane.comp.version-control.git/185355 I don't think that's what Junio said. Did this actually happen, or is it a theoretical worry? I think I would be more likely to run "git cherry-pick <foo>..<bar>" than "git cherry-pick --continue" if I had forgotten about an in-progress revert. The former already errors out with a sensible message. Or is the problem that I might run: git revert foo..bar git reset --merge; # conflict --- let's clean this up # ah, I remember reverting the patch that conflicted before; # let's reuse the resolution. git cherry-pick baz edit file.c; # another conflict, sigh git add file.c git cherry-pick --continue; # oops! ? That seems like a real worry, but the same problem could happen with cherry-pick used both for the multipick and single-pick, so I don't think your patch fundamentally addresses it. In other words, this is a problem caused by the overloading of the same cherry-pick command for single-pick and multi-pick. I think it should be preventable by remembering which action failed when stopping a sequence and doing only a single-pick resume if CHERRY_PICK_HEAD/REVERT_HEAD/whatever doesn't match that. The "oops" is bad since the operator might have been intending to run some more tests and amend as necessary before continuing the multi-pick. It is not _that_ bad, since more typically one would have already run some tests before running cherry-pick --continue to commit the resolution. Still probably worth fixing. > The problem becomes more serious when the > sequencer grows more capabilities: a "git merge --continue" to > continue a "git am" sounds much more absurd. Ofcourse, we will > provide a way to continue any sequencer operation in the future: "git > continue" seems to be a good candidate. I don't understand why "cherry-pick --continue" resuming a revert sequence implies that "merge --continue" would have to as well. All that said, forbidding cherry-pick --continue from resuming a revert sequence would be fine with me, _as long as the semantics are clearly spelled out in the commit message and documentation_. What happens when there is a mixture of picks and reverts? Thanks. Jonathan ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 3/6] revert: don't let revert continue a cherry-pick 2012-01-08 20:22 ` Jonathan Nieder @ 2012-01-08 20:28 ` Ramkumar Ramachandra 2012-01-08 20:45 ` Jonathan Nieder 0 siblings, 1 reply; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-08 20:28 UTC (permalink / raw) To: Jonathan Nieder; +Cc: Git List, Junio C Hamano Hi again, Jonathan Nieder wrote: > Ramkumar Ramachandra wrote: >> Jonathan Nieder wrote: > [...] >> Junio explained this to me in [1]. It's very unnatural for a user to >> want to execute "git cherry-pick --continue" when the previous command >> was a "git revert": it probably means that she forgot about the >> in-progress "git revert". > [...] >> [1]: http://thread.gmane.org/gmane.comp.version-control.git/185355 > > I don't think that's what Junio said. > > Did this actually happen, or is it a theoretical worry? I think I would > be more likely to run "git cherry-pick <foo>..<bar>" than "git > cherry-pick --continue" if I had forgotten about an in-progress > revert. The former already errors out with a sensible message. > > Or is the problem that I might run: > > git revert foo..bar > git reset --merge; # conflict --- let's clean this up > > # ah, I remember reverting the patch that conflicted before; > # let's reuse the resolution. > git cherry-pick baz > edit file.c; # another conflict, sigh > git add file.c > git cherry-pick --continue; # oops! > > ? That seems like a real worry, but the same problem could happen > with cherry-pick used both for the multipick and single-pick, so I > don't think your patch fundamentally addresses it. Good catch. I didn't replay this scenario in my head earlier. > In other words, this is a problem caused by the overloading of the > same cherry-pick command for single-pick and multi-pick. I think it > should be preventable by remembering which action failed when stopping > a sequence and doing only a single-pick resume if > CHERRY_PICK_HEAD/REVERT_HEAD/whatever doesn't match that. I was attempting to fix this to simplify the life of the user, not complicate it further- the user might have no idea what the next command in the sequence is, and I don't see the point in inconveniencing her. In retrospect, I think we should simply drop this patch. > What > happens when there is a mixture of picks and reverts? Permitted. -- Ram ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 3/6] revert: don't let revert continue a cherry-pick 2012-01-08 20:28 ` Ramkumar Ramachandra @ 2012-01-08 20:45 ` Jonathan Nieder 0 siblings, 0 replies; 64+ messages in thread From: Jonathan Nieder @ 2012-01-08 20:45 UTC (permalink / raw) To: Ramkumar Ramachandra; +Cc: Git List, Junio C Hamano Ramkumar Ramachandra wrote: > In retrospect, I think we should simply drop > this patch. Great --- I don't think it had much to do with this series. I'll keep this mail in my inbox. Who knows --- maybe I'll get a moment to teach conflicted single-picks to write a .git/sequencer/when-continuing-just-continue-the-single-pick file. ^ permalink raw reply [flat|nested] 64+ messages in thread
* [PATCH 4/6] revert: allow mixing "pick" and "revert" actions 2012-01-08 12:27 [PATCH 0/6] The move to sequencer.c Ramkumar Ramachandra ` (2 preceding siblings ...) 2012-01-08 12:27 ` [PATCH 3/6] revert: don't let revert continue a cherry-pick Ramkumar Ramachandra @ 2012-01-08 12:27 ` Ramkumar Ramachandra 2012-01-08 19:40 ` Jonathan Nieder 2012-01-08 12:27 ` [PATCH 5/6] revert: report fine-grained error messages from insn parser Ramkumar Ramachandra ` (3 subsequent siblings) 7 siblings, 1 reply; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-08 12:27 UTC (permalink / raw) To: Git List; +Cc: Junio C Hamano, Jonathan Nieder Parse the instruction sheet in '.git/sequencer/todo' as a list of (action, operand) pairs, instead of assuming that all lines have the same action. Now, an instruction sheet like the following is perfectly valid: pick fdc0b12 picked revert 965fed4 anotherpick The operator can use this feature by hand-editing the instruction sheet and using '--continue' as appropriate: $ git cherry-pick foo..bar [conflict occurs] $ edit problematicfile $ git add problematicfile $ edit .git/sequencer/todo $ git cherry-pick --continue [finishes successfully] Helped-by: Jonathan Nieder <jrnider@gmail.com> Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com> --- builtin/revert.c | 135 +++++++++++++++++++-------------------- sequencer.h | 6 ++ t/t3510-cherry-pick-sequence.sh | 46 ++++++++++---- 3 files changed, 105 insertions(+), 82 deletions(-) diff --git a/builtin/revert.c b/builtin/revert.c index 52fa115..54ea394 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -475,7 +475,8 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts) return run_command_v_opt(args, RUN_GIT_CMD); } -static int do_pick_commit(struct commit *commit, struct replay_opts *opts) +static int do_pick_commit(struct commit *commit, enum replay_action action, + struct replay_opts *opts) { unsigned char head[20]; struct commit *base, *next, *parent; @@ -550,7 +551,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) defmsg = git_pathdup("MERGE_MSG"); - if (opts->command == REPLAY_CMD_REVERT) { + if (action == REPLAY_REVERT) { base = commit; base_label = msg.label; next = parent; @@ -591,7 +592,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) } } - if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->command == REPLAY_CMD_REVERT) { + if (!opts->strategy || !strcmp(opts->strategy, "recursive") || action == REPLAY_REVERT) { res = do_recursive_merge(base, next, base_label, next_label, head, &msgbuf, opts); write_message(&msgbuf, defmsg); @@ -615,13 +616,13 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) * However, if the merge did not even start, then we don't want to * write it at all. */ - if (opts->command == REPLAY_CMD_CHERRY_PICK && !opts->no_commit && (res == 0 || res == 1)) + if (action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1)) write_cherry_pick_head(commit, "CHERRY_PICK_HEAD"); - if (opts->command == REPLAY_CMD_REVERT && ((opts->no_commit && res == 0) || res == 1)) + if (action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1)) write_cherry_pick_head(commit, "REVERT_HEAD"); if (res) { - error(opts->command == REPLAY_CMD_REVERT + error(action == REPLAY_REVERT ? _("could not revert %s... %s") : _("could not apply %s... %s"), find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), @@ -668,70 +669,70 @@ static void read_and_refresh_cache(struct replay_opts *opts) } /* - * Append a commit to the end of the commit_list. + * Append a (commit, action) to the end of the replay_insn_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. + * empty replay_insn_list, and is updated to point to the "next" field of + * the last item on the list as new (commit, action) pairs are appended. * * Usage example: * - * struct commit_list *list; - * struct commit_list **next = &list; + * struct replay_insn_list *list; + * struct replay_insn_list **next = &list; * - * next = commit_list_append(c1, next); - * next = commit_list_append(c2, next); - * assert(commit_list_count(list) == 2); + * next = replay_insn_list_append(c1, a1, next); + * next = replay_insn_list_append(c2, a2, next); + * assert(len(list) == 2); * return list; */ -static struct commit_list **commit_list_append(struct commit *commit, - struct commit_list **next) +static struct replay_insn_list **replay_insn_list_append(struct commit *operand, + enum replay_action action, + struct replay_insn_list **next) { - struct commit_list *new = xmalloc(sizeof(struct commit_list)); - new->item = commit; + struct replay_insn_list *new = xmalloc(sizeof(*new)); + new->action = action; + new->operand = operand; *next = new; new->next = NULL; return &new->next; } -static int format_todo(struct strbuf *buf, struct commit_list *todo_list, - struct replay_opts *opts) +static int format_todo(struct strbuf *buf, struct replay_insn_list *todo_list) { - struct commit_list *cur = NULL; - const char *sha1_abbrev = NULL; - const char *action_str = opts->command == REPLAY_CMD_REVERT ? "revert" : "pick"; - const char *subject; - int subject_len; + struct replay_insn_list *cur; for (cur = todo_list; cur; cur = cur->next) { - sha1_abbrev = find_unique_abbrev(cur->item->object.sha1, DEFAULT_ABBREV); - subject_len = find_commit_subject(cur->item->buffer, &subject); + const char *sha1_abbrev, *action_str, *subject; + int subject_len; + + action_str = action_name(cur->action); + sha1_abbrev = find_unique_abbrev(cur->operand->object.sha1, DEFAULT_ABBREV); + subject_len = find_commit_subject(cur->operand->buffer, &subject); strbuf_addf(buf, "%s %s %.*s\n", action_str, sha1_abbrev, subject_len, subject); } return 0; } -static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts *opts) +static int parse_insn_line(char *bol, char *eol, struct replay_insn_list *item) { unsigned char commit_sha1[20]; - enum replay_action action; char *end_of_object_name; int saved, status, padding; if (!prefixcmp(bol, "pick")) { - action = REPLAY_PICK; + item->action = REPLAY_PICK; bol += strlen("pick"); } else if (!prefixcmp(bol, "revert")) { - action = REPLAY_REVERT; + item->action = REPLAY_REVERT; bol += strlen("revert"); } else - return NULL; + return -1; /* Eat up extra spaces/ tabs before object name */ padding = strspn(bol, " \t"); if (!padding) - return NULL; + return -1; bol += padding; end_of_object_name = bol + strcspn(bol, " \t\n"); @@ -740,37 +741,29 @@ static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts * status = get_sha1(bol, commit_sha1); *end_of_object_name = saved; - /* - * Verify that the action matches up with the one in - * opts; we don't support arbitrary instructions - */ - if ((action == REPLAY_PICK && opts->command == REPLAY_CMD_REVERT) || - (action == REPLAY_REVERT && opts->command == REPLAY_CMD_CHERRY_PICK)) { - error(_("Cannot %s during a %s"), action_name(action), - command_name(opts)); - return NULL; - } - if (status < 0) - return NULL; + return -1; + + item->operand = lookup_commit_reference(commit_sha1); + if (!item->operand) + return -1; - return lookup_commit_reference(commit_sha1); + item->next = NULL; + return 0; } -static int parse_insn_buffer(char *buf, struct commit_list **todo_list, - struct replay_opts *opts) +static int parse_insn_buffer(char *buf, struct replay_insn_list **todo_list) { - struct commit_list **next = todo_list; - struct commit *commit; + struct replay_insn_list **next = todo_list; + struct replay_insn_list item = { NULL, 0, NULL }; char *p = buf; int i; for (i = 1; *p; i++) { char *eol = strchrnul(p, '\n'); - commit = parse_insn_line(p, eol, opts); - if (!commit) + if (parse_insn_line(p, eol, &item) < 0) return error(_("Could not parse line %d."), i); - next = commit_list_append(commit, next); + next = replay_insn_list_append(item.operand, item.action, next); p = *eol ? eol + 1 : eol; } if (!*todo_list) @@ -778,8 +771,7 @@ static int parse_insn_buffer(char *buf, struct commit_list **todo_list, return 0; } -static void read_populate_todo(struct commit_list **todo_list, - struct replay_opts *opts) +static void read_populate_todo(struct replay_insn_list **todo_list) { const char *todo_file = git_path(SEQ_TODO_FILE); struct strbuf buf = STRBUF_INIT; @@ -795,7 +787,7 @@ static void read_populate_todo(struct commit_list **todo_list, } close(fd); - res = parse_insn_buffer(buf.buf, todo_list, opts); + res = parse_insn_buffer(buf.buf, todo_list); strbuf_release(&buf); if (res) die(_("Unusable instruction sheet: %s"), todo_file); @@ -844,17 +836,19 @@ static void read_populate_opts(struct replay_opts **opts_ptr) die(_("Malformed options sheet: %s"), opts_file); } -static void walk_revs_populate_todo(struct commit_list **todo_list, +static void walk_revs_populate_todo(struct replay_insn_list **todo_list, struct replay_opts *opts) { struct commit *commit; - struct commit_list **next; + struct replay_insn_list **next; + enum replay_action action; prepare_revs(opts); next = todo_list; + action = opts->command == REPLAY_CMD_REVERT ? REPLAY_REVERT : REPLAY_PICK; while ((commit = get_revision(opts->revs))) - next = commit_list_append(commit, next); + next = replay_insn_list_append(commit, action, next); } static int create_seq_dir(void) @@ -996,7 +990,7 @@ fail: return -1; } -static void save_todo(struct commit_list *todo_list, struct replay_opts *opts) +static void save_todo(struct replay_insn_list *todo_list) { const char *todo_file = git_path(SEQ_TODO_FILE); static struct lock_file todo_lock; @@ -1004,7 +998,7 @@ static void save_todo(struct commit_list *todo_list, struct replay_opts *opts) int fd; fd = hold_lock_file_for_update(&todo_lock, todo_file, LOCK_DIE_ON_ERROR); - if (format_todo(&buf, todo_list, opts) < 0) + if (format_todo(&buf, todo_list) < 0) die(_("Could not format %s."), todo_file); if (write_in_full(fd, buf.buf, buf.len) < 0) { strbuf_release(&buf); @@ -1048,9 +1042,9 @@ static void save_opts(struct replay_opts *opts) } } -static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts) +static int pick_commits(struct replay_insn_list *todo_list, struct replay_opts *opts) { - struct commit_list *cur; + struct replay_insn_list *cur; int res; setenv(GIT_REFLOG_ACTION, command_name(opts), 0); @@ -1060,8 +1054,8 @@ static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts) read_and_refresh_cache(opts); for (cur = todo_list; cur; cur = cur->next) { - save_todo(cur, opts); - res = do_pick_commit(cur->item, opts); + save_todo(cur); + res = do_pick_commit(cur->operand, cur->action, opts); if (res) return res; } @@ -1086,7 +1080,7 @@ static int continue_single_pick(void) static int sequencer_continue(struct replay_opts *opts) { - struct commit_list *todo_list = NULL; + struct replay_insn_list *todo_list = NULL; enum replay_command cmd; if (!file_exists(git_path(SEQ_TODO_FILE))) @@ -1103,7 +1097,7 @@ static int sequencer_continue(struct replay_opts *opts) cmd == REPLAY_CMD_REVERT ? "revert" : "cherry-pick"); read_populate_opts(&opts); - read_populate_todo(&todo_list, opts); + read_populate_todo(&todo_list); /* Verify that the conflict has been resolved */ if (file_exists(git_path("CHERRY_PICK_HEAD")) || @@ -1120,13 +1114,16 @@ static int sequencer_continue(struct replay_opts *opts) static int single_pick(struct commit *cmit, struct replay_opts *opts) { + enum replay_action action; + action = opts->command == REPLAY_CMD_REVERT ? REPLAY_REVERT : REPLAY_PICK; + setenv(GIT_REFLOG_ACTION, command_name(opts), 0); - return do_pick_commit(cmit, opts); + return do_pick_commit(cmit, action, opts); } static int pick_revisions(struct replay_opts *opts) { - struct commit_list *todo_list = NULL; + struct replay_insn_list *todo_list = NULL; unsigned char sha1[20]; if (opts->subcommand == REPLAY_NONE) diff --git a/sequencer.h b/sequencer.h index 00ab685..d1cb5c2 100644 --- a/sequencer.h +++ b/sequencer.h @@ -24,6 +24,12 @@ enum replay_subcommand { REPLAY_ROLLBACK }; +struct replay_insn_list { + struct commit *operand; + enum replay_action action; + struct replay_insn_list *next; +}; + /* Removes SEQ_DIR. */ extern void remove_sequencer_state(void); diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh index 73298cf..f97ab79 100755 --- a/t/t3510-cherry-pick-sequence.sh +++ b/t/t3510-cherry-pick-sequence.sh @@ -456,7 +456,7 @@ test_expect_success 'sign-off needs to be reaffirmed after conflict resolution, ! grep Signed-off-by: msg ' -test_expect_success 'malformed instruction sheet 1' ' +test_expect_success 'malformed instruction sheet, action' ' pristine_detach initial && test_expect_code 1 git cherry-pick base..anotherpick && echo "resolved" >foo && @@ -467,23 +467,12 @@ test_expect_success 'malformed instruction sheet 1' ' test_expect_code 128 git cherry-pick --continue ' -test_expect_success 'malformed instruction sheet 2' ' - pristine_detach initial && - test_expect_code 1 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_expect_code 128 git cherry-pick --continue -' - test_expect_success 'empty commit set' ' pristine_detach initial && test_expect_code 128 git cherry-pick base..base ' -test_expect_success 'malformed instruction sheet 3' ' +test_expect_success 'malformed instruction sheet, object name' ' pristine_detach initial && test_expect_code 1 git cherry-pick base..anotherpick && echo "resolved" >foo && @@ -528,4 +517,35 @@ test_expect_success 'revert --continue refuses to follow cherry-pick' ' test_expect_code 128 git revert --continue ' +test_expect_success 'mixed pick and revert instructions' ' + pristine_detach initial && + test_expect_code 1 git cherry-pick base..anotherpick && + echo "c" >foo && + git add foo && + git commit && + oldsha=`git rev-parse --short HEAD~1` && + echo "revert $oldsha unrelatedpick" >>.git/sequencer/todo && + 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 unrelated + 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_done -- 1.7.8.2 ^ permalink raw reply related [flat|nested] 64+ messages in thread
* Re: [PATCH 4/6] revert: allow mixing "pick" and "revert" actions 2012-01-08 12:27 ` [PATCH 4/6] revert: allow mixing "pick" and "revert" actions Ramkumar Ramachandra @ 2012-01-08 19:40 ` Jonathan Nieder 2012-01-08 20:17 ` Ramkumar Ramachandra 0 siblings, 1 reply; 64+ messages in thread From: Jonathan Nieder @ 2012-01-08 19:40 UTC (permalink / raw) To: Ramkumar Ramachandra; +Cc: Git List, Junio C Hamano Ramkumar Ramachandra wrote: > Parse the instruction sheet in '.git/sequencer/todo' as a list of > (action, operand) pairs, instead of assuming that all lines have the > same action. Yes, I've always liked this one. Haven't re-read the patch to avoid wasted effort if there are changes when the previous patches in the series change. Maybe it would be possible to send as a standalone? ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 4/6] revert: allow mixing "pick" and "revert" actions 2012-01-08 19:40 ` Jonathan Nieder @ 2012-01-08 20:17 ` Ramkumar Ramachandra 2012-01-08 21:40 ` Jonathan Nieder 0 siblings, 1 reply; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-08 20:17 UTC (permalink / raw) To: Jonathan Nieder; +Cc: Git List, Junio C Hamano Hi, Jonathan Nieder wrote: > Ramkumar Ramachandra wrote: > >> Parse the instruction sheet in '.git/sequencer/todo' as a list of >> (action, operand) pairs, instead of assuming that all lines have the >> same action. > > Yes, I've always liked this one. > > Haven't re-read the patch to avoid wasted effort if there are changes > when the previous patches in the series change. Maybe it would be > possible to send as a standalone? If I don't get manage to get the series right in a couple of re-rolls, I'll do that. -- Ram ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 4/6] revert: allow mixing "pick" and "revert" actions 2012-01-08 20:17 ` Ramkumar Ramachandra @ 2012-01-08 21:40 ` Jonathan Nieder 2012-01-08 21:55 ` Jonathan Nieder 0 siblings, 1 reply; 64+ messages in thread From: Jonathan Nieder @ 2012-01-08 21:40 UTC (permalink / raw) To: Ramkumar Ramachandra; +Cc: Git List, Junio C Hamano Ramkumar Ramachandra wrote: > Jonathan Nieder wrote: >> Yes, I've always liked this one. >> >> Haven't re-read the patch to avoid wasted effort if there are changes >> when the previous patches in the series change. Maybe it would be >> possible to send as a standalone? > > If I don't get manage to get the series right in a couple of re-rolls, > I'll do that. Splitting out unrelated changes (or at least putting the uncontroversial ones near the front) is part of getting a series right. I'll pick up this patch, play around with it and send separately. ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 4/6] revert: allow mixing "pick" and "revert" actions 2012-01-08 21:40 ` Jonathan Nieder @ 2012-01-08 21:55 ` Jonathan Nieder 2012-01-10 3:40 ` Ramkumar Ramachandra 0 siblings, 1 reply; 64+ messages in thread From: Jonathan Nieder @ 2012-01-08 21:55 UTC (permalink / raw) To: Ramkumar Ramachandra; +Cc: Git List, Junio C Hamano Jonathan Nieder wrote: > I'll pick up > this patch, play around with it and send separately. On second thought, do you have a link to the last submitted version, and do you remember if there were any important changes since then? The base for that one should be closer to "master", I think. ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 4/6] revert: allow mixing "pick" and "revert" actions 2012-01-08 21:55 ` Jonathan Nieder @ 2012-01-10 3:40 ` Ramkumar Ramachandra 0 siblings, 0 replies; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-10 3:40 UTC (permalink / raw) To: Jonathan Nieder; +Cc: Git List, Junio C Hamano Hi Jonathan, Jonathan Nieder wrote: > On second thought, do you have a link to the last submitted version, > and do you remember if there were any important changes since then? > The base for that one should be closer to "master", I think. There you go: http://thread.gmane.org/gmane.comp.version-control.git/186638/focus=186644 -- Ram ^ permalink raw reply [flat|nested] 64+ messages in thread
* [PATCH 5/6] revert: report fine-grained error messages from insn parser 2012-01-08 12:27 [PATCH 0/6] The move to sequencer.c Ramkumar Ramachandra ` (3 preceding siblings ...) 2012-01-08 12:27 ` [PATCH 4/6] revert: allow mixing "pick" and "revert" actions Ramkumar Ramachandra @ 2012-01-08 12:27 ` Ramkumar Ramachandra 2012-01-08 20:07 ` Jonathan Nieder 2012-01-08 12:27 ` [PATCH 6/6] sequencer: factor code out of revert builtin Ramkumar Ramachandra ` (2 subsequent siblings) 7 siblings, 1 reply; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-08 12:27 UTC (permalink / raw) To: Git List; +Cc: Junio C Hamano, Jonathan Nieder Three kinds of errors can arise from parsing '.git/sequencer/todo': 1. Unrecognized action 2. Malformed object name 3. Object name does not refer to a valid commit Since we would like to make the instruction sheet user-editable in the future (much like the 'rebase -i' sheet), report more fine-grained parse errors prefixed with the filename and line number. Helped-by: Junio C Hamano <gitster@pobox.com> Helped-by: Jonathan Nieder <jrnieder@gmail.com> Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com> --- builtin/revert.c | 45 +++++++++++++++++++++++++++------------------ 1 files changed, 27 insertions(+), 18 deletions(-) diff --git a/builtin/revert.c b/builtin/revert.c index 54ea394..35553e7 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -714,26 +714,29 @@ static int format_todo(struct strbuf *buf, struct replay_insn_list *todo_list) return 0; } -static int parse_insn_line(char *bol, char *eol, struct replay_insn_list *item) +static int parse_insn_line(char *bol, char *eol, + struct replay_insn_list *item, int lineno) { + const char *todo_file = git_path(SEQ_TODO_FILE); unsigned char commit_sha1[20]; char *end_of_object_name; - int saved, status, padding; + int saved, status; + size_t error_len; - if (!prefixcmp(bol, "pick")) { + if (!prefixcmp(bol, "pick ") || !prefixcmp(bol, "pick\t")) { item->action = REPLAY_PICK; - bol += strlen("pick"); - } else if (!prefixcmp(bol, "revert")) { + bol += strlen("pick "); + } else if (!prefixcmp(bol, "revert ") || !prefixcmp(bol, "revert\t")) { item->action = REPLAY_REVERT; - bol += strlen("revert"); - } else - return -1; + bol += strlen("revert "); + } else { + error_len = eol - bol > 255 ? 255 : eol - bol; + return error(_("%s:%d: Unrecognized action: %.*s"), + todo_file, lineno, (int)error_len, bol); + } /* Eat up extra spaces/ tabs before object name */ - padding = strspn(bol, " \t"); - if (!padding) - return -1; - bol += padding; + bol += strspn(bol, " \t"); end_of_object_name = bol + strcspn(bol, " \t\n"); saved = *end_of_object_name; @@ -741,12 +744,18 @@ static int parse_insn_line(char *bol, char *eol, struct replay_insn_list *item) status = get_sha1(bol, commit_sha1); *end_of_object_name = saved; - if (status < 0) - return -1; + if (status < 0) { + error_len = eol - bol > 255 ? 255 : eol - bol; + return error(_("%s:%d: Malformed object name: %.*s"), + todo_file, lineno, (int)error_len, bol); + } item->operand = lookup_commit_reference(commit_sha1); - if (!item->operand) - return -1; + if (!item->operand) { + error_len = eol - bol > 255 ? 255 : eol - bol; + return error(_("%s:%d: Not a valid commit: %.*s"), + todo_file, lineno, (int)error_len, bol); + } item->next = NULL; return 0; @@ -761,8 +770,8 @@ static int parse_insn_buffer(char *buf, struct replay_insn_list **todo_list) for (i = 1; *p; i++) { char *eol = strchrnul(p, '\n'); - if (parse_insn_line(p, eol, &item) < 0) - return error(_("Could not parse line %d."), i); + if (parse_insn_line(p, eol, &item, i) < 0) + return -1; next = replay_insn_list_append(item.operand, item.action, next); p = *eol ? eol + 1 : eol; } -- 1.7.8.2 ^ permalink raw reply related [flat|nested] 64+ messages in thread
* Re: [PATCH 5/6] revert: report fine-grained error messages from insn parser 2012-01-08 12:27 ` [PATCH 5/6] revert: report fine-grained error messages from insn parser Ramkumar Ramachandra @ 2012-01-08 20:07 ` Jonathan Nieder 2012-01-08 20:16 ` Ramkumar Ramachandra 0 siblings, 1 reply; 64+ messages in thread From: Jonathan Nieder @ 2012-01-08 20:07 UTC (permalink / raw) To: Ramkumar Ramachandra; +Cc: Git List, Junio C Hamano Ramkumar Ramachandra wrote: > Three kinds of errors can arise from parsing '.git/sequencer/todo': > 1. Unrecognized action > 2. Malformed object name > 3. Object name does not refer to a valid commit > > Since we would like to make the instruction sheet user-editable in the > future (much like the 'rebase -i' sheet), report more fine-grained > parse errors prefixed with the filename and line number. Seems like a sensible idea. In other words, the infrastructure that parses .git/sequencer/todo is meant to handle arbitrary user input some day, so it can be used as the implementation of "git rebase --interactive" and "git sequence --edit", and currently it is suboptimal for that purpose because the parse error messages just say error: Could not parse line 5. This patch shifts responsibility to parse_insn_line(), which can come up with a more detailed message like error: .git/sequencer/todo:5: unrecognized action "frobnicate" Once the operator is allowed to edit the sequence, the message might be adjusted to something meaning error: <sequence you just gave me>:5: unrecognized action "frobnicate" instead of exposing an implementation detail, or maybe some day "git sequence --edit" could even re-launch the editor with an error message in a comment before the problematic line and the cursor pointing there. And for now, pointing to the explicit filename is useful since this should only happen if there was filesystem corruption, tampering, or a git bug. By the way, an example of the output before and after the patch would have been useful in saving some trouble of figuring this out. Ok, onward to the patch. [...] > --- a/builtin/revert.c > +++ b/builtin/revert.c > @@ -714,26 +714,29 @@ static int format_todo(struct strbuf *buf, struct replay_insn_list *todo_list) > return 0; > } > > -static int parse_insn_line(char *bol, char *eol, struct replay_insn_list *item) > +static int parse_insn_line(char *bol, char *eol, > + struct replay_insn_list *item, int lineno) > { > + const char *todo_file = git_path(SEQ_TODO_FILE); This idiom is _still_ scary. I don't want to have to shout about this, but why didn't the commit message at least acknowledge it to put the reader's mind at ease when this has come up again and again? [...] > + bol += strlen("revert "); > + } else { > + error_len = eol - bol > 255 ? 255 : eol - bol; > + return error(_("%s:%d: Unrecognized action: %.*s"), > + todo_file, lineno, (int)error_len, bol); Ah, so my example above was wrong: the actual message is error: .git/sequencer/todo:5: Unrecognized action: the quick bro wn fox jumps over the lazy dog I guess that's fine. Is it intended? > + } > > /* Eat up extra spaces/ tabs before object name */ > - padding = strspn(bol, " \t"); > - if (!padding) > - return -1; > - bol += padding; > + bol += strspn(bol, " \t"); What does this have to do with the stated goal of the patch? It seems to me that an unrelated and unadvertised bugfix snuck in. [...] > @@ -741,12 +744,18 @@ static int parse_insn_line(char *bol, char *eol, struct replay_insn_list *item) > status = get_sha1(bol, commit_sha1); > *end_of_object_name = saved; > > - if (status < 0) > - return -1; > + if (status < 0) { > + error_len = eol - bol > 255 ? 255 : eol - bol; > + return error(_("%s:%d: Malformed object name: %.*s"), > + todo_file, lineno, (int)error_len, bol); > + } This seems a little repetitive --- maybe it would make sense to factor out a function to emit errors of the form error: file:lineno: message: line By the way, this is gross. Probably get_sha1 should grow a variant that takes a buffer and a length. [...] > item->operand = lookup_commit_reference(commit_sha1); > - if (!item->operand) > - return -1; > + if (!item->operand) { > + error_len = eol - bol > 255 ? 255 : eol - bol; > + return error(_("%s:%d: Not a valid commit: %.*s"), > + todo_file, lineno, (int)error_len, bol); > + } Hmm, this one can be emitted even when there was no corruption or internal error, because the user removed a commit she was cherry-picking and the gc-ed before a "git cherry-pick --continue". Alternatively, it can happen because the repository has grown very crowded and what used to be an unambiguous commit name no longer is one (not enough digits). Will the error message be intuitive in these situations? [...] > @@ -761,8 +770,8 @@ static int parse_insn_buffer(char *buf, struct replay_insn_list **todo_list) > > for (i = 1; *p; i++) { > char *eol = strchrnul(p, '\n'); > - if (parse_insn_line(p, eol, &item) < 0) > - return error(_("Could not parse line %d."), i); > + if (parse_insn_line(p, eol, &item, i) < 0) > + return -1; Not related to this patch, but why the "< 0" test? It makes this reader worry that there is some unusual return value convention that he should be taking into account. To sum up: the idea looks promising, but this patch doesn't seem to be ready yet. Hope that helps, Jonathan ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 5/6] revert: report fine-grained error messages from insn parser 2012-01-08 20:07 ` Jonathan Nieder @ 2012-01-08 20:16 ` Ramkumar Ramachandra 2012-01-08 21:33 ` Jonathan Nieder 0 siblings, 1 reply; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-08 20:16 UTC (permalink / raw) To: Jonathan Nieder; +Cc: Git List, Junio C Hamano Hi, Jonathan Nieder wrote: > Ramkumar Ramachandra wrote: > >> Three kinds of errors can arise from parsing '.git/sequencer/todo': >> 1. Unrecognized action >> 2. Malformed object name >> 3. Object name does not refer to a valid commit >> >> Since we would like to make the instruction sheet user-editable in the >> future (much like the 'rebase -i' sheet), report more fine-grained >> parse errors prefixed with the filename and line number. > > Seems like a sensible idea. In other words, > [...] Thanks for the new wording. > [...] >> --- a/builtin/revert.c >> +++ b/builtin/revert.c >> @@ -714,26 +714,29 @@ static int format_todo(struct strbuf *buf, struct replay_insn_list *todo_list) >> return 0; >> } >> >> -static int parse_insn_line(char *bol, char *eol, struct replay_insn_list *item) >> +static int parse_insn_line(char *bol, char *eol, >> + struct replay_insn_list *item, int lineno) >> { >> + const char *todo_file = git_path(SEQ_TODO_FILE); > > This idiom is _still_ scary. I don't want to have to shout about > this, but why didn't the commit message at least acknowledge it to put > the reader's mind at ease when this has come up again and again? Carried over from the previous re-roll; sorry I didn't pay enough attention. > [...] >> + bol += strlen("revert "); >> + } else { >> + error_len = eol - bol > 255 ? 255 : eol - bol; >> + return error(_("%s:%d: Unrecognized action: %.*s"), >> + todo_file, lineno, (int)error_len, bol); > > Ah, so my example above was wrong: the actual message is > > error: .git/sequencer/todo:5: Unrecognized action: the quick bro > wn fox jumps over the lazy dog > > I guess that's fine. Is it intended? > >> + } >> >> /* Eat up extra spaces/ tabs before object name */ >> - padding = strspn(bol, " \t"); >> - if (!padding) >> - return -1; >> - bol += padding; >> + bol += strspn(bol, " \t"); > > What does this have to do with the stated goal of the patch? It seems > to me that an unrelated and unadvertised bugfix snuck in. Not a bugfix: since I have to report sensible error messages now, I changed the "pick" and "revert" checks to "pick " || "pick\t" and "revert " || "revert\t" -- then, I can report "invalid action" if it doesn't match instead of a useless "missing space after action". > [...] > By the way, this is gross. Probably get_sha1 should grow a variant > that takes a buffer and a length. Yes; will do. > [...] >> item->operand = lookup_commit_reference(commit_sha1); >> - if (!item->operand) >> - return -1; >> + if (!item->operand) { >> + error_len = eol - bol > 255 ? 255 : eol - bol; >> + return error(_("%s:%d: Not a valid commit: %.*s"), >> + todo_file, lineno, (int)error_len, bol); >> + } > > Hmm, this one can be emitted even when there was no corruption or > internal error, because the user removed a commit she was > cherry-picking and the gc-ed before a "git cherry-pick --continue". > Alternatively, it can happen because the repository has grown very > crowded and what used to be an unambiguous commit name no longer is > one (not enough digits). Will the error message be intuitive in these > situations? Something like "Unable to look up commit: %s" perhaps? > [...] >> @@ -761,8 +770,8 @@ static int parse_insn_buffer(char *buf, struct replay_insn_list **todo_list) >> >> for (i = 1; *p; i++) { >> char *eol = strchrnul(p, '\n'); >> - if (parse_insn_line(p, eol, &item) < 0) >> - return error(_("Could not parse line %d."), i); >> + if (parse_insn_line(p, eol, &item, i) < 0) >> + return -1; > > Not related to this patch, but why the "< 0" test? It makes this > reader worry that there is some unusual return value convention that > he should be taking into account. Right. Will fix. Thanks. -- Ram ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 5/6] revert: report fine-grained error messages from insn parser 2012-01-08 20:16 ` Ramkumar Ramachandra @ 2012-01-08 21:33 ` Jonathan Nieder 2012-01-10 15:24 ` Ramkumar Ramachandra 0 siblings, 1 reply; 64+ messages in thread From: Jonathan Nieder @ 2012-01-08 21:33 UTC (permalink / raw) To: Ramkumar Ramachandra; +Cc: Git List, Junio C Hamano Ramkumar Ramachandra wrote: > Jonathan Nieder wrote: >> Ramkumar Ramachandra wrote: >>> /* Eat up extra spaces/ tabs before object name */ >>> - padding = strspn(bol, " \t"); >>> - if (!padding) >>> - return -1; >>> - bol += padding; >>> + bol += strspn(bol, " \t"); [...] > Not a bugfix: since I have to report sensible error messages now, I > changed the "pick" and "revert" checks to "pick " || "pick\t" and > "revert " || "revert\t" -- then, I can report "invalid action" if it > doesn't match instead of a useless "missing space after action". Ah, I forgot that the "if (!padding)" check noticed errors like picking foo before. So this is just a code cleanup, with no functional effect. However, you can still report "invalid action" with the old code structure --- it would just mean duplicating an error message in the code, since you reach the same conclusion by two code paths. So it's a relevant cleanup, but I'd still suggest lifting it into a patch that comes before so future readers can assure themselves that it introduces no functional change instead of being confused. [...] >>> + return error(_("%s:%d: Not a valid commit: %.*s"), >>> + todo_file, lineno, (int)error_len, bol); >>> + } >> >> Hmm, this one can be emitted even when there was no corruption or >> internal error, because the user removed a commit she was >> cherry-picking and the gc-ed before a "git cherry-pick --continue". >> Alternatively, it can happen because the repository has grown very >> crowded and what used to be an unambiguous commit name no longer is >> one (not enough digits). Will the error message be intuitive in these >> situations? > > Something like "Unable to look up commit: %s" perhaps? My "alternatively" was bogus --- lookup_commit_reference takes a (raw) full commit name as its argument. I dunno. The question was not actually rhetorical --- I just meant that it's worth thinking about these cases. There are a few cases: - missing object - object is present but corrupt - object is a blob, not a commit In the second case, there's an error message printed describing the problem, but in the other two there isn't. The other callers tend to say "not a valid <foo>" or "could not lookup commit <foo>, so I guess error: .git/sequencer/todo:5: not a valid commit: 78a89f493 would be fine. Except that this focusses on the .git/sequencer/todo filename which would leave the person debugging astray. It is not that .git/sequencer/todo contains a typo (that would have been caught by get_sha1), but that it referred to a bad object or non-commit. Maybe something in the direction of error: cannot pick 78a89f493 because it is not a valid commit would be more helpful. Is this the right moment to report that error? Will the operator be happy that we errored out right away before cherry-picking anything and wasting the human's time in assisting with that process, or will she be unhappy that inability to do something later that she might have been planning on skipping anyway prevented making progress right away? (I'm not sure what the best thing to do is --- I guess some advice like hint: to abort, use cherry-pick --abort hint: to skip or replace that commit, use cherry-pick --edit would help.) Thanks for some food for thought. Jonathan ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 5/6] revert: report fine-grained error messages from insn parser 2012-01-08 21:33 ` Jonathan Nieder @ 2012-01-10 15:24 ` Ramkumar Ramachandra 0 siblings, 0 replies; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-10 15:24 UTC (permalink / raw) To: Jonathan Nieder; +Cc: Git List, Junio C Hamano Jonathan Nieder wrote: > My "alternatively" was bogus --- lookup_commit_reference takes a (raw) > full commit name as its argument. > > I dunno. The question was not actually rhetorical --- I just meant > that it's worth thinking about these cases. There are a few cases: > > - missing object > - object is present but corrupt > - object is a blob, not a commit > > In the second case, there's an error message printed describing the > problem, but in the other two there isn't. The other callers tend to > say "not a valid <foo>" or "could not lookup commit <foo>, so I guess > > error: .git/sequencer/todo:5: not a valid commit: 78a89f493 > > would be fine. > > Except that this focusses on the .git/sequencer/todo filename which > would leave the person debugging astray. It is not that > .git/sequencer/todo contains a typo (that would have been caught by > get_sha1), but that it referred to a bad object or non-commit. Maybe > something in the direction of > > error: cannot pick 78a89f493 because it is not a valid commit > > would be more helpful. > > Is this the right moment to report that error? Will the operator be > happy that we errored out right away before cherry-picking anything > and wasting the human's time in assisting with that process, or will > she be unhappy that inability to do something later that she might > have been planning on skipping anyway prevented making progress right > away? (I'm not sure what the best thing to do is --- I guess some > advice like > > hint: to abort, use cherry-pick --abort > hint: to skip or replace that commit, use cherry-pick --edit > > would help.) Ignoring this in the re-roll: I'd be inclined to put these changes in a separate series that begins with bolting on some advice configuration to the sequencer. Thanks for thinking about these things. -- Ram ^ permalink raw reply [flat|nested] 64+ messages in thread
* [PATCH 6/6] sequencer: factor code out of revert builtin 2012-01-08 12:27 [PATCH 0/6] The move to sequencer.c Ramkumar Ramachandra ` (4 preceding siblings ...) 2012-01-08 12:27 ` [PATCH 5/6] revert: report fine-grained error messages from insn parser Ramkumar Ramachandra @ 2012-01-08 12:27 ` Ramkumar Ramachandra 2012-01-08 20:38 ` Jonathan Nieder 2012-01-08 19:28 ` [PATCH 0/6] The move to sequencer.c Jonathan Nieder 2012-01-10 16:13 ` [PATCH v2 0/8] " Ramkumar Ramachandra 7 siblings, 1 reply; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-08 12:27 UTC (permalink / raw) To: Git List; +Cc: Junio C Hamano, Jonathan Nieder Expose the cherry-picking machinery through a public sequencer_pick_revisions() (renamed from pick_revisions() in builtin/revert.c), so that cherry-picking and reverting are special cases of a general sequencer operation. The cherry-pick builtin is now a thin wrapper that does command-line argument parsing before calling into sequencer_pick_revisions(). In the future, we can write a new "foo" builtin that calls into the sequencer like: memset(&opts, 0, sizeof(opts)); opts.command = REPLAY_CMD_FOO; opts.revisions = xmalloc(sizeof(*opts.revs)); parse_args_populate_opts(argc, argv, &opts); init_revisions(opts.revs); sequencer_pick_revisions(&opts); This patch does not intend to make any functional changes. Check with: $ git blame -s -C HEAD^..HEAD -- sequencer.c | grep -C3 '^[^^]' Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com> --- builtin/revert.c | 1006 +----------------------------------------------------- sequencer.c | 987 ++++++++++++++++++++++++++++++++++++++++++++++++++++- sequencer.h | 25 ++ 3 files changed, 1013 insertions(+), 1005 deletions(-) diff --git a/builtin/revert.c b/builtin/revert.c index 35553e7..47f71f3 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -1,19 +1,9 @@ #include "cache.h" #include "builtin.h" -#include "object.h" -#include "commit.h" -#include "tag.h" -#include "run-command.h" -#include "exec_cmd.h" -#include "utf8.h" #include "parse-options.h" -#include "cache-tree.h" #include "diff.h" #include "revision.h" #include "rerere.h" -#include "merge-recursive.h" -#include "refs.h" -#include "dir.h" #include "sequencer.h" /* @@ -39,43 +29,11 @@ static const char * const cherry_pick_usage[] = { NULL }; -struct replay_opts { - enum replay_command command; - 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; - - /* Merge strategy */ - const char *strategy; - const char **xopts; - size_t xopts_nr, xopts_alloc; - - /* Only used by REPLAY_NONE */ - struct rev_info *revs; -}; - -#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" - static const char *command_name(struct replay_opts *opts) { return opts->command == REPLAY_CMD_REVERT ? "revert" : "cherry-pick"; } -static const char *action_name(enum replay_action action) -{ - return action == REPLAY_REVERT ? "revert" : "pick"; -} - -static char *get_encoding(const char *message); - static const char * const *revert_or_cherry_pick_usage(struct replay_opts *opts) { return opts->command == REPLAY_CMD_REVERT ? revert_usage : cherry_pick_usage; @@ -234,966 +192,6 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) usage_with_options(usage_str, options); } -struct commit_message { - char *parent_label; - const char *label; - const char *subject; - char *reencoded_message; - const char *message; -}; - -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 (!commit->buffer) - return -1; - 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 = commit->buffer; - if (strcmp(encoding, git_commit_encoding)) - out->reencoded_message = reencode_string(commit->buffer, - 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); - - subject_len = find_commit_subject(out->message, &subject); - - out->parent_label = xmalloc(strlen("parent of ") + abbrev_len + - strlen("... ") + subject_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, subject, subject_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) -{ - const char *p = message, *eol; - - while (*p && *p != '\n') { - for (eol = p + 1; *eol && *eol != '\n'; eol++) - ; /* do nothing */ - if (!prefixcmp(p, "encoding ")) { - char *result = xmalloc(eol - 8 - p); - strlcpy(result, p + 9, eol - 8 - p); - return result; - } - p = eol; - if (*p == '\n') - p++; - } - return NULL; -} - -static void write_cherry_pick_head(struct commit *commit, const char *pseudoref) -{ - const char *filename; - int fd; - struct strbuf buf = STRBUF_INIT; - - strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1)); - - filename = git_path("%s", pseudoref); - fd = open(filename, O_WRONLY | O_CREAT, 0666); - if (fd < 0) - die_errno(_("Could not open '%s' for writing"), filename); - if (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd)) - die_errno(_("Could not write to '%s'"), filename); - strbuf_release(&buf); -} - -static void print_advice(int show_hint) -{ - char *msg = getenv("GIT_CHERRY_PICK_HELP"); - - if (msg) { - fprintf(stderr, "%s\n", msg); - /* - * A conflict has occured but the porcelain - * (typically rebase --interactive) wants to take care - * of the commit itself so remove CHERRY_PICK_HEAD - */ - unlink(git_path("CHERRY_PICK_HEAD")); - return; - } - - if (show_hint) { - advise("after resolving the conflicts, mark the corrected paths"); - advise("with 'git add <paths>' or 'git rm <paths>'"); - advise("and commit the result with 'git commit'"); - } -} - -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) -{ - return lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN); -} - -static int error_dirty_index(struct replay_opts *opts) -{ - if (read_cache_unmerged()) - return error_resolve_conflict(command_name(opts)); - - /* Different translation strings for cherry-pick and revert */ - if (opts->command == REPLAY_CMD_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) -{ - 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 int do_recursive_merge(struct commit *base, struct commit *next, - const char *base_label, const char *next_label, - unsigned char *head, struct strbuf *msgbuf, - struct replay_opts *opts) -{ - struct merge_options o; - struct tree *result, *next_tree, *base_tree, *head_tree; - int clean, index_fd; - const char **xopt; - 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(); - - for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++) - parse_merge_opt(&o, *xopt); - - 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))) - /* TRANSLATORS: %s will be "revert" or "cherry-pick" */ - die(_("%s: Unable to write new index file"), command_name(opts)); - 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++; - } - } - } - - return !clean; -} - -/* - * If we are cherry-pick, and if the merge did not result in - * hand-editing, we will hit this commit and inherit the original - * author date and name. - * If we are revert, or if our cherry-pick results in a hand merge, - * we had better say that the current user is responsible for that. - */ -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]; - int i = 0; - - args[i++] = "commit"; - args[i++] = "-n"; - if (opts->signoff) - args[i++] = "-s"; - if (!opts->edit) { - args[i++] = "-F"; - args[i++] = defmsg; - } - args[i] = NULL; - - return run_command_v_opt(args, RUN_GIT_CMD); -} - -static int do_pick_commit(struct commit *commit, enum replay_action action, - struct replay_opts *opts) -{ - unsigned char head[20]; - struct commit *base, *next, *parent; - const char *base_label, *next_label; - struct commit_message msg = { NULL, NULL, NULL, NULL, NULL }; - char *defmsg = NULL; - struct strbuf msgbuf = STRBUF_INIT; - int res; - - 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 - * that represents the "current" state for merge-recursive - * to work on. - */ - if (write_cache_as_tree(head, 0, NULL)) - die (_("Your index file is unmerged.")); - } else { - if (get_sha1("HEAD", head)) - return error(_("You do not have a valid HEAD")); - if (index_differs_from("HEAD", 0)) - return error_dirty_index(opts); - } - discard_cache(); - - if (!commit->parents) { - parent = NULL; - } - else if (commit->parents->next) { - /* Reverting or cherry-picking a merge commit */ - int cnt; - struct commit_list *p; - - 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 != opts->mainline && p; - cnt++) - p = p->next; - 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 < 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 (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 */ - return error(_("%s: cannot parse parent commit %s"), - command_name(opts), sha1_to_hex(parent->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 - * the difference it introduces since its first parent "prev" - * on top of the current HEAD if we are cherry-pick. Or the - * reverse of it if we are revert. - */ - - defmsg = git_pathdup("MERGE_MSG"); - - if (action == REPLAY_REVERT) { - base = commit; - base_label = msg.label; - next = parent; - 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 && commit->parents->next) { - strbuf_addstr(&msgbuf, ", reversing\nchanges made to "); - strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1)); - } - strbuf_addstr(&msgbuf, ".\n"); - } else { - const char *p; - - base = parent; - base_label = msg.parent_label; - next = commit; - next_label = msg.label; - - /* - * 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 (!opts->strategy || !strcmp(opts->strategy, "recursive") || action == REPLAY_REVERT) { - res = do_recursive_merge(base, next, base_label, next_label, - head, &msgbuf, opts); - write_message(&msgbuf, defmsg); - } else { - 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(opts->strategy, opts->xopts_nr, opts->xopts, - common, sha1_to_hex(head), remotes); - free_commit_list(common); - free_commit_list(remotes); - } - - /* - * If the merge was clean or if it failed due to conflict, we write - * CHERRY_PICK_HEAD for the subsequent invocation of commit to use. - * However, if the merge did not even start, then we don't want to - * write it at all. - */ - if (action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1)) - write_cherry_pick_head(commit, "CHERRY_PICK_HEAD"); - if (action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1)) - write_cherry_pick_head(commit, "REVERT_HEAD"); - - if (res) { - error(action == REPLAY_REVERT - ? _("could not revert %s... %s") - : _("could not apply %s... %s"), - find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), - msg.subject); - print_advice(res == 1); - rerere(opts->allow_rerere_auto); - } else { - if (!opts->no_commit) - res = run_git_commit(defmsg, opts); - } - - free_message(&msg); - free(defmsg); - - return res; -} - -static void prepare_revs(struct replay_opts *opts) -{ - if (opts->command != REPLAY_CMD_REVERT) - opts->revs->reverse ^= 1; - - if (prepare_revision_walk(opts->revs)) - die(_("revision walk setup failed")); - - if (!opts->revs->commits) - die(_("empty commit set passed")); -} - -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"), command_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"), - command_name(opts)); - } - rollback_lock_file(&index_lock); -} - -/* - * Append a (commit, action) to the end of the replay_insn_list. - * - * next starts by pointing to the variable that holds the head of an - * empty replay_insn_list, and is updated to point to the "next" field of - * the last item on the list as new (commit, action) pairs are appended. - * - * Usage example: - * - * struct replay_insn_list *list; - * struct replay_insn_list **next = &list; - * - * next = replay_insn_list_append(c1, a1, next); - * next = replay_insn_list_append(c2, a2, next); - * assert(len(list) == 2); - * return list; - */ -static struct replay_insn_list **replay_insn_list_append(struct commit *operand, - enum replay_action action, - struct replay_insn_list **next) -{ - struct replay_insn_list *new = xmalloc(sizeof(*new)); - new->action = action; - new->operand = operand; - *next = new; - new->next = NULL; - return &new->next; -} - -static int format_todo(struct strbuf *buf, struct replay_insn_list *todo_list) -{ - struct replay_insn_list *cur; - - for (cur = todo_list; cur; cur = cur->next) { - const char *sha1_abbrev, *action_str, *subject; - int subject_len; - - action_str = action_name(cur->action); - sha1_abbrev = find_unique_abbrev(cur->operand->object.sha1, DEFAULT_ABBREV); - subject_len = find_commit_subject(cur->operand->buffer, &subject); - strbuf_addf(buf, "%s %s %.*s\n", action_str, sha1_abbrev, - subject_len, subject); - } - return 0; -} - -static int parse_insn_line(char *bol, char *eol, - struct replay_insn_list *item, int lineno) -{ - const char *todo_file = git_path(SEQ_TODO_FILE); - unsigned char commit_sha1[20]; - char *end_of_object_name; - int saved, status; - size_t error_len; - - if (!prefixcmp(bol, "pick ") || !prefixcmp(bol, "pick\t")) { - item->action = REPLAY_PICK; - bol += strlen("pick "); - } else if (!prefixcmp(bol, "revert ") || !prefixcmp(bol, "revert\t")) { - item->action = REPLAY_REVERT; - bol += strlen("revert "); - } else { - error_len = eol - bol > 255 ? 255 : eol - bol; - return error(_("%s:%d: Unrecognized action: %.*s"), - todo_file, lineno, (int)error_len, bol); - } - - /* Eat up extra spaces/ tabs before object name */ - bol += strspn(bol, " \t"); - - end_of_object_name = bol + strcspn(bol, " \t\n"); - saved = *end_of_object_name; - *end_of_object_name = '\0'; - status = get_sha1(bol, commit_sha1); - *end_of_object_name = saved; - - if (status < 0) { - error_len = eol - bol > 255 ? 255 : eol - bol; - return error(_("%s:%d: Malformed object name: %.*s"), - todo_file, lineno, (int)error_len, bol); - } - - item->operand = lookup_commit_reference(commit_sha1); - if (!item->operand) { - error_len = eol - bol > 255 ? 255 : eol - bol; - return error(_("%s:%d: Not a valid commit: %.*s"), - todo_file, lineno, (int)error_len, bol); - } - - item->next = NULL; - return 0; -} - -static int parse_insn_buffer(char *buf, struct replay_insn_list **todo_list) -{ - struct replay_insn_list **next = todo_list; - struct replay_insn_list item = { NULL, 0, NULL }; - char *p = buf; - int i; - - for (i = 1; *p; i++) { - char *eol = strchrnul(p, '\n'); - if (parse_insn_line(p, eol, &item, i) < 0) - return -1; - next = replay_insn_list_append(item.operand, item.action, next); - p = *eol ? eol + 1 : eol; - } - if (!*todo_list) - return error(_("No commits parsed.")); - return 0; -} - -static void read_populate_todo(struct replay_insn_list **todo_list) -{ - 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); - 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 replay_insn_list **todo_list, - struct replay_opts *opts) -{ - struct commit *commit; - struct replay_insn_list **next; - enum replay_action action; - - prepare_revs(opts); - - next = todo_list; - action = opts->command == REPLAY_CMD_REVERT ? REPLAY_REVERT : REPLAY_PICK; - while ((commit = get_revision(opts->revs))) - next = replay_insn_list_append(commit, action, next); -} - -static int create_seq_dir(void) -{ - const char *seq_dir = git_path(SEQ_DIR); - - if (file_exists(seq_dir)) { - error(_("a cherry-pick or revert is already in progress")); - advise(_("try \"git cherry-pick (--continue | --quit | --abort)\"")); - return -1; - } - else if (mkdir(seq_dir, 0777) < 0) - die_errno(_("Could not create sequencer directory %s"), seq_dir); - return 0; -} - -static enum replay_command read_cmd(void) -{ - const char *cmd_file = git_path(SEQ_CMD_FILE); - struct strbuf buf = STRBUF_INIT; - enum replay_command res; - int fd; - - fd = open(cmd_file, O_RDONLY); - if (fd < 0) - die_errno(_("Could not open %s"), cmd_file); - if (strbuf_read(&buf, fd, 0) < 0) { - close(fd); - strbuf_release(&buf); - die(_("Could not read %s."), cmd_file); - } - close(fd); - - if (!strcmp(buf.buf, "revert\n")) - res = REPLAY_CMD_REVERT; - else if (!strcmp(buf.buf, "cherry-pick\n")) - res = REPLAY_CMD_CHERRY_PICK; - else { - strbuf_release(&buf); - die(_("Malformed command file: %s"), cmd_file); - } - strbuf_release(&buf); - return res; -} - -static void save_cmd(struct replay_opts *opts) -{ - const char *cmd_file = git_path(SEQ_CMD_FILE); - static struct lock_file cmd_lock; - struct strbuf buf = STRBUF_INIT; - int fd; - - fd = hold_lock_file_for_update(&cmd_lock, cmd_file, LOCK_DIE_ON_ERROR); - strbuf_addf(&buf, "%s\n", command_name(opts)); - if (write_in_full(fd, buf.buf, buf.len) < 0) - die_errno(_("Could not write to %s"), cmd_file); - if (commit_lock_file(&cmd_lock) < 0) - die(_("Error wrapping up %s."), cmd_file); -} - -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 int reset_for_rollback(const unsigned char *sha1) -{ - const char *argv[4]; /* reset --merge <arg> + NULL */ - argv[0] = "reset"; - argv[1] = "--merge"; - argv[2] = sha1_to_hex(sha1); - argv[3] = NULL; - return run_command_v_opt(argv, RUN_GIT_CMD); -} - -static int rollback_single_pick(void) -{ - unsigned char head_sha1[20]; - - if (!file_exists(git_path("CHERRY_PICK_HEAD")) && - !file_exists(git_path("REVERT_HEAD"))) - return error(_("no cherry-pick or revert in progress")); - if (read_ref_full("HEAD", head_sha1, 0, NULL)) - return error(_("cannot resolve HEAD")); - if (is_null_sha1(head_sha1)) - return error(_("cannot abort from a branch yet to be born")); - return reset_for_rollback(head_sha1); -} - -static int sequencer_rollback(struct replay_opts *opts) -{ - const char *filename; - FILE *f; - unsigned char sha1[20]; - struct strbuf buf = STRBUF_INIT; - - filename = git_path(SEQ_HEAD_FILE); - f = fopen(filename, "r"); - if (!f && errno == ENOENT) { - /* - * There is no multiple-cherry-pick in progress. - * If CHERRY_PICK_HEAD or REVERT_HEAD indicates - * a single-cherry-pick in progress, abort that. - */ - return rollback_single_pick(); - } - if (!f) - return error(_("cannot open %s: %s"), filename, - strerror(errno)); - if (strbuf_getline(&buf, f, '\n')) { - error(_("cannot read %s: %s"), filename, ferror(f) ? - strerror(errno) : _("unexpected end of file")); - fclose(f); - goto fail; - } - fclose(f); - if (get_sha1_hex(buf.buf, sha1) || buf.buf[40] != '\0') { - error(_("stored pre-cherry-pick HEAD file '%s' is corrupt"), - filename); - goto fail; - } - if (reset_for_rollback(sha1)) - goto fail; - remove_sequencer_state(); - strbuf_release(&buf); - return 0; -fail: - strbuf_release(&buf); - return -1; -} - -static void save_todo(struct replay_insn_list *todo_list) -{ - 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) < 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 replay_insn_list *todo_list, struct replay_opts *opts) -{ - struct replay_insn_list *cur; - int res; - - setenv(GIT_REFLOG_ACTION, command_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); - res = do_pick_commit(cur->operand, cur->action, opts); - if (res) - return res; - } - - /* - * Sequence of picks finished successfully; cleanup by - * removing the .git/sequencer directory - */ - remove_sequencer_state(); - return 0; -} - -static int continue_single_pick(void) -{ - const char *argv[] = { "commit", NULL }; - - if (!file_exists(git_path("CHERRY_PICK_HEAD")) && - !file_exists(git_path("REVERT_HEAD"))) - return error(_("no cherry-pick or revert in progress")); - return run_command_v_opt(argv, RUN_GIT_CMD); -} - -static int sequencer_continue(struct replay_opts *opts) -{ - struct replay_insn_list *todo_list = NULL; - enum replay_command cmd; - - if (!file_exists(git_path(SEQ_TODO_FILE))) - return continue_single_pick(); - - /* - * Disallow continuing a cherry-pick with 'git revert - * --continue' and viceversa - */ - cmd = read_cmd(); - if (cmd != opts->command) - return error(_("cannot %s: a %s is in progress."), - command_name(opts), - cmd == REPLAY_CMD_REVERT ? "revert" : "cherry-pick"); - - read_populate_opts(&opts); - read_populate_todo(&todo_list); - - /* Verify that the conflict has been resolved */ - if (file_exists(git_path("CHERRY_PICK_HEAD")) || - file_exists(git_path("REVERT_HEAD"))) { - int ret = continue_single_pick(); - if (ret) - return ret; - } - if (index_differs_from("HEAD", 0)) - return error_dirty_index(opts); - todo_list = todo_list->next; - return pick_commits(todo_list, opts); -} - -static int single_pick(struct commit *cmit, struct replay_opts *opts) -{ - enum replay_action action; - action = opts->command == REPLAY_CMD_REVERT ? REPLAY_REVERT : REPLAY_PICK; - - setenv(GIT_REFLOG_ACTION, command_name(opts), 0); - return do_pick_commit(cmit, action, opts); -} - -static int pick_revisions(struct replay_opts *opts) -{ - struct replay_insn_list *todo_list = NULL; - unsigned char sha1[20]; - - if (opts->subcommand == REPLAY_NONE) - assert(opts->revs); - - 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_REMOVE_STATE) { - remove_sequencer_state(); - return 0; - } - if (opts->subcommand == REPLAY_ROLLBACK) - return sequencer_rollback(opts); - if (opts->subcommand == REPLAY_CONTINUE) - return sequencer_continue(opts); - - /* - * If we were called as "git cherry-pick <commit>", just - * cherry-pick/revert it, set CHERRY_PICK_HEAD / - * REVERT_HEAD, and don't touch the sequencer state. - * This means it is possible to cherry-pick in the middle - * of a cherry-pick sequence. - */ - if (opts->revs->cmdline.nr == 1 && - opts->revs->cmdline.rev->whence == REV_CMD_REV && - opts->revs->no_walk && - !opts->revs->cmdline.rev->flags) { - struct commit *cmit; - if (prepare_revision_walk(opts->revs)) - die(_("revision walk setup failed")); - cmit = get_revision(opts->revs); - if (!cmit || get_revision(opts->revs)) - die("BUG: expected exactly one commit from walk"); - return single_pick(cmit, opts); - } - - /* - * 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) - return -1; - if (get_sha1("HEAD", sha1)) { - if (opts->command == REPLAY_CMD_REVERT) - return error(_("Can't revert as initial commit")); - return error(_("Can't cherry-pick into empty head")); - } - save_cmd(opts); - save_head(sha1_to_hex(sha1)); - save_opts(opts); - return pick_commits(todo_list, opts); -} - int cmd_revert(int argc, const char **argv, const char *prefix) { struct replay_opts opts; @@ -1205,7 +203,7 @@ int cmd_revert(int argc, const char **argv, const char *prefix) opts.command = REPLAY_CMD_REVERT; git_config(git_default_config, NULL); parse_args(argc, argv, &opts); - res = pick_revisions(&opts); + res = sequencer_pick_revisions(&opts); if (res < 0) die(_("revert failed")); return res; @@ -1220,7 +218,7 @@ int cmd_cherry_pick(int argc, const char **argv, const char *prefix) opts.command = REPLAY_CMD_CHERRY_PICK; git_config(git_default_config, NULL); parse_args(argc, argv, &opts); - res = pick_revisions(&opts); + res = sequencer_pick_revisions(&opts); if (res < 0) die(_("cherry-pick failed")); return res; diff --git a/sequencer.c b/sequencer.c index d1f28a6..a7e494c 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1,7 +1,20 @@ #include "cache.h" #include "sequencer.h" -#include "strbuf.h" #include "dir.h" +#include "object.h" +#include "commit.h" +#include "tag.h" +#include "run-command.h" +#include "exec_cmd.h" +#include "utf8.h" +#include "cache-tree.h" +#include "diff.h" +#include "revision.h" +#include "rerere.h" +#include "merge-recursive.h" +#include "refs.h" + +#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" void remove_sequencer_state(void) { @@ -11,3 +24,975 @@ void remove_sequencer_state(void) remove_dir_recursively(&seq_dir, 0); strbuf_release(&seq_dir); } + +static const char *command_name(struct replay_opts *opts) +{ + return opts->command == REPLAY_CMD_REVERT ? "revert" : "cherry-pick"; +} + +static const char *action_name(enum replay_action action) +{ + return action == REPLAY_REVERT ? "revert" : "pick"; +} + +struct commit_message { + char *parent_label; + const char *label; + const char *subject; + char *reencoded_message; + const char *message; +}; + +static char *get_encoding(const char *message); + +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 (!commit->buffer) + return -1; + 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 = commit->buffer; + if (strcmp(encoding, git_commit_encoding)) + out->reencoded_message = reencode_string(commit->buffer, + 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); + + subject_len = find_commit_subject(out->message, &subject); + + out->parent_label = xmalloc(strlen("parent of ") + abbrev_len + + strlen("... ") + subject_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, subject, subject_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) +{ + const char *p = message, *eol; + + while (*p && *p != '\n') { + for (eol = p + 1; *eol && *eol != '\n'; eol++) + ; /* do nothing */ + if (!prefixcmp(p, "encoding ")) { + char *result = xmalloc(eol - 8 - p); + strlcpy(result, p + 9, eol - 8 - p); + return result; + } + p = eol; + if (*p == '\n') + p++; + } + return NULL; +} + +static void write_cherry_pick_head(struct commit *commit, const char *pseudoref) +{ + const char *filename; + int fd; + struct strbuf buf = STRBUF_INIT; + + strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1)); + + filename = git_path("%s", pseudoref); + fd = open(filename, O_WRONLY | O_CREAT, 0666); + if (fd < 0) + die_errno(_("Could not open '%s' for writing"), filename); + if (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd)) + die_errno(_("Could not write to '%s'"), filename); + strbuf_release(&buf); +} + +static void print_advice(int show_hint) +{ + char *msg = getenv("GIT_CHERRY_PICK_HELP"); + + if (msg) { + fprintf(stderr, "%s\n", msg); + /* + * A conflict has occured but the porcelain + * (typically rebase --interactive) wants to take care + * of the commit itself so remove CHERRY_PICK_HEAD + */ + unlink(git_path("CHERRY_PICK_HEAD")); + return; + } + + if (show_hint) { + advise("after resolving the conflicts, mark the corrected paths"); + advise("with 'git add <paths>' or 'git rm <paths>'"); + advise("and commit the result with 'git commit'"); + } +} + +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) +{ + return lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN); +} + +static int error_dirty_index(struct replay_opts *opts) +{ + if (read_cache_unmerged()) + return error_resolve_conflict(command_name(opts)); + + /* Different translation strings for cherry-pick and revert */ + if (opts->command == REPLAY_CMD_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) +{ + 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 int do_recursive_merge(struct commit *base, struct commit *next, + const char *base_label, const char *next_label, + unsigned char *head, struct strbuf *msgbuf, + struct replay_opts *opts) +{ + struct merge_options o; + struct tree *result, *next_tree, *base_tree, *head_tree; + int clean, index_fd; + const char **xopt; + 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(); + + for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++) + parse_merge_opt(&o, *xopt); + + 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))) + /* TRANSLATORS: %s will be "revert" or "cherry-pick" */ + die(_("%s: Unable to write new index file"), command_name(opts)); + 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++; + } + } + } + + return !clean; +} + +/* + * If we are cherry-pick, and if the merge did not result in + * hand-editing, we will hit this commit and inherit the original + * author date and name. + * If we are revert, or if our cherry-pick results in a hand merge, + * we had better say that the current user is responsible for that. + */ +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]; + int i = 0; + + args[i++] = "commit"; + args[i++] = "-n"; + if (opts->signoff) + args[i++] = "-s"; + if (!opts->edit) { + args[i++] = "-F"; + args[i++] = defmsg; + } + args[i] = NULL; + + return run_command_v_opt(args, RUN_GIT_CMD); +} + +static int do_pick_commit(struct commit *commit, enum replay_action action, + struct replay_opts *opts) +{ + unsigned char head[20]; + struct commit *base, *next, *parent; + const char *base_label, *next_label; + struct commit_message msg = { NULL, NULL, NULL, NULL, NULL }; + char *defmsg = NULL; + struct strbuf msgbuf = STRBUF_INIT; + int res; + + 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 + * that represents the "current" state for merge-recursive + * to work on. + */ + if (write_cache_as_tree(head, 0, NULL)) + die (_("Your index file is unmerged.")); + } else { + if (get_sha1("HEAD", head)) + return error(_("You do not have a valid HEAD")); + if (index_differs_from("HEAD", 0)) + return error_dirty_index(opts); + } + discard_cache(); + + if (!commit->parents) { + parent = NULL; + } + else if (commit->parents->next) { + /* Reverting or cherry-picking a merge commit */ + int cnt; + struct commit_list *p; + + 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 != opts->mainline && p; + cnt++) + p = p->next; + 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 < 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 (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 */ + return error(_("%s: cannot parse parent commit %s"), + command_name(opts), sha1_to_hex(parent->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 + * the difference it introduces since its first parent "prev" + * on top of the current HEAD if we are cherry-pick. Or the + * reverse of it if we are revert. + */ + + defmsg = git_pathdup("MERGE_MSG"); + + if (action == REPLAY_REVERT) { + base = commit; + base_label = msg.label; + next = parent; + 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 && commit->parents->next) { + strbuf_addstr(&msgbuf, ", reversing\nchanges made to "); + strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1)); + } + strbuf_addstr(&msgbuf, ".\n"); + } else { + const char *p; + + base = parent; + base_label = msg.parent_label; + next = commit; + next_label = msg.label; + + /* + * 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 (!opts->strategy || !strcmp(opts->strategy, "recursive") || action == REPLAY_REVERT) { + res = do_recursive_merge(base, next, base_label, next_label, + head, &msgbuf, opts); + write_message(&msgbuf, defmsg); + } else { + 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(opts->strategy, opts->xopts_nr, opts->xopts, + common, sha1_to_hex(head), remotes); + free_commit_list(common); + free_commit_list(remotes); + } + + /* + * If the merge was clean or if it failed due to conflict, we write + * CHERRY_PICK_HEAD for the subsequent invocation of commit to use. + * However, if the merge did not even start, then we don't want to + * write it at all. + */ + if (action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1)) + write_cherry_pick_head(commit, "CHERRY_PICK_HEAD"); + if (action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1)) + write_cherry_pick_head(commit, "REVERT_HEAD"); + + if (res) { + error(action == REPLAY_REVERT + ? _("could not revert %s... %s") + : _("could not apply %s... %s"), + find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), + msg.subject); + print_advice(res == 1); + rerere(opts->allow_rerere_auto); + } else { + if (!opts->no_commit) + res = run_git_commit(defmsg, opts); + } + + free_message(&msg); + free(defmsg); + + return res; +} + +static void prepare_revs(struct replay_opts *opts) +{ + if (opts->command != REPLAY_CMD_REVERT) + opts->revs->reverse ^= 1; + + if (prepare_revision_walk(opts->revs)) + die(_("revision walk setup failed")); + + if (!opts->revs->commits) + die(_("empty commit set passed")); +} + +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"), command_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"), + command_name(opts)); + } + rollback_lock_file(&index_lock); +} + +/* + * Append a (commit, action) to the end of the replay_insn_list. + * + * next starts by pointing to the variable that holds the head of an + * empty replay_insn_list, and is updated to point to the "next" field of + * the last item on the list as new (commit, action) pairs are appended. + * + * Usage example: + * + * struct replay_insn_list *list; + * struct replay_insn_list **next = &list; + * + * next = replay_insn_list_append(c1, a1, next); + * next = replay_insn_list_append(c2, a2, next); + * assert(len(list) == 2); + * return list; + */ +static struct replay_insn_list **replay_insn_list_append(struct commit *operand, + enum replay_action action, + struct replay_insn_list **next) +{ + struct replay_insn_list *new = xmalloc(sizeof(*new)); + new->action = action; + new->operand = operand; + *next = new; + new->next = NULL; + return &new->next; +} + +static int format_todo(struct strbuf *buf, struct replay_insn_list *todo_list) +{ + struct replay_insn_list *cur; + + for (cur = todo_list; cur; cur = cur->next) { + const char *sha1_abbrev, *action_str, *subject; + int subject_len; + + action_str = action_name(cur->action); + sha1_abbrev = find_unique_abbrev(cur->operand->object.sha1, DEFAULT_ABBREV); + subject_len = find_commit_subject(cur->operand->buffer, &subject); + strbuf_addf(buf, "%s %s %.*s\n", action_str, sha1_abbrev, + subject_len, subject); + } + return 0; +} + +static int parse_insn_line(char *bol, char *eol, + struct replay_insn_list *item, int lineno) +{ + const char *todo_file = git_path(SEQ_TODO_FILE); + unsigned char commit_sha1[20]; + char *end_of_object_name; + int saved, status; + size_t error_len; + + if (!prefixcmp(bol, "pick ") || !prefixcmp(bol, "pick\t")) { + item->action = REPLAY_PICK; + bol += strlen("pick "); + } else if (!prefixcmp(bol, "revert ") || !prefixcmp(bol, "revert\t")) { + item->action = REPLAY_REVERT; + bol += strlen("revert "); + } else { + error_len = eol - bol > 255 ? 255 : eol - bol; + return error(_("%s:%d: Unrecognized action: %.*s"), + todo_file, lineno, (int)error_len, bol); + } + + /* Eat up extra spaces/ tabs before object name */ + bol += strspn(bol, " \t"); + + end_of_object_name = bol + strcspn(bol, " \t\n"); + saved = *end_of_object_name; + *end_of_object_name = '\0'; + status = get_sha1(bol, commit_sha1); + *end_of_object_name = saved; + + if (status < 0) { + error_len = eol - bol > 255 ? 255 : eol - bol; + return error(_("%s:%d: Malformed object name: %.*s"), + todo_file, lineno, (int)error_len, bol); + } + + item->operand = lookup_commit_reference(commit_sha1); + if (!item->operand) { + error_len = eol - bol > 255 ? 255 : eol - bol; + return error(_("%s:%d: Not a valid commit: %.*s"), + todo_file, lineno, (int)error_len, bol); + } + + item->next = NULL; + return 0; +} + +static int parse_insn_buffer(char *buf, struct replay_insn_list **todo_list) +{ + struct replay_insn_list **next = todo_list; + struct replay_insn_list item = { NULL, 0, NULL }; + char *p = buf; + int i; + + for (i = 1; *p; i++) { + char *eol = strchrnul(p, '\n'); + if (parse_insn_line(p, eol, &item, i) < 0) + return -1; + next = replay_insn_list_append(item.operand, item.action, next); + p = *eol ? eol + 1 : eol; + } + if (!*todo_list) + return error(_("No commits parsed.")); + return 0; +} + +static void read_populate_todo(struct replay_insn_list **todo_list) +{ + 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); + 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 replay_insn_list **todo_list, + struct replay_opts *opts) +{ + struct commit *commit; + struct replay_insn_list **next; + enum replay_action action; + + prepare_revs(opts); + + next = todo_list; + action = opts->command == REPLAY_CMD_REVERT ? REPLAY_REVERT : REPLAY_PICK; + while ((commit = get_revision(opts->revs))) + next = replay_insn_list_append(commit, action, next); +} + +static int create_seq_dir(void) +{ + const char *seq_dir = git_path(SEQ_DIR); + + if (file_exists(seq_dir)) { + error(_("a cherry-pick or revert is already in progress")); + advise(_("try \"git cherry-pick (--continue | --quit | --abort)\"")); + return -1; + } + else if (mkdir(seq_dir, 0777) < 0) + die_errno(_("Could not create sequencer directory %s"), seq_dir); + return 0; +} + +static enum replay_command read_cmd(void) +{ + const char *cmd_file = git_path(SEQ_CMD_FILE); + struct strbuf buf = STRBUF_INIT; + enum replay_command res; + int fd; + + fd = open(cmd_file, O_RDONLY); + if (fd < 0) + die_errno(_("Could not open %s"), cmd_file); + if (strbuf_read(&buf, fd, 0) < 0) { + close(fd); + strbuf_release(&buf); + die(_("Could not read %s."), cmd_file); + } + close(fd); + + if (!strcmp(buf.buf, "revert\n")) + res = REPLAY_CMD_REVERT; + else if (!strcmp(buf.buf, "cherry-pick\n")) + res = REPLAY_CMD_CHERRY_PICK; + else { + strbuf_release(&buf); + die(_("Malformed command file: %s"), cmd_file); + } + strbuf_release(&buf); + return res; +} + +static void save_cmd(struct replay_opts *opts) +{ + const char *cmd_file = git_path(SEQ_CMD_FILE); + static struct lock_file cmd_lock; + struct strbuf buf = STRBUF_INIT; + int fd; + + fd = hold_lock_file_for_update(&cmd_lock, cmd_file, LOCK_DIE_ON_ERROR); + strbuf_addf(&buf, "%s\n", command_name(opts)); + if (write_in_full(fd, buf.buf, buf.len) < 0) + die_errno(_("Could not write to %s"), cmd_file); + if (commit_lock_file(&cmd_lock) < 0) + die(_("Error wrapping up %s."), cmd_file); +} + +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 int reset_for_rollback(const unsigned char *sha1) +{ + const char *argv[4]; /* reset --merge <arg> + NULL */ + argv[0] = "reset"; + argv[1] = "--merge"; + argv[2] = sha1_to_hex(sha1); + argv[3] = NULL; + return run_command_v_opt(argv, RUN_GIT_CMD); +} + +static int rollback_single_pick(void) +{ + unsigned char head_sha1[20]; + + if (!file_exists(git_path("CHERRY_PICK_HEAD")) && + !file_exists(git_path("REVERT_HEAD"))) + return error(_("no cherry-pick or revert in progress")); + if (read_ref_full("HEAD", head_sha1, 0, NULL)) + return error(_("cannot resolve HEAD")); + if (is_null_sha1(head_sha1)) + return error(_("cannot abort from a branch yet to be born")); + return reset_for_rollback(head_sha1); +} + +static int sequencer_rollback(struct replay_opts *opts) +{ + const char *filename; + FILE *f; + unsigned char sha1[20]; + struct strbuf buf = STRBUF_INIT; + + filename = git_path(SEQ_HEAD_FILE); + f = fopen(filename, "r"); + if (!f && errno == ENOENT) { + /* + * There is no multiple-cherry-pick in progress. + * If CHERRY_PICK_HEAD or REVERT_HEAD indicates + * a single-cherry-pick in progress, abort that. + */ + return rollback_single_pick(); + } + if (!f) + return error(_("cannot open %s: %s"), filename, + strerror(errno)); + if (strbuf_getline(&buf, f, '\n')) { + error(_("cannot read %s: %s"), filename, ferror(f) ? + strerror(errno) : _("unexpected end of file")); + fclose(f); + goto fail; + } + fclose(f); + if (get_sha1_hex(buf.buf, sha1) || buf.buf[40] != '\0') { + error(_("stored pre-cherry-pick HEAD file '%s' is corrupt"), + filename); + goto fail; + } + if (reset_for_rollback(sha1)) + goto fail; + remove_sequencer_state(); + strbuf_release(&buf); + return 0; +fail: + strbuf_release(&buf); + return -1; +} + +static void save_todo(struct replay_insn_list *todo_list) +{ + 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) < 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 replay_insn_list *todo_list, struct replay_opts *opts) +{ + struct replay_insn_list *cur; + int res; + + setenv(GIT_REFLOG_ACTION, command_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); + res = do_pick_commit(cur->operand, cur->action, opts); + if (res) + return res; + } + + /* + * Sequence of picks finished successfully; cleanup by + * removing the .git/sequencer directory + */ + remove_sequencer_state(); + return 0; +} + +static int continue_single_pick(void) +{ + const char *argv[] = { "commit", NULL }; + + if (!file_exists(git_path("CHERRY_PICK_HEAD")) && + !file_exists(git_path("REVERT_HEAD"))) + return error(_("no cherry-pick or revert in progress")); + return run_command_v_opt(argv, RUN_GIT_CMD); +} + +static int sequencer_continue(struct replay_opts *opts) +{ + struct replay_insn_list *todo_list = NULL; + enum replay_command cmd; + + if (!file_exists(git_path(SEQ_TODO_FILE))) + return continue_single_pick(); + + /* + * Disallow continuing a cherry-pick with 'git revert + * --continue' and viceversa + */ + cmd = read_cmd(); + if (cmd != opts->command) + return error(_("cannot %s: a %s is in progress."), + command_name(opts), + cmd == REPLAY_CMD_REVERT ? "revert" : "cherry-pick"); + + read_populate_opts(&opts); + read_populate_todo(&todo_list); + + /* Verify that the conflict has been resolved */ + if (file_exists(git_path("CHERRY_PICK_HEAD")) || + file_exists(git_path("REVERT_HEAD"))) { + int ret = continue_single_pick(); + if (ret) + return ret; + } + if (index_differs_from("HEAD", 0)) + return error_dirty_index(opts); + todo_list = todo_list->next; + return pick_commits(todo_list, opts); +} + +static int single_pick(struct commit *cmit, struct replay_opts *opts) +{ + enum replay_action action; + action = opts->command == REPLAY_CMD_REVERT ? REPLAY_REVERT : REPLAY_PICK; + + setenv(GIT_REFLOG_ACTION, command_name(opts), 0); + return do_pick_commit(cmit, action, opts); +} + +int sequencer_pick_revisions(struct replay_opts *opts) +{ + struct replay_insn_list *todo_list = NULL; + unsigned char sha1[20]; + + if (opts->subcommand == REPLAY_NONE) + assert(opts->revs); + + 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_REMOVE_STATE) { + remove_sequencer_state(); + return 0; + } + if (opts->subcommand == REPLAY_ROLLBACK) + return sequencer_rollback(opts); + if (opts->subcommand == REPLAY_CONTINUE) + return sequencer_continue(opts); + + /* + * If we were called as "git cherry-pick <commit>", just + * cherry-pick/revert it, set CHERRY_PICK_HEAD / + * REVERT_HEAD, and don't touch the sequencer state. + * This means it is possible to cherry-pick in the middle + * of a cherry-pick sequence. + */ + if (opts->revs->cmdline.nr == 1 && + opts->revs->cmdline.rev->whence == REV_CMD_REV && + opts->revs->no_walk && + !opts->revs->cmdline.rev->flags) { + struct commit *cmit; + if (prepare_revision_walk(opts->revs)) + die(_("revision walk setup failed")); + cmit = get_revision(opts->revs); + if (!cmit || get_revision(opts->revs)) + die("BUG: expected exactly one commit from walk"); + return single_pick(cmit, opts); + } + + /* + * 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) + return -1; + if (get_sha1("HEAD", sha1)) { + if (opts->command == REPLAY_CMD_REVERT) + return error(_("Can't revert as initial commit")); + return error(_("Can't cherry-pick into empty head")); + } + save_cmd(opts); + save_head(sha1_to_hex(sha1)); + save_opts(opts); + return pick_commits(todo_list, opts); +} diff --git a/sequencer.h b/sequencer.h index d1cb5c2..58482f1 100644 --- a/sequencer.h +++ b/sequencer.h @@ -24,6 +24,29 @@ enum replay_subcommand { REPLAY_ROLLBACK }; +struct replay_opts { + enum replay_command command; + 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; + + /* Merge strategy */ + const char *strategy; + const char **xopts; + size_t xopts_nr, xopts_alloc; + + /* Only used by REPLAY_NONE */ + struct rev_info *revs; +}; + struct replay_insn_list { struct commit *operand; enum replay_action action; @@ -33,4 +56,6 @@ struct replay_insn_list { /* Removes SEQ_DIR. */ extern void remove_sequencer_state(void); +int sequencer_pick_revisions(struct replay_opts *opts); + #endif -- 1.7.8.2 ^ permalink raw reply related [flat|nested] 64+ messages in thread
* Re: [PATCH 6/6] sequencer: factor code out of revert builtin 2012-01-08 12:27 ` [PATCH 6/6] sequencer: factor code out of revert builtin Ramkumar Ramachandra @ 2012-01-08 20:38 ` Jonathan Nieder 2012-01-10 15:21 ` Ramkumar Ramachandra 0 siblings, 1 reply; 64+ messages in thread From: Jonathan Nieder @ 2012-01-08 20:38 UTC (permalink / raw) To: Ramkumar Ramachandra; +Cc: Git List, Junio C Hamano Ramkumar Ramachandra wrote: > Expose the cherry-picking machinery through a public > sequencer_pick_revisions() (renamed from pick_revisions() in > builtin/revert.c), so that cherry-picking and reverting are special > cases of a general sequencer operation. The cherry-pick builtin is > now a thin wrapper that does command-line argument parsing before > calling into sequencer_pick_revisions(). In the future, we can write > a new "foo" builtin that calls into the sequencer like: Nice. I don't think the plan is actually a "foo" builtin. I guess I would say "So now your 'foo' builtin can use the sequencer machinery by implementing a 'parse_args_populate_opts' function and then running the following:" so as to leave the reader more excited and not to imply a promise we are not going to keep. :) > memset(&opts, 0, sizeof(opts)); > opts.command = REPLAY_CMD_FOO; > opts.revisions = xmalloc(sizeof(*opts.revs)); > parse_args_populate_opts(argc, argv, &opts); > init_revisions(opts.revs); > sequencer_pick_revisions(&opts); Hm, I wonder if opts.command should be a string so each new caller does not have to add to the enum and switch statements... > This patch does not intend to make any functional changes. Check > with: > > $ git blame -s -C HEAD^..HEAD -- sequencer.c Neat. [...] > --- a/sequencer.h > +++ b/sequencer.h > @@ -24,6 +24,29 @@ enum replay_subcommand { > REPLAY_ROLLBACK > }; > > +struct replay_opts { [...] > +}; [...] > @@ -33,4 +56,6 @@ struct replay_insn_list { [...] > +int sequencer_pick_revisions(struct replay_opts *opts); Looks sensible. Maybe it would be useful to include a Documentation/technical/api-sequencer.txt explaining how this is meant to be used. (Not much detail needed, just an overview. See the surrounding files for some examples.) Haven't checked the code movement yet, since it is sitting on top of a slushy base. Next time, hopefully. Thanks, Jonathan ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 6/6] sequencer: factor code out of revert builtin 2012-01-08 20:38 ` Jonathan Nieder @ 2012-01-10 15:21 ` Ramkumar Ramachandra 0 siblings, 0 replies; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-10 15:21 UTC (permalink / raw) To: Jonathan Nieder; +Cc: Git List, Junio C Hamano Jonathan Nieder wrote: > Ramkumar Ramachandra wrote: >> memset(&opts, 0, sizeof(opts)); >> opts.command = REPLAY_CMD_FOO; >> opts.revisions = xmalloc(sizeof(*opts.revs)); >> parse_args_populate_opts(argc, argv, &opts); >> init_revisions(opts.revs); >> sequencer_pick_revisions(&opts); > > Hm, I wonder if opts.command should be a string so each new caller > does not have to add to the enum and switch statements... The new caller would have to add enum and switch statements to define a new action anyway, so I think this should be an enum too. -- Ram ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 0/6] The move to sequencer.c 2012-01-08 12:27 [PATCH 0/6] The move to sequencer.c Ramkumar Ramachandra ` (5 preceding siblings ...) 2012-01-08 12:27 ` [PATCH 6/6] sequencer: factor code out of revert builtin Ramkumar Ramachandra @ 2012-01-08 19:28 ` Jonathan Nieder 2012-01-08 19:51 ` Ramkumar Ramachandra 2012-01-10 16:13 ` [PATCH v2 0/8] " Ramkumar Ramachandra 7 siblings, 1 reply; 64+ messages in thread From: Jonathan Nieder @ 2012-01-08 19:28 UTC (permalink / raw) To: Ramkumar Ramachandra; +Cc: Git List (-cc: Junio) Ramkumar Ramachandra wrote: > I've tried a slightly different approach: the objective of the patches > seem to be much clearer this time. For the sake of new and forgetful readers: what is that objective? ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 0/6] The move to sequencer.c 2012-01-08 19:28 ` [PATCH 0/6] The move to sequencer.c Jonathan Nieder @ 2012-01-08 19:51 ` Ramkumar Ramachandra 2012-01-08 20:43 ` Jonathan Nieder 0 siblings, 1 reply; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-08 19:51 UTC (permalink / raw) To: Jonathan Nieder; +Cc: Git List Jonathan Nieder wrote: > For the sake of new and forgetful readers: what is that objective? The objective is to build a generalized sequencer, of which "git revert" and "git cherry-pick" are two special cases. We can later extend it to encompass more builtins and other commands. Why? In general, we want uniform semantics to continue, skip, and abort any operation that requires user-intervention (example: a conflict that needs to be resolved before proceeding). A lot of this work is inspired by the way "rebase -i" works: it presents the user an instruction sheet with a sequence of actions to perform. Example future use cases: $ git cherry-pick moo..foo [conflict] $ edit problematicfile $ git add problematicfile $ git continue [finishes successfully] $ git revert moo..foo [conflict] $ edit problematicfile $ git add problematicfile $ git continue [finishes successfully] In these examples, the instruction sheet is uniformly filled with "pick" or "revert" actions, which is not very interesting. When we get an interface to allow easy editing of the instruction sheet and encompass more builtins, more interesting sequences of operations will be possible like: pick rr/revert-cherry-pick^2~34 revert master@{12} merge next am /tmp/jrn.patch Thanks. -- Ram ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 0/6] The move to sequencer.c 2012-01-08 19:51 ` Ramkumar Ramachandra @ 2012-01-08 20:43 ` Jonathan Nieder 0 siblings, 0 replies; 64+ messages in thread From: Jonathan Nieder @ 2012-01-08 20:43 UTC (permalink / raw) To: Ramkumar Ramachandra; +Cc: Git List Ramkumar Ramachandra wrote: > Jonathan Nieder wrote: >> For the sake of new and forgetful readers: what is that objective? > > The objective is to build a generalized sequencer [...] > In these examples, the instruction sheet is uniformly filled with > "pick" or "revert" actions, which is not very interesting. When we > get an interface to allow easy editing of the instruction sheet and > encompass more builtins, more interesting sequences of operations will > be possible like: > > pick rr/revert-cherry-pick^2~34 > revert master@{12} > merge next > am /tmp/jrn.patch Ah, okay. So the tasks at hand are (1) allowing heterogeneous instruction sheets (pick + revert for now) and (2) exposing the sequencer interface in sequencer.h. (2) does not strictly depend on (1) but (1) has more short-term benefit that is easy to test so it comes first. No UI aside from editing the instruction sheet manually for now, but that can come later. Thanks for explaining. Jonathan ^ permalink raw reply [flat|nested] 64+ messages in thread
* [PATCH v2 0/8] The move to sequencer.c 2012-01-08 12:27 [PATCH 0/6] The move to sequencer.c Ramkumar Ramachandra ` (6 preceding siblings ...) 2012-01-08 19:28 ` [PATCH 0/6] The move to sequencer.c Jonathan Nieder @ 2012-01-10 16:13 ` Ramkumar Ramachandra 2012-01-10 16:13 ` [PATCH 1/8] revert: prepare to move replay_action to header Ramkumar Ramachandra ` (7 more replies) 7 siblings, 8 replies; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-10 16:13 UTC (permalink / raw) To: Git List; +Cc: Junio C Hamano, Jonathan Nieder Hi, The big changes in this round are: 1. Dropped "revert: don't let revert continue a cherry-pick" from the last round after a quick discussion with Jonathan. 2. Separated out "revert: separate out parse errors logically" from "revert: report fine-grained errors from insn parser". Definitely looks clearer. 3. Improved "revert: report fine-grained errors from insn parser" by eliminating repetition. 20 is a bit arbitrary, but it looks pretty enough on my terminal. 4. Added "sha1_name: introduce getn_sha1() to take length" and "revert: use getn_sha1() to simplify insn parsing". I'm happy with them. Name is inspired from the strncmp() variant of strcmp(). 5. Included minimal API documentation with "sequencer: factor code out of revert builtin". Thanks for reading. I think I'll work on fixing the memory leaks now. Ramkumar Ramachandra (8): revert: prepare to move replay_action to header revert: decouple sequencer actions from builtin commands revert: allow mixing "pick" and "revert" actions revert: separate out parse errors logically revert: report fine-grained errors from insn parser sha1_name: introduce getn_sha1() to take length revert: use getn_sha1() to simplify insn parsing sequencer: factor code out of revert builtin Documentation/technical/api-sequencer.txt | 22 + builtin/revert.c | 958 +---------------------------- cache.h | 1 + sequencer.c | 925 ++++++++++++++++++++++++++++- sequencer.h | 48 ++ sha1_name.c | 23 +- t/t3510-cherry-pick-sequence.sh | 46 +- 7 files changed, 1056 insertions(+), 967 deletions(-) create mode 100644 Documentation/technical/api-sequencer.txt -- 1.7.8.2 ^ permalink raw reply [flat|nested] 64+ messages in thread
* [PATCH 1/8] revert: prepare to move replay_action to header 2012-01-10 16:13 ` [PATCH v2 0/8] " Ramkumar Ramachandra @ 2012-01-10 16:13 ` Ramkumar Ramachandra 2012-01-10 18:27 ` Jonathan Nieder 2012-01-10 16:13 ` [PATCH 2/8] revert: decouple sequencer actions from builtin commands Ramkumar Ramachandra ` (6 subsequent siblings) 7 siblings, 1 reply; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-10 16:13 UTC (permalink / raw) To: Git List; +Cc: Junio C Hamano, Jonathan Nieder Later in the series, we will build a generalized sequencer by factoring code out of the revert builtin, and leaving it with just argument parsing work. This involves moving "replay_action" to sequencer.h, so that both sequencer.c and builtin/revert.c can use it. Unfortunately, "REVERT" and "CHERRY_PICK" are unsuitable variable names, as their purpose is unclear in the global context; in anticipation of the move, rename them to "REPLAY_REVERT" and "REPLAY_PICK" respectively. Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com> --- builtin/revert.c | 40 ++++++++++++++++++++++------------------ 1 files changed, 22 insertions(+), 18 deletions(-) diff --git a/builtin/revert.c b/builtin/revert.c index 0d8020c..2739405 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -39,7 +39,11 @@ static const char * const cherry_pick_usage[] = { NULL }; -enum replay_action { REVERT, CHERRY_PICK }; +enum replay_action { + REPLAY_REVERT, + REPLAY_PICK +}; + enum replay_subcommand { REPLAY_NONE, REPLAY_REMOVE_STATE, @@ -74,14 +78,14 @@ struct replay_opts { static const char *action_name(const struct replay_opts *opts) { - return opts->action == REVERT ? "revert" : "cherry-pick"; + return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick"; } static char *get_encoding(const char *message); static const char * const *revert_or_cherry_pick_usage(struct replay_opts *opts) { - return opts->action == REVERT ? revert_usage : cherry_pick_usage; + return opts->action == REPLAY_REVERT ? revert_usage : cherry_pick_usage; } static int option_parse_x(const struct option *opt, @@ -160,7 +164,7 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) OPT_END(), }; - if (opts->action == CHERRY_PICK) { + if (opts->action == REPLAY_PICK) { struct option cp_extra[] = { OPT_BOOLEAN('x', NULL, &opts->record_origin, "append commit name"), OPT_BOOLEAN(0, "ff", &opts->allow_ff, "allow fast-forward"), @@ -374,7 +378,7 @@ static int error_dirty_index(struct replay_opts *opts) return error_resolve_conflict(action_name(opts)); /* Different translation strings for cherry-pick and revert */ - if (opts->action == CHERRY_PICK) + if (opts->action == REPLAY_PICK) error(_("Your local changes would be overwritten by cherry-pick.")); else error(_("Your local changes would be overwritten by revert.")); @@ -553,7 +557,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) defmsg = git_pathdup("MERGE_MSG"); - if (opts->action == REVERT) { + if (opts->action == REPLAY_REVERT) { base = commit; base_label = msg.label; next = parent; @@ -594,7 +598,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) } } - if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REVERT) { + if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REPLAY_REVERT) { res = do_recursive_merge(base, next, base_label, next_label, head, &msgbuf, opts); write_message(&msgbuf, defmsg); @@ -618,13 +622,13 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) * However, if the merge did not even start, then we don't want to * write it at all. */ - if (opts->action == CHERRY_PICK && !opts->no_commit && (res == 0 || res == 1)) + if (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1)) write_cherry_pick_head(commit, "CHERRY_PICK_HEAD"); - if (opts->action == REVERT && ((opts->no_commit && res == 0) || res == 1)) + if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1)) write_cherry_pick_head(commit, "REVERT_HEAD"); if (res) { - error(opts->action == REVERT + error(opts->action == REPLAY_REVERT ? _("could not revert %s... %s") : _("could not apply %s... %s"), find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), @@ -644,7 +648,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) static void prepare_revs(struct replay_opts *opts) { - if (opts->action != REVERT) + if (opts->action != REPLAY_REVERT) opts->revs->reverse ^= 1; if (prepare_revision_walk(opts->revs)) @@ -701,7 +705,7 @@ static int format_todo(struct strbuf *buf, struct commit_list *todo_list, { struct commit_list *cur = NULL; const char *sha1_abbrev = NULL; - const char *action_str = opts->action == REVERT ? "revert" : "pick"; + const char *action_str = opts->action == REPLAY_REVERT ? "revert" : "pick"; const char *subject; int subject_len; @@ -722,10 +726,10 @@ static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts * int saved, status, padding; if (!prefixcmp(bol, "pick")) { - action = CHERRY_PICK; + action = REPLAY_PICK; bol += strlen("pick"); } else if (!prefixcmp(bol, "revert")) { - action = REVERT; + action = REPLAY_REVERT; bol += strlen("revert"); } else return NULL; @@ -748,7 +752,7 @@ static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts * */ if (action != opts->action) { const char *action_str; - action_str = action == REVERT ? "revert" : "cherry-pick"; + action_str = action == REPLAY_REVERT ? "revert" : "cherry-pick"; error(_("Cannot %s during a %s"), action_str, action_name(opts)); return NULL; } @@ -1124,7 +1128,7 @@ static int pick_revisions(struct replay_opts *opts) if (create_seq_dir() < 0) return -1; if (get_sha1("HEAD", sha1)) { - if (opts->action == REVERT) + if (opts->action == REPLAY_REVERT) return error(_("Can't revert as initial commit")); return error(_("Can't cherry-pick into empty head")); } @@ -1141,7 +1145,7 @@ int cmd_revert(int argc, const char **argv, const char *prefix) memset(&opts, 0, sizeof(opts)); if (isatty(0)) opts.edit = 1; - opts.action = REVERT; + opts.action = REPLAY_REVERT; git_config(git_default_config, NULL); parse_args(argc, argv, &opts); res = pick_revisions(&opts); @@ -1156,7 +1160,7 @@ int cmd_cherry_pick(int argc, const char **argv, const char *prefix) int res; memset(&opts, 0, sizeof(opts)); - opts.action = CHERRY_PICK; + opts.action = REPLAY_PICK; git_config(git_default_config, NULL); parse_args(argc, argv, &opts); res = pick_revisions(&opts); -- 1.7.8.2 ^ permalink raw reply related [flat|nested] 64+ messages in thread
* Re: [PATCH 1/8] revert: prepare to move replay_action to header 2012-01-10 16:13 ` [PATCH 1/8] revert: prepare to move replay_action to header Ramkumar Ramachandra @ 2012-01-10 18:27 ` Jonathan Nieder 0 siblings, 0 replies; 64+ messages in thread From: Jonathan Nieder @ 2012-01-10 18:27 UTC (permalink / raw) To: Ramkumar Ramachandra; +Cc: Git List, Junio C Hamano Ramkumar Ramachandra wrote: > Later in the series, we will build a generalized sequencer by > factoring code out of the revert builtin, and leaving it with just > argument parsing work. This involves moving "replay_action" to > sequencer.h, so that both sequencer.c and builtin/revert.c can use it. > Unfortunately, "REVERT" and "CHERRY_PICK" are unsuitable variable > names, as their purpose is unclear in the global context; in > anticipation of the move, rename them to "REPLAY_REVERT" and > "REPLAY_PICK" respectively. This could be pruned to just the final bit. Something like: REVERT and CHERRY_PICK and are unsuitable names for an enumerator in a public interface, because they are generic enough to be likely to clash with identifiers with other meanings. Rename to REPLAY_REVERT and REPLAY_PICK as preparation for exposing them. The patch proper looks safe and the effect positive to me, so I would be all for applying it as long as the commit message is cleaned up. ^ permalink raw reply [flat|nested] 64+ messages in thread
* [PATCH 2/8] revert: decouple sequencer actions from builtin commands 2012-01-10 16:13 ` [PATCH v2 0/8] " Ramkumar Ramachandra 2012-01-10 16:13 ` [PATCH 1/8] revert: prepare to move replay_action to header Ramkumar Ramachandra @ 2012-01-10 16:13 ` Ramkumar Ramachandra 2012-01-10 18:38 ` Jonathan Nieder 2012-01-10 16:13 ` [PATCH 3/8] revert: allow mixing "pick" and "revert" actions Ramkumar Ramachandra ` (5 subsequent siblings) 7 siblings, 1 reply; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-10 16:13 UTC (permalink / raw) To: Git List; +Cc: Junio C Hamano, Jonathan Nieder Currently, 'git cherry-pick' fills up the '.git/sequencer/todo' instruction sheet with "pick" actions, while 'git revert' fills it up with "revert" actions. Inspired by the way 'rebase -i' works, we would like to permit mixing arbitrary actions in the same instruction sheet. To do this, we first have to decouple the notion of an action in the instruction sheet from builtin commands. While a future instruction sheet would look like: pickle next~4 action3 b74fea revert rr/moo^2~34 the actions "pickle", "action3" and "revert" need not necessarily correspond to the specific builtins. Introduce a "replay_command", and replace instances of "replay_action" with "replay_command" everywhere except in parse_insn_line(), the function that parses the instruction sheet. Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com> --- builtin/revert.c | 63 ++++++++++++++++++++++++++++++------------------------ 1 files changed, 35 insertions(+), 28 deletions(-) diff --git a/builtin/revert.c b/builtin/revert.c index 2739405..9bca9c7 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -44,6 +44,11 @@ enum replay_action { REPLAY_PICK }; +enum replay_command { + REPLAY_CMD_REVERT, + REPLAY_CMD_CHERRY_PICK +}; + enum replay_subcommand { REPLAY_NONE, REPLAY_REMOVE_STATE, @@ -52,7 +57,7 @@ enum replay_subcommand { }; struct replay_opts { - enum replay_action action; + enum replay_command command; enum replay_subcommand subcommand; /* Boolean options */ @@ -76,16 +81,16 @@ struct replay_opts { #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" -static const char *action_name(const struct replay_opts *opts) +static const char *command_name(struct replay_opts *opts) { - return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick"; + return opts->command == REPLAY_CMD_REVERT ? "revert" : "cherry-pick"; } static char *get_encoding(const char *message); static const char * const *revert_or_cherry_pick_usage(struct replay_opts *opts) { - return opts->action == REPLAY_REVERT ? revert_usage : cherry_pick_usage; + return opts->command == REPLAY_CMD_REVERT ? revert_usage : cherry_pick_usage; } static int option_parse_x(const struct option *opt, @@ -142,7 +147,7 @@ static void verify_opt_mutually_compatible(const char *me, ...) 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); + const char *me = command_name(opts); int remove_state = 0; int contin = 0; int rollback = 0; @@ -164,7 +169,7 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) OPT_END(), }; - if (opts->action == REPLAY_PICK) { + if (opts->command == REPLAY_CMD_CHERRY_PICK) { struct option cp_extra[] = { OPT_BOOLEAN('x', NULL, &opts->record_origin, "append commit name"), OPT_BOOLEAN(0, "ff", &opts->allow_ff, "allow fast-forward"), @@ -375,10 +380,10 @@ static struct tree *empty_tree(void) static int error_dirty_index(struct replay_opts *opts) { if (read_cache_unmerged()) - return error_resolve_conflict(action_name(opts)); + return error_resolve_conflict(command_name(opts)); /* Different translation strings for cherry-pick and revert */ - if (opts->action == REPLAY_PICK) + if (opts->command == REPLAY_CMD_CHERRY_PICK) error(_("Your local changes would be overwritten by cherry-pick.")); else error(_("Your local changes would be overwritten by revert.")); @@ -434,7 +439,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"), action_name(opts)); + die(_("%s: Unable to write new index file"), command_name(opts)); rollback_lock_file(&index_lock); if (!clean) { @@ -542,7 +547,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) /* TRANSLATORS: The first %s will be "revert" or "cherry-pick", the second %s a SHA1 */ return error(_("%s: cannot parse parent commit %s"), - action_name(opts), sha1_to_hex(parent->object.sha1)); + command_name(opts), sha1_to_hex(parent->object.sha1)); if (get_message(commit, &msg) != 0) return error(_("Cannot get commit message for %s"), @@ -557,7 +562,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) defmsg = git_pathdup("MERGE_MSG"); - if (opts->action == REPLAY_REVERT) { + if (opts->command == REPLAY_CMD_REVERT) { base = commit; base_label = msg.label; next = parent; @@ -598,7 +603,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) } } - if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REPLAY_REVERT) { + if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->command == REPLAY_CMD_REVERT) { res = do_recursive_merge(base, next, base_label, next_label, head, &msgbuf, opts); write_message(&msgbuf, defmsg); @@ -622,13 +627,13 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) * However, if the merge did not even start, then we don't want to * write it at all. */ - if (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1)) + if (opts->command == REPLAY_CMD_CHERRY_PICK && !opts->no_commit && (res == 0 || res == 1)) write_cherry_pick_head(commit, "CHERRY_PICK_HEAD"); - if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1)) + if (opts->command == REPLAY_CMD_REVERT && ((opts->no_commit && res == 0) || res == 1)) write_cherry_pick_head(commit, "REVERT_HEAD"); if (res) { - error(opts->action == REPLAY_REVERT + error(opts->command == REPLAY_CMD_REVERT ? _("could not revert %s... %s") : _("could not apply %s... %s"), find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), @@ -648,7 +653,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) static void prepare_revs(struct replay_opts *opts) { - if (opts->action != REPLAY_REVERT) + if (opts->command != REPLAY_CMD_REVERT) opts->revs->reverse ^= 1; if (prepare_revision_walk(opts->revs)) @@ -663,12 +668,13 @@ 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"), action_name(opts)); + die(_("git %s: failed to read the index"), command_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"), action_name(opts)); + die(_("git %s: failed to refresh the index"), + command_name(opts)); } rollback_lock_file(&index_lock); } @@ -705,7 +711,7 @@ static int format_todo(struct strbuf *buf, struct commit_list *todo_list, { struct commit_list *cur = NULL; const char *sha1_abbrev = NULL; - const char *action_str = opts->action == REPLAY_REVERT ? "revert" : "pick"; + const char *action_str = opts->command == REPLAY_CMD_REVERT ? "revert" : "pick"; const char *subject; int subject_len; @@ -750,10 +756,11 @@ static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts * * 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 == REPLAY_REVERT ? "revert" : "cherry-pick"; - error(_("Cannot %s during a %s"), action_str, action_name(opts)); + if ((action == REPLAY_PICK && opts->command == REPLAY_CMD_REVERT) || + (action == REPLAY_REVERT && opts->command == REPLAY_CMD_CHERRY_PICK)) { + error(_("Cannot %s during a %s"), + action == REPLAY_REVERT ? "revert" : "pick", + command_name(opts)); return NULL; } @@ -1015,7 +1022,7 @@ 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); + setenv(GIT_REFLOG_ACTION, command_name(opts), 0); if (opts->allow_ff) assert(!(opts->signoff || opts->no_commit || opts->record_origin || opts->edit)); @@ -1070,7 +1077,7 @@ static int sequencer_continue(struct replay_opts *opts) static int single_pick(struct commit *cmit, struct replay_opts *opts) { - setenv(GIT_REFLOG_ACTION, action_name(opts), 0); + setenv(GIT_REFLOG_ACTION, command_name(opts), 0); return do_pick_commit(cmit, opts); } @@ -1128,7 +1135,7 @@ static int pick_revisions(struct replay_opts *opts) if (create_seq_dir() < 0) return -1; if (get_sha1("HEAD", sha1)) { - if (opts->action == REPLAY_REVERT) + if (opts->command == REPLAY_CMD_REVERT) return error(_("Can't revert as initial commit")); return error(_("Can't cherry-pick into empty head")); } @@ -1145,7 +1152,7 @@ int cmd_revert(int argc, const char **argv, const char *prefix) memset(&opts, 0, sizeof(opts)); if (isatty(0)) opts.edit = 1; - opts.action = REPLAY_REVERT; + opts.command = REPLAY_CMD_REVERT; git_config(git_default_config, NULL); parse_args(argc, argv, &opts); res = pick_revisions(&opts); @@ -1160,7 +1167,7 @@ int cmd_cherry_pick(int argc, const char **argv, const char *prefix) int res; memset(&opts, 0, sizeof(opts)); - opts.action = REPLAY_PICK; + opts.command = REPLAY_CMD_CHERRY_PICK; git_config(git_default_config, NULL); parse_args(argc, argv, &opts); res = pick_revisions(&opts); -- 1.7.8.2 ^ permalink raw reply related [flat|nested] 64+ messages in thread
* Re: [PATCH 2/8] revert: decouple sequencer actions from builtin commands 2012-01-10 16:13 ` [PATCH 2/8] revert: decouple sequencer actions from builtin commands Ramkumar Ramachandra @ 2012-01-10 18:38 ` Jonathan Nieder 2012-01-11 4:02 ` Ramkumar Ramachandra 0 siblings, 1 reply; 64+ messages in thread From: Jonathan Nieder @ 2012-01-10 18:38 UTC (permalink / raw) To: Ramkumar Ramachandra; +Cc: Git List, Junio C Hamano Ramkumar Ramachandra wrote: > Currently, 'git cherry-pick' fills up the '.git/sequencer/todo' > instruction sheet with "pick" actions, while 'git revert' fills it up > with "revert" actions. Inspired by the way 'rebase -i' works, we > would like to permit mixing arbitrary actions in the same instruction > sheet. Ok. > To do this, we first have to decouple the notion of an action > in the instruction sheet from builtin commands. Wha? I guess at this point I get lost because you are using jargon. What is the difference between an action in the instruction sheet and a builtin command? How does being a busybox-style hard link to the main "git" binary as opposed to a separate binary (which is what "builtin" means) have anything to do with this at all? And I don't have any sense of the impact. Will this change the behavior of "git cherry-pick"? Will it avoid some confusion on the part of future people modifying the code? Is it a necessary step before some future change we want? Does it just make the code prettier, and help the sanity of future readers in the process (which is definitely valuable, too)? The above doesn't make it clear to me. > --- a/builtin/revert.c > +++ b/builtin/revert.c [...] > @@ -52,7 +57,7 @@ enum replay_subcommand { > }; > > struct replay_opts { > - enum replay_action action; > + enum replay_command command; > enum replay_subcommand subcommand; This part seems to be renaming "action" to "command". A cosmetic change, which I don't have an immediate opinion about either way. [...] > -static const char *action_name(const struct replay_opts *opts) > +static const char *command_name(struct replay_opts *opts) This part does a similar renaming, and drops a const while at it for no intelligible reason. [...] > @@ -142,7 +147,7 @@ static void verify_opt_mutually_compatible(const char *me, ...) > 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); > + const char *me = command_name(opts); The rest is stuff like this, which follows from the first part. Stepping back, I think the idea is that "enum replay_action" is not a good way to identify the command name in error messages like fatal: cherry-pick: --abort cannot be used with --continue So you introduce a _new_ enum to represent the command name. Why not just use a string, so commands using the nice and generic sequencer library do not have to register themselves in a global callers list to use it? Plus the renaming of the ->action field and action_name() function feel gratuitous, and drowned out the actual changes in the noise. Does that clarify? I guess I agree with the problem you are solving, but I don't agree with the solution at all. Thanks, Jonathan ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 2/8] revert: decouple sequencer actions from builtin commands 2012-01-10 18:38 ` Jonathan Nieder @ 2012-01-11 4:02 ` Ramkumar Ramachandra 2012-01-11 4:17 ` Ramkumar Ramachandra 0 siblings, 1 reply; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-11 4:02 UTC (permalink / raw) To: Jonathan Nieder; +Cc: Git List, Junio C Hamano Jonathan Nieder wrote: > Ramkumar Ramachandra wrote: > [...] >> -static const char *action_name(const struct replay_opts *opts) >> +static const char *command_name(struct replay_opts *opts) > > This part does a similar renaming, and drops a const while at it for > no intelligible reason. Carried over from the previous iteration- sorry I forgot to fix this. > [...] >> @@ -142,7 +147,7 @@ static void verify_opt_mutually_compatible(const char *me, ...) >> 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); >> + const char *me = command_name(opts); > > The rest is stuff like this, which follows from the first part. > > Stepping back, I think the idea is that "enum replay_action" is not a > good way to identify the command name in error messages like > > fatal: cherry-pick: --abort cannot be used with --continue > > So you introduce a _new_ enum to represent the command name. Why not > just use a string, so commands using the nice and generic sequencer > library do not have to register themselves in a global callers list to > use it? Fine; I'm sold on the string idea. Also, I figured it would be easier to explain the changes if I change this enum to a string -- I should probably use "ease of explaining changes" as a stronger criterion in the future when I have two competing implementations in mind. Thanks. -- Ram ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 2/8] revert: decouple sequencer actions from builtin commands 2012-01-11 4:02 ` Ramkumar Ramachandra @ 2012-01-11 4:17 ` Ramkumar Ramachandra 2012-01-11 5:04 ` Jonathan Nieder 0 siblings, 1 reply; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-11 4:17 UTC (permalink / raw) To: Jonathan Nieder; +Cc: Git List, Junio C Hamano Ramkumar Ramachandra wrote: > Fine; I'm sold on the string idea. Also, I figured it would be > easier to explain the changes if I change this enum to a string -- I > should probably use "ease of explaining changes" as a stronger > criterion in the future when I have two competing implementations in > mind. I wrote that too quickly. I can't stand seeing so many strcmp() calls all over my codebase -- look at the number of instances of matching opts->command to REPLAY_CMD_*. Why should I have to use strcmp() when the data is semantic? It makes no sense: by spelling out the string in so many places, I'm just making the code more error-prone, because the compiler can't warn me if I make a spelling mistake in one place. Why do you like the string so much? A new caller will have to register new actions in the replay_actions enum and modify the codebase to define a codepath for its specific case anyway: so I don't mind it registering a new command in replay_command. -- Ram ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 2/8] revert: decouple sequencer actions from builtin commands 2012-01-11 4:17 ` Ramkumar Ramachandra @ 2012-01-11 5:04 ` Jonathan Nieder 2012-01-11 5:14 ` Ramkumar Ramachandra 0 siblings, 1 reply; 64+ messages in thread From: Jonathan Nieder @ 2012-01-11 5:04 UTC (permalink / raw) To: Ramkumar Ramachandra; +Cc: Git List, Junio C Hamano Ramkumar Ramachandra wrote: > I wrote that too quickly. I can't stand seeing so many strcmp() calls > all over my codebase -- look at the number of instances of matching > opts->command to REPLAY_CMD_*. If the number of such in sequencer.c is greater than 0, it's probably a bug. Why would the sequencer change behavior based on its caller's name? It's very easy for me to be wrong, though --- this is just a first reaction, and I haven't looked at this version of sequencer.c yet, since it doesn't exist. :) Is this what you're talking about? 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.")); Now imagine "git rebase" wants to hook into the same stuff. What should the message be? Maybe: error(_("your local changes would be overwritten - aborting")); Or: error(_(opts->error_msgs[REPLAY_WOULD_CLOBBER_LOCAL_CHANGES])); ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 2/8] revert: decouple sequencer actions from builtin commands 2012-01-11 5:04 ` Jonathan Nieder @ 2012-01-11 5:14 ` Ramkumar Ramachandra 2012-01-11 5:26 ` Ramkumar Ramachandra 2012-01-11 13:18 ` Jonathan Nieder 0 siblings, 2 replies; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-11 5:14 UTC (permalink / raw) To: Jonathan Nieder; +Cc: Git List, Junio C Hamano Jonathan Nieder wrote: > Ramkumar Ramachandra wrote: > >> I wrote that too quickly. I can't stand seeing so many strcmp() calls >> all over my codebase -- look at the number of instances of matching >> opts->command to REPLAY_CMD_*. > > If the number of such in sequencer.c is greater than 0, it's probably > a bug. Why would the sequencer change behavior based on its caller's > name? In the super-final version, yes- I agree. However, we're not there yet -- this patch is more of a transition, to make the life of "revert: allow mixing "pick" and "revert" actions" easier. Once the painful move to sequencer.c is completed, we can think about all these things. Right now, I'm only focusing on the move, and everything that should logically precede it (in my opinion). > Is this what you're talking about? > > 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.")); 1. Yes, error messages like this. 2. Since I haven't modified function prototypes to include a "replay_action" at the "revert: decouple sequencer actions from builtin commands" stage, functions like do_pick_commit() have to rely on opts->command! That's the entire point: I want to show how this "opts->command" is changing to "action" in most places in "revert: allow mixing "pick" and "revert" actions". I'd rationalize that taking care of (1) is not urgent- we can do it after we move to sequencer.c. As for (2), we have little choice at this point- either make it a string like it's supposed to be in the super-final version, and struggle with the ugliness of strcmp() now, or make it an enum now; I choose the latter. Thanks for making me explain this. -- Ram ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 2/8] revert: decouple sequencer actions from builtin commands 2012-01-11 5:14 ` Ramkumar Ramachandra @ 2012-01-11 5:26 ` Ramkumar Ramachandra 2012-01-11 5:49 ` Jonathan Nieder 2012-01-11 13:18 ` Jonathan Nieder 1 sibling, 1 reply; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-11 5:26 UTC (permalink / raw) To: Jonathan Nieder; +Cc: Git List, Junio C Hamano Hi Jonathan, I wrote a new commit message for this patch. Perhaps it can help clarify? revert: decouple sequencer actions from commands Currently, we have two actions "pick" and "revert" that directly correspond to the action of the commands 'git cherry-pick' and 'git revert' respectively. Since we would like to permit mixing different actions in the same instruction sheet inspired by the way 'git rebase --interactive' works, a future instruction sheet would look like: pickle next~4 revert rr/moo^2~34 action3 b74fea where the actions "pickle", "revert" and "action3" need not directly correspond to the specific commands. So, instead of using the same representation for both the command executed and the sequencer action (the "replay_action" enum), store the command separately as a "replay_command" enum. Since we don't allow mixing actions yet, the entire operation of 'git cherry-pick'/ 'git revert' relies on the command, with the exception of the instruction sheet parser which converts a "replay_action" to a "replay_command". As we support mixing actions in future patches, more portions of the code will rely on the action, demoting the command to a string used in error reporting. -- Ram ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 2/8] revert: decouple sequencer actions from builtin commands 2012-01-11 5:26 ` Ramkumar Ramachandra @ 2012-01-11 5:49 ` Jonathan Nieder 2012-01-11 9:19 ` Ramkumar Ramachandra 0 siblings, 1 reply; 64+ messages in thread From: Jonathan Nieder @ 2012-01-11 5:49 UTC (permalink / raw) To: Ramkumar Ramachandra; +Cc: Git List, Junio C Hamano Ramkumar Ramachandra wrote: > Hi Jonathan, > > I wrote a new commit message for this patch. Perhaps it can help clarify? Thanks. > revert: decouple sequencer actions from commands > > Currently, we have two actions "pick" and "revert" that directly > correspond to the action of the commands 'git cherry-pick' and 'git > revert' respectively. Well, let's start here. The two insns "pick" and "revert" and the ability to mix them doesn't have much to do with the picture, does it? I think the actual problem being solved is that insn types, as described by the replay_action enum, are being abused to refer to top-level git commands "revert" and "cherry-pick". The sequencer isn't supposed to care which top-level git command called it, except in some messages, so we'd certainly like to stop pretending that has something to do with insn types. Based on what you've said, correcting this cleanly is complicated in some places by the inconvenient fact that the sequencer _does_ care which top-level git command called it. (I haven't checked this; I'm just taking it on faith from you.) If we want to let other git commands (like "git rebase" or "git sequence") call into the sequencer, that sounds like a way bigger problem than any conflict of terminology. ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 2/8] revert: decouple sequencer actions from builtin commands 2012-01-11 5:49 ` Jonathan Nieder @ 2012-01-11 9:19 ` Ramkumar Ramachandra 2012-01-11 9:52 ` Jonathan Nieder 0 siblings, 1 reply; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-11 9:19 UTC (permalink / raw) To: Jonathan Nieder; +Cc: Git List, Junio C Hamano Jonathan Nieder wrote: > Well, let's start here. The two insns "pick" and "revert" and the > ability to mix them doesn't have much to do with the picture, does it? > > I think the actual problem being solved is that insn types, as described > by the replay_action enum, are being abused to refer to top-level git > commands "revert" and "cherry-pick". The sequencer isn't supposed to > care which top-level git command called it, except in some messages, so > we'd certainly like to stop pretending that has something to do with > insn types. Exactly. I'll update the commit message. > Based on what you've said, correcting this cleanly is complicated in > some places by the inconvenient fact that the sequencer _does_ care > which top-level git command called it. (I haven't checked this; I'm > just taking it on faith from you.) If we want to let other git > commands (like "git rebase" or "git sequence") call into the > sequencer, that sounds like a way bigger problem than any conflict of > terminology. There's no need to trust me: just think about the problem. We fill out a replay_opts structure to call into the sequencer with- how does the sequencer know what to do with this hypothetical command string (say "cherry-pick") on a fresh invocation? It needs to translate this into a replay_action at some point, right? There are atleast three places where this happens: prepare_revs(), walk_revs_populate_todo(), and single_pick(). -- Ram ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 2/8] revert: decouple sequencer actions from builtin commands 2012-01-11 9:19 ` Ramkumar Ramachandra @ 2012-01-11 9:52 ` Jonathan Nieder 2012-01-11 10:11 ` Ramkumar Ramachandra 0 siblings, 1 reply; 64+ messages in thread From: Jonathan Nieder @ 2012-01-11 9:52 UTC (permalink / raw) To: Ramkumar Ramachandra; +Cc: Git List, Junio C Hamano Ramkumar Ramachandra wrote: > how does > the sequencer know what to do with this hypothetical command string > (say "cherry-pick") on a fresh invocation? It needs to translate this > into a replay_action at some point, right? There are atleast three > places where this happens: prepare_revs(), walk_revs_populate_todo(), > and single_pick(). I see. Perhaps cherry-pick and revert should be different values for replay_subcommand, to avoid conflating the mechanics and the command name? Resulting in something like this: enum replay_subcommand { REPLAY_PICK_REVISIONS, REPLAY_REVERT_REVISIONS, REPLAY_EDIT_SEQUENCE, REPLAY_REMOVE_STATE, REPLAY_CONTINUE, REPLAY_SKIP, REPLAY_ROLLBACK }; Though this dispatcher on an enum to perform many different actions already felt a bit awkward, so an alternative could be extern int pick_revisions(struct replay_opts *opts); extern int revert_revisions(struct replay_opts *opts); extern int launch_sequence_editor(struct replay_opts *opts); extern void remove_sequencer_state(void); extern int sequencer_continue(struct replay_opts *opts); extern int sequencer_skip(struct replay_opts *opts); extern int sequencer_rollback(struct replay_opts *opts); which would make it easier to add arguments specific to any one of the routines as appropriate. ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 2/8] revert: decouple sequencer actions from builtin commands 2012-01-11 9:52 ` Jonathan Nieder @ 2012-01-11 10:11 ` Ramkumar Ramachandra 2012-01-11 13:40 ` Jonathan Nieder 0 siblings, 1 reply; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-11 10:11 UTC (permalink / raw) To: Jonathan Nieder; +Cc: Git List, Junio C Hamano Jonathan Nieder wrote: > Perhaps cherry-pick and revert should be different values for > replay_subcommand, to avoid conflating the mechanics and the command > name? Resulting in something like this: > > enum replay_subcommand { > REPLAY_PICK_REVISIONS, > REPLAY_REVERT_REVISIONS, > REPLAY_EDIT_SEQUENCE, > REPLAY_REMOVE_STATE, > REPLAY_CONTINUE, > REPLAY_SKIP, > REPLAY_ROLLBACK > }; We'd be prematurely locking ourselves into a design where we can't tell which top-level command issued the continue/ abort -- this means that there's no way to deny a 'git rebase --continue' from running after a 'git cherry-pick' conflicts (assuming that rebase is implemented in terms of the sequencer ofcourse). Even if that specific objection isn't to your taste, I'm not comfortable about painting ourselves into such a tight corner so early on. My sincere suggestion is to procrastinate the problem until we have a tighter usecase (a new top-level command or action added, for instance). I don't think we have to worry about preserving backward compatibility in the sequencer API? -- Ram ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 2/8] revert: decouple sequencer actions from builtin commands 2012-01-11 10:11 ` Ramkumar Ramachandra @ 2012-01-11 13:40 ` Jonathan Nieder 0 siblings, 0 replies; 64+ messages in thread From: Jonathan Nieder @ 2012-01-11 13:40 UTC (permalink / raw) To: Ramkumar Ramachandra; +Cc: Git List, Junio C Hamano Ramkumar Ramachandra wrote: > We'd be prematurely locking ourselves into a design where we can't > tell which top-level command issued the continue/ abort The .git/sequencer/todo file already doesn't record which top-level command initiated the sequence and doesn't seem to operate under a model in which that is a useful question. Honestly, that's my only objection to the "git revert --continue during git cherry-pick" check. I think it is not premature to think about whether that matters. I've already said a little about related cases where it seemed to matter but there was instead something else at play. Can you offer some examples of how people might use the "git cherry-pick" / "git revert" commands and get stuck or run into trouble, and how git can help them? [...] > My sincere > suggestion is to procrastinate the problem until we have a tighter > usecase (a new top-level command or action added, for instance). Thinking carefully about sequencer use cases also seems like a good idea. Is it intended that "git am" and "git rebase" should be reimplemented on top of the sequencer? Do you have goals in mind for commands like "git sequence --step" that could be used to examine, influence, and carry out git's idea of what should happen next? If we have no use case, then there's no reason to change the code. It already works[*] for cherry-pick and revert. I think we shouldn't be moving this code to sequencer.c or cleaning up the API to suit other commands (e.g., introducing two ways to sane "am I picking or reverting") without having one in mind. [*] Modulo bugs and some missing features such as --skip. ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 2/8] revert: decouple sequencer actions from builtin commands 2012-01-11 5:14 ` Ramkumar Ramachandra 2012-01-11 5:26 ` Ramkumar Ramachandra @ 2012-01-11 13:18 ` Jonathan Nieder 2012-01-11 16:39 ` Ramkumar Ramachandra 1 sibling, 1 reply; 64+ messages in thread From: Jonathan Nieder @ 2012-01-11 13:18 UTC (permalink / raw) To: Ramkumar Ramachandra; +Cc: Git List, Junio C Hamano Ramkumar Ramachandra wrote: > Once the > painful move to sequencer.c is completed, we can think about all these > things. Honestly, moving code verbatim between files is very easy. Repeatedly rebasing a patch that carries out such a move would presumably be hard, though. But this pain is unnecessary! Just like I haven't been reviewing the code movement, I'd be perfectly happy to read a "patch" that says "And then we move the functions from the following list to sequencer.c. I'll send a patch doing so once work has settled down in patches earlier in this series." Now you are telling me that in the super-final future my worries are valid, but I should forget about them today, because later in this series there is some code movement. That we need to get this painful part over with. I would be much more comforted if you said that in the future my worries were _not_ valid, that the current design is a good one, and that these patches are not making the program worse; otherwise, wouldn't it be better to skip whichever are the questionable patches and just carry out the code movement, which doesn't depend on them? ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 2/8] revert: decouple sequencer actions from builtin commands 2012-01-11 13:18 ` Jonathan Nieder @ 2012-01-11 16:39 ` Ramkumar Ramachandra 2012-01-11 16:47 ` Jonathan Nieder 0 siblings, 1 reply; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-11 16:39 UTC (permalink / raw) To: Jonathan Nieder; +Cc: Git List, Junio C Hamano Jonathan Nieder wrote: > Honestly, moving code verbatim between files is very easy. Repeatedly > rebasing a patch that carries out such a move would presumably be > hard, though. But this pain is unnecessary! > > Just like I haven't been reviewing the code movement, I'd be perfectly > happy to read a "patch" that says > > "And then we move the functions from the following list to > sequencer.c. I'll send a patch doing so once work has settled > down in patches earlier in this series." More than the pain of rebasing the patch everytime, I guess what I'm asking is: is it worth stretching my foresight like this? Once the code is in sequencer.c, it just becomes so much easier for me to write scratch code to help me wrap my head around the generalization. If the answer to the question is yes, I suppose it makes sense to submit the good parts now and work on the other parts over an extended period of time. -- Ram ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 2/8] revert: decouple sequencer actions from builtin commands 2012-01-11 16:39 ` Ramkumar Ramachandra @ 2012-01-11 16:47 ` Jonathan Nieder 2012-01-11 16:52 ` Ramkumar Ramachandra 2012-01-11 18:15 ` [PATCH v3 0/2] The move to sequencer.c Ramkumar Ramachandra 0 siblings, 2 replies; 64+ messages in thread From: Jonathan Nieder @ 2012-01-11 16:47 UTC (permalink / raw) To: Ramkumar Ramachandra; +Cc: Git List, Junio C Hamano Ramkumar Ramachandra wrote: > More than the pain of rebasing the patch everytime, I guess what I'm > asking is: is it worth stretching my foresight like this? Once the > code is in sequencer.c, it just becomes so much easier for me to write > scratch code to help me wrap my head around the generalization. In that case, why not just a patch to move the code to sequencer.c, with whatever is the minimum of related fixes (just namespace stuff, I'd imagine) before it? ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 2/8] revert: decouple sequencer actions from builtin commands 2012-01-11 16:47 ` Jonathan Nieder @ 2012-01-11 16:52 ` Ramkumar Ramachandra 2012-01-11 18:15 ` [PATCH v3 0/2] The move to sequencer.c Ramkumar Ramachandra 1 sibling, 0 replies; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-11 16:52 UTC (permalink / raw) To: Jonathan Nieder; +Cc: Git List, Junio C Hamano Jonathan Nieder wrote: > In that case, why not just a patch to move the code to sequencer.c, > with whatever is the minimum of related fixes (just namespace stuff, > I'd imagine) before it? Cool, I'll do that then. I thought what I'd originally posted was an acceptable minimum to show the motivation for the move. -- Ram ^ permalink raw reply [flat|nested] 64+ messages in thread
* [PATCH v3 0/2] The move to sequencer.c 2012-01-11 16:47 ` Jonathan Nieder 2012-01-11 16:52 ` Ramkumar Ramachandra @ 2012-01-11 18:15 ` Ramkumar Ramachandra 2012-01-11 18:15 ` [PATCH 1/2] revert: prepare to move replay_action to header Ramkumar Ramachandra ` (2 more replies) 1 sibling, 3 replies; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-11 18:15 UTC (permalink / raw) To: Git List; +Cc: Junio C Hamano, Jonathan Nieder Hi, I figured I'd just post a minimal series performing the move: 1. I can write up scratch code and toy with various ideas I have on the generalization. It'll help with my thought process. 2. I can stop thinking about how to begin every commit message with a note about anticipating this move. This'll be a big relief! Thanks. Ramkumar Ramachandra (2): revert: prepare to move replay_action to header sequencer: factor code out of revert builtin builtin/revert.c | 952 +----------------------------------------------------- sequencer.c | 918 ++++++++++++++++++++++++++++++++++++++++++++++++++++- sequencer.h | 37 +++ 3 files changed, 961 insertions(+), 946 deletions(-) -- 1.7.8.2 ^ permalink raw reply [flat|nested] 64+ messages in thread
* [PATCH 1/2] revert: prepare to move replay_action to header 2012-01-11 18:15 ` [PATCH v3 0/2] The move to sequencer.c Ramkumar Ramachandra @ 2012-01-11 18:15 ` Ramkumar Ramachandra 2012-01-11 18:15 ` [PATCH 2/2] sequencer: factor code out of revert builtin Ramkumar Ramachandra 2012-01-11 18:40 ` [PATCH v3 0/2] The move to sequencer.c Jonathan Nieder 2 siblings, 0 replies; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-11 18:15 UTC (permalink / raw) To: Git List; +Cc: Junio C Hamano, Jonathan Nieder REVERT and CHERRY_PICK and are unsuitable names for an enumerator in a public interface, because they are generic enough to be likely to clash with identifiers with other meanings. Rename to REPLAY_REVERT and REPLAY_PICK as preparation for exposing them. Helped-by: Jonathan Nieder <jrnieder@gmail.com> Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com> --- builtin/revert.c | 40 ++++++++++++++++++++++------------------ 1 files changed, 22 insertions(+), 18 deletions(-) diff --git a/builtin/revert.c b/builtin/revert.c index 0d8020c..2739405 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -39,7 +39,11 @@ static const char * const cherry_pick_usage[] = { NULL }; -enum replay_action { REVERT, CHERRY_PICK }; +enum replay_action { + REPLAY_REVERT, + REPLAY_PICK +}; + enum replay_subcommand { REPLAY_NONE, REPLAY_REMOVE_STATE, @@ -74,14 +78,14 @@ struct replay_opts { static const char *action_name(const struct replay_opts *opts) { - return opts->action == REVERT ? "revert" : "cherry-pick"; + return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick"; } static char *get_encoding(const char *message); static const char * const *revert_or_cherry_pick_usage(struct replay_opts *opts) { - return opts->action == REVERT ? revert_usage : cherry_pick_usage; + return opts->action == REPLAY_REVERT ? revert_usage : cherry_pick_usage; } static int option_parse_x(const struct option *opt, @@ -160,7 +164,7 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) OPT_END(), }; - if (opts->action == CHERRY_PICK) { + if (opts->action == REPLAY_PICK) { struct option cp_extra[] = { OPT_BOOLEAN('x', NULL, &opts->record_origin, "append commit name"), OPT_BOOLEAN(0, "ff", &opts->allow_ff, "allow fast-forward"), @@ -374,7 +378,7 @@ static int error_dirty_index(struct replay_opts *opts) return error_resolve_conflict(action_name(opts)); /* Different translation strings for cherry-pick and revert */ - if (opts->action == CHERRY_PICK) + if (opts->action == REPLAY_PICK) error(_("Your local changes would be overwritten by cherry-pick.")); else error(_("Your local changes would be overwritten by revert.")); @@ -553,7 +557,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) defmsg = git_pathdup("MERGE_MSG"); - if (opts->action == REVERT) { + if (opts->action == REPLAY_REVERT) { base = commit; base_label = msg.label; next = parent; @@ -594,7 +598,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) } } - if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REVERT) { + if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REPLAY_REVERT) { res = do_recursive_merge(base, next, base_label, next_label, head, &msgbuf, opts); write_message(&msgbuf, defmsg); @@ -618,13 +622,13 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) * However, if the merge did not even start, then we don't want to * write it at all. */ - if (opts->action == CHERRY_PICK && !opts->no_commit && (res == 0 || res == 1)) + if (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1)) write_cherry_pick_head(commit, "CHERRY_PICK_HEAD"); - if (opts->action == REVERT && ((opts->no_commit && res == 0) || res == 1)) + if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1)) write_cherry_pick_head(commit, "REVERT_HEAD"); if (res) { - error(opts->action == REVERT + error(opts->action == REPLAY_REVERT ? _("could not revert %s... %s") : _("could not apply %s... %s"), find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), @@ -644,7 +648,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) static void prepare_revs(struct replay_opts *opts) { - if (opts->action != REVERT) + if (opts->action != REPLAY_REVERT) opts->revs->reverse ^= 1; if (prepare_revision_walk(opts->revs)) @@ -701,7 +705,7 @@ static int format_todo(struct strbuf *buf, struct commit_list *todo_list, { struct commit_list *cur = NULL; const char *sha1_abbrev = NULL; - const char *action_str = opts->action == REVERT ? "revert" : "pick"; + const char *action_str = opts->action == REPLAY_REVERT ? "revert" : "pick"; const char *subject; int subject_len; @@ -722,10 +726,10 @@ static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts * int saved, status, padding; if (!prefixcmp(bol, "pick")) { - action = CHERRY_PICK; + action = REPLAY_PICK; bol += strlen("pick"); } else if (!prefixcmp(bol, "revert")) { - action = REVERT; + action = REPLAY_REVERT; bol += strlen("revert"); } else return NULL; @@ -748,7 +752,7 @@ static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts * */ if (action != opts->action) { const char *action_str; - action_str = action == REVERT ? "revert" : "cherry-pick"; + action_str = action == REPLAY_REVERT ? "revert" : "cherry-pick"; error(_("Cannot %s during a %s"), action_str, action_name(opts)); return NULL; } @@ -1124,7 +1128,7 @@ static int pick_revisions(struct replay_opts *opts) if (create_seq_dir() < 0) return -1; if (get_sha1("HEAD", sha1)) { - if (opts->action == REVERT) + if (opts->action == REPLAY_REVERT) return error(_("Can't revert as initial commit")); return error(_("Can't cherry-pick into empty head")); } @@ -1141,7 +1145,7 @@ int cmd_revert(int argc, const char **argv, const char *prefix) memset(&opts, 0, sizeof(opts)); if (isatty(0)) opts.edit = 1; - opts.action = REVERT; + opts.action = REPLAY_REVERT; git_config(git_default_config, NULL); parse_args(argc, argv, &opts); res = pick_revisions(&opts); @@ -1156,7 +1160,7 @@ int cmd_cherry_pick(int argc, const char **argv, const char *prefix) int res; memset(&opts, 0, sizeof(opts)); - opts.action = CHERRY_PICK; + opts.action = REPLAY_PICK; git_config(git_default_config, NULL); parse_args(argc, argv, &opts); res = pick_revisions(&opts); -- 1.7.8.2 ^ permalink raw reply related [flat|nested] 64+ messages in thread
* [PATCH 2/2] sequencer: factor code out of revert builtin 2012-01-11 18:15 ` [PATCH v3 0/2] The move to sequencer.c Ramkumar Ramachandra 2012-01-11 18:15 ` [PATCH 1/2] revert: prepare to move replay_action to header Ramkumar Ramachandra @ 2012-01-11 18:15 ` Ramkumar Ramachandra 2012-01-11 18:40 ` [PATCH v3 0/2] The move to sequencer.c Jonathan Nieder 2 siblings, 0 replies; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-11 18:15 UTC (permalink / raw) To: Git List; +Cc: Junio C Hamano, Jonathan Nieder Expose the cherry-picking machinery through a public sequencer_pick_revisions() (renamed from pick_revisions() in builtin/revert.c), so that cherry-picking and reverting are special cases of a general sequencer operation. The cherry-pick builtin is now a thin wrapper that does command-line argument parsing before calling into sequencer_pick_revisions(). In the future, we can write a new "foo" builtin that calls into the sequencer like: memset(&opts, 0, sizeof(opts)); opts.action = REPLAY_FOO; opts.revisions = xmalloc(sizeof(*opts.revs)); parse_args_populate_opts(argc, argv, &opts); init_revisions(opts.revs); sequencer_pick_revisions(&opts); This patch does not intend to make any functional changes. Check with: $ git blame -s -C HEAD^..HEAD -- sequencer.c | grep -C3 '^[^^]' Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com> --- builtin/revert.c | 946 +----------------------------------------------------- sequencer.c | 918 ++++++++++++++++++++++++++++++++++++++++++++++++++++- sequencer.h | 37 +++ 3 files changed, 956 insertions(+), 945 deletions(-) diff --git a/builtin/revert.c b/builtin/revert.c index 2739405..4116f2d 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -1,18 +1,9 @@ #include "cache.h" #include "builtin.h" -#include "object.h" -#include "commit.h" -#include "tag.h" -#include "run-command.h" -#include "exec_cmd.h" -#include "utf8.h" #include "parse-options.h" -#include "cache-tree.h" #include "diff.h" #include "revision.h" #include "rerere.h" -#include "merge-recursive.h" -#include "refs.h" #include "dir.h" #include "sequencer.h" @@ -39,41 +30,6 @@ static const char * const cherry_pick_usage[] = { NULL }; -enum replay_action { - REPLAY_REVERT, - REPLAY_PICK -}; - -enum replay_subcommand { - REPLAY_NONE, - REPLAY_REMOVE_STATE, - REPLAY_CONTINUE, - REPLAY_ROLLBACK -}; - -struct replay_opts { - enum replay_action action; - enum replay_subcommand subcommand; - - /* Boolean options */ - int edit; - int record_origin; - int no_commit; - int signoff; - int allow_ff; - int allow_rerere_auto; - - int mainline; - - /* Merge strategy */ - const char *strategy; - const char **xopts; - size_t xopts_nr, xopts_alloc; - - /* Only used by REPLAY_NONE */ - struct rev_info *revs; -}; - #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" static const char *action_name(const struct replay_opts *opts) @@ -81,8 +37,6 @@ static const char *action_name(const struct replay_opts *opts) return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick"; } -static char *get_encoding(const char *message); - static const char * const *revert_or_cherry_pick_usage(struct replay_opts *opts) { return opts->action == REPLAY_REVERT ? revert_usage : cherry_pick_usage; @@ -241,902 +195,6 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) usage_with_options(usage_str, options); } -struct commit_message { - char *parent_label; - const char *label; - const char *subject; - char *reencoded_message; - const char *message; -}; - -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 (!commit->buffer) - return -1; - 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 = commit->buffer; - if (strcmp(encoding, git_commit_encoding)) - out->reencoded_message = reencode_string(commit->buffer, - 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); - - subject_len = find_commit_subject(out->message, &subject); - - out->parent_label = xmalloc(strlen("parent of ") + abbrev_len + - strlen("... ") + subject_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, subject, subject_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) -{ - const char *p = message, *eol; - - while (*p && *p != '\n') { - for (eol = p + 1; *eol && *eol != '\n'; eol++) - ; /* do nothing */ - if (!prefixcmp(p, "encoding ")) { - char *result = xmalloc(eol - 8 - p); - strlcpy(result, p + 9, eol - 8 - p); - return result; - } - p = eol; - if (*p == '\n') - p++; - } - return NULL; -} - -static void write_cherry_pick_head(struct commit *commit, const char *pseudoref) -{ - const char *filename; - int fd; - struct strbuf buf = STRBUF_INIT; - - strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1)); - - filename = git_path("%s", pseudoref); - fd = open(filename, O_WRONLY | O_CREAT, 0666); - if (fd < 0) - die_errno(_("Could not open '%s' for writing"), filename); - if (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd)) - die_errno(_("Could not write to '%s'"), filename); - strbuf_release(&buf); -} - -static void print_advice(int show_hint) -{ - char *msg = getenv("GIT_CHERRY_PICK_HELP"); - - if (msg) { - fprintf(stderr, "%s\n", msg); - /* - * A conflict has occured but the porcelain - * (typically rebase --interactive) wants to take care - * of the commit itself so remove CHERRY_PICK_HEAD - */ - unlink(git_path("CHERRY_PICK_HEAD")); - return; - } - - if (show_hint) { - advise("after resolving the conflicts, mark the corrected paths"); - advise("with 'git add <paths>' or 'git rm <paths>'"); - advise("and commit the result with 'git commit'"); - } -} - -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) -{ - return lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN); -} - -static int error_dirty_index(struct replay_opts *opts) -{ - if (read_cache_unmerged()) - return error_resolve_conflict(action_name(opts)); - - /* Different translation strings for cherry-pick and revert */ - if (opts->action == REPLAY_PICK) - error(_("Your local changes would be overwritten by cherry-pick.")); - else - error(_("Your local changes would be overwritten by revert.")); - - 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) -{ - 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 int do_recursive_merge(struct commit *base, struct commit *next, - const char *base_label, const char *next_label, - unsigned char *head, struct strbuf *msgbuf, - struct replay_opts *opts) -{ - struct merge_options o; - struct tree *result, *next_tree, *base_tree, *head_tree; - int clean, index_fd; - const char **xopt; - 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(); - - for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++) - parse_merge_opt(&o, *xopt); - - 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))) - /* TRANSLATORS: %s will be "revert" or "cherry-pick" */ - die(_("%s: Unable to write new index file"), action_name(opts)); - 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++; - } - } - } - - return !clean; -} - -/* - * If we are cherry-pick, and if the merge did not result in - * hand-editing, we will hit this commit and inherit the original - * author date and name. - * If we are revert, or if our cherry-pick results in a hand merge, - * we had better say that the current user is responsible for that. - */ -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]; - int i = 0; - - args[i++] = "commit"; - args[i++] = "-n"; - if (opts->signoff) - args[i++] = "-s"; - if (!opts->edit) { - args[i++] = "-F"; - args[i++] = defmsg; - } - args[i] = NULL; - - return run_command_v_opt(args, RUN_GIT_CMD); -} - -static int do_pick_commit(struct commit *commit, struct replay_opts *opts) -{ - unsigned char head[20]; - struct commit *base, *next, *parent; - const char *base_label, *next_label; - struct commit_message msg = { NULL, NULL, NULL, NULL, NULL }; - char *defmsg = NULL; - struct strbuf msgbuf = STRBUF_INIT; - int res; - - 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 - * that represents the "current" state for merge-recursive - * to work on. - */ - if (write_cache_as_tree(head, 0, NULL)) - die (_("Your index file is unmerged.")); - } else { - if (get_sha1("HEAD", head)) - return error(_("You do not have a valid HEAD")); - if (index_differs_from("HEAD", 0)) - return error_dirty_index(opts); - } - discard_cache(); - - if (!commit->parents) { - parent = NULL; - } - else if (commit->parents->next) { - /* Reverting or cherry-picking a merge commit */ - int cnt; - struct commit_list *p; - - 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 != opts->mainline && p; - cnt++) - p = p->next; - 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 < 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 (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 */ - return error(_("%s: cannot parse parent commit %s"), - action_name(opts), sha1_to_hex(parent->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 - * the difference it introduces since its first parent "prev" - * on top of the current HEAD if we are cherry-pick. Or the - * reverse of it if we are revert. - */ - - defmsg = git_pathdup("MERGE_MSG"); - - if (opts->action == REPLAY_REVERT) { - base = commit; - base_label = msg.label; - next = parent; - 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 && commit->parents->next) { - strbuf_addstr(&msgbuf, ", reversing\nchanges made to "); - strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1)); - } - strbuf_addstr(&msgbuf, ".\n"); - } else { - const char *p; - - base = parent; - base_label = msg.parent_label; - next = commit; - next_label = msg.label; - - /* - * 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 (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REPLAY_REVERT) { - res = do_recursive_merge(base, next, base_label, next_label, - head, &msgbuf, opts); - write_message(&msgbuf, defmsg); - } else { - 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(opts->strategy, opts->xopts_nr, opts->xopts, - common, sha1_to_hex(head), remotes); - free_commit_list(common); - free_commit_list(remotes); - } - - /* - * If the merge was clean or if it failed due to conflict, we write - * CHERRY_PICK_HEAD for the subsequent invocation of commit to use. - * However, if the merge did not even start, then we don't want to - * write it at all. - */ - if (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1)) - write_cherry_pick_head(commit, "CHERRY_PICK_HEAD"); - if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1)) - write_cherry_pick_head(commit, "REVERT_HEAD"); - - if (res) { - error(opts->action == REPLAY_REVERT - ? _("could not revert %s... %s") - : _("could not apply %s... %s"), - find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), - msg.subject); - print_advice(res == 1); - rerere(opts->allow_rerere_auto); - } else { - if (!opts->no_commit) - res = run_git_commit(defmsg, opts); - } - - free_message(&msg); - free(defmsg); - - return res; -} - -static void prepare_revs(struct replay_opts *opts) -{ - if (opts->action != REPLAY_REVERT) - opts->revs->reverse ^= 1; - - if (prepare_revision_walk(opts->revs)) - die(_("revision walk setup failed")); - - if (!opts->revs->commits) - die(_("empty commit set passed")); -} - -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"), 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"), action_name(opts)); - } - rollback_lock_file(&index_lock); -} - -/* - * 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; - const char *sha1_abbrev = NULL; - const char *action_str = opts->action == REPLAY_REVERT ? "revert" : "pick"; - const char *subject; - int subject_len; - - for (cur = todo_list; cur; cur = cur->next) { - sha1_abbrev = find_unique_abbrev(cur->item->object.sha1, DEFAULT_ABBREV); - subject_len = find_commit_subject(cur->item->buffer, &subject); - strbuf_addf(buf, "%s %s %.*s\n", action_str, sha1_abbrev, - subject_len, subject); - } - return 0; -} - -static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts *opts) -{ - unsigned char commit_sha1[20]; - enum replay_action action; - char *end_of_object_name; - int saved, status, padding; - - if (!prefixcmp(bol, "pick")) { - action = REPLAY_PICK; - bol += strlen("pick"); - } else if (!prefixcmp(bol, "revert")) { - action = REPLAY_REVERT; - bol += strlen("revert"); - } else - return NULL; - - /* Eat up extra spaces/ tabs before object name */ - padding = strspn(bol, " \t"); - if (!padding) - return NULL; - bol += padding; - - end_of_object_name = bol + strcspn(bol, " \t\n"); - saved = *end_of_object_name; - *end_of_object_name = '\0'; - status = get_sha1(bol, commit_sha1); - *end_of_object_name = saved; - - /* - * Verify that the action matches up with the one in - * opts; we don't support arbitrary instructions - */ - if (action != opts->action) { - const char *action_str; - action_str = action == REPLAY_REVERT ? "revert" : "cherry-pick"; - error(_("Cannot %s during a %s"), action_str, action_name(opts)); - return NULL; - } - - if (status < 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++) { - char *eol = strchrnul(p, '\n'); - commit = parse_insn_line(p, eol, opts); - if (!commit) - return error(_("Could not parse line %d."), i); - next = commit_list_append(commit, next); - p = *eol ? eol + 1 : eol; - } - 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 commit *commit; - struct commit_list **next; - - prepare_revs(opts); - - next = todo_list; - while ((commit = get_revision(opts->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)) { - error(_("a cherry-pick or revert is already in progress")); - advise(_("try \"git cherry-pick (--continue | --quit | --abort)\"")); - return -1; - } - 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 int reset_for_rollback(const unsigned char *sha1) -{ - const char *argv[4]; /* reset --merge <arg> + NULL */ - argv[0] = "reset"; - argv[1] = "--merge"; - argv[2] = sha1_to_hex(sha1); - argv[3] = NULL; - return run_command_v_opt(argv, RUN_GIT_CMD); -} - -static int rollback_single_pick(void) -{ - unsigned char head_sha1[20]; - - if (!file_exists(git_path("CHERRY_PICK_HEAD")) && - !file_exists(git_path("REVERT_HEAD"))) - return error(_("no cherry-pick or revert in progress")); - if (read_ref_full("HEAD", head_sha1, 0, NULL)) - return error(_("cannot resolve HEAD")); - if (is_null_sha1(head_sha1)) - return error(_("cannot abort from a branch yet to be born")); - return reset_for_rollback(head_sha1); -} - -static int sequencer_rollback(struct replay_opts *opts) -{ - const char *filename; - FILE *f; - unsigned char sha1[20]; - struct strbuf buf = STRBUF_INIT; - - filename = git_path(SEQ_HEAD_FILE); - f = fopen(filename, "r"); - if (!f && errno == ENOENT) { - /* - * There is no multiple-cherry-pick in progress. - * If CHERRY_PICK_HEAD or REVERT_HEAD indicates - * a single-cherry-pick in progress, abort that. - */ - return rollback_single_pick(); - } - if (!f) - return error(_("cannot open %s: %s"), filename, - strerror(errno)); - if (strbuf_getline(&buf, f, '\n')) { - error(_("cannot read %s: %s"), filename, ferror(f) ? - strerror(errno) : _("unexpected end of file")); - fclose(f); - goto fail; - } - fclose(f); - if (get_sha1_hex(buf.buf, sha1) || buf.buf[40] != '\0') { - error(_("stored pre-cherry-pick HEAD file '%s' is corrupt"), - filename); - goto fail; - } - if (reset_for_rollback(sha1)) - goto fail; - remove_sequencer_state(); - strbuf_release(&buf); - return 0; -fail: - strbuf_release(&buf); - return -1; -} - -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) - return res; - } - - /* - * Sequence of picks finished successfully; cleanup by - * removing the .git/sequencer directory - */ - remove_sequencer_state(); - return 0; -} - -static int continue_single_pick(void) -{ - const char *argv[] = { "commit", NULL }; - - if (!file_exists(git_path("CHERRY_PICK_HEAD")) && - !file_exists(git_path("REVERT_HEAD"))) - return error(_("no cherry-pick or revert in progress")); - return run_command_v_opt(argv, RUN_GIT_CMD); -} - -static int sequencer_continue(struct replay_opts *opts) -{ - struct commit_list *todo_list = NULL; - - if (!file_exists(git_path(SEQ_TODO_FILE))) - return continue_single_pick(); - read_populate_opts(&opts); - read_populate_todo(&todo_list, opts); - - /* Verify that the conflict has been resolved */ - if (file_exists(git_path("CHERRY_PICK_HEAD")) || - file_exists(git_path("REVERT_HEAD"))) { - int ret = continue_single_pick(); - if (ret) - return ret; - } - if (index_differs_from("HEAD", 0)) - return error_dirty_index(opts); - todo_list = todo_list->next; - return pick_commits(todo_list, opts); -} - -static int single_pick(struct commit *cmit, struct replay_opts *opts) -{ - setenv(GIT_REFLOG_ACTION, action_name(opts), 0); - return do_pick_commit(cmit, opts); -} - -static int pick_revisions(struct replay_opts *opts) -{ - struct commit_list *todo_list = NULL; - unsigned char sha1[20]; - - if (opts->subcommand == REPLAY_NONE) - assert(opts->revs); - - 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_REMOVE_STATE) { - remove_sequencer_state(); - return 0; - } - if (opts->subcommand == REPLAY_ROLLBACK) - return sequencer_rollback(opts); - if (opts->subcommand == REPLAY_CONTINUE) - return sequencer_continue(opts); - - /* - * If we were called as "git cherry-pick <commit>", just - * cherry-pick/revert it, set CHERRY_PICK_HEAD / - * REVERT_HEAD, and don't touch the sequencer state. - * This means it is possible to cherry-pick in the middle - * of a cherry-pick sequence. - */ - if (opts->revs->cmdline.nr == 1 && - opts->revs->cmdline.rev->whence == REV_CMD_REV && - opts->revs->no_walk && - !opts->revs->cmdline.rev->flags) { - struct commit *cmit; - if (prepare_revision_walk(opts->revs)) - die(_("revision walk setup failed")); - cmit = get_revision(opts->revs); - if (!cmit || get_revision(opts->revs)) - die("BUG: expected exactly one commit from walk"); - return single_pick(cmit, opts); - } - - /* - * 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) - return -1; - if (get_sha1("HEAD", sha1)) { - if (opts->action == REPLAY_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); -} - int cmd_revert(int argc, const char **argv, const char *prefix) { struct replay_opts opts; @@ -1148,7 +206,7 @@ int cmd_revert(int argc, const char **argv, const char *prefix) opts.action = REPLAY_REVERT; git_config(git_default_config, NULL); parse_args(argc, argv, &opts); - res = pick_revisions(&opts); + res = sequencer_pick_revisions(&opts); if (res < 0) die(_("revert failed")); return res; @@ -1163,7 +221,7 @@ int cmd_cherry_pick(int argc, const char **argv, const char *prefix) opts.action = REPLAY_PICK; git_config(git_default_config, NULL); parse_args(argc, argv, &opts); - res = pick_revisions(&opts); + res = sequencer_pick_revisions(&opts); if (res < 0) die(_("cherry-pick failed")); return res; diff --git a/sequencer.c b/sequencer.c index d1f28a6..5477119 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1,7 +1,20 @@ #include "cache.h" #include "sequencer.h" -#include "strbuf.h" #include "dir.h" +#include "object.h" +#include "commit.h" +#include "tag.h" +#include "run-command.h" +#include "exec_cmd.h" +#include "utf8.h" +#include "cache-tree.h" +#include "diff.h" +#include "revision.h" +#include "rerere.h" +#include "merge-recursive.h" +#include "refs.h" + +#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" void remove_sequencer_state(void) { @@ -11,3 +24,906 @@ void remove_sequencer_state(void) remove_dir_recursively(&seq_dir, 0); strbuf_release(&seq_dir); } + +static const char *action_name(const struct replay_opts *opts) +{ + return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick"; +} + +static char *get_encoding(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(struct commit *commit, struct commit_message *out) +{ + const char *encoding; + const char *abbrev, *subject; + int abbrev_len, subject_len; + char *q; + + if (!commit->buffer) + return -1; + 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 = commit->buffer; + if (strcmp(encoding, git_commit_encoding)) + out->reencoded_message = reencode_string(commit->buffer, + 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); + + subject_len = find_commit_subject(out->message, &subject); + + out->parent_label = xmalloc(strlen("parent of ") + abbrev_len + + strlen("... ") + subject_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, subject, subject_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) +{ + const char *p = message, *eol; + + while (*p && *p != '\n') { + for (eol = p + 1; *eol && *eol != '\n'; eol++) + ; /* do nothing */ + if (!prefixcmp(p, "encoding ")) { + char *result = xmalloc(eol - 8 - p); + strlcpy(result, p + 9, eol - 8 - p); + return result; + } + p = eol; + if (*p == '\n') + p++; + } + return NULL; +} + +static void write_cherry_pick_head(struct commit *commit, const char *pseudoref) +{ + const char *filename; + int fd; + struct strbuf buf = STRBUF_INIT; + + strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1)); + + filename = git_path("%s", pseudoref); + fd = open(filename, O_WRONLY | O_CREAT, 0666); + if (fd < 0) + die_errno(_("Could not open '%s' for writing"), filename); + if (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd)) + die_errno(_("Could not write to '%s'"), filename); + strbuf_release(&buf); +} + +static void print_advice(int show_hint) +{ + char *msg = getenv("GIT_CHERRY_PICK_HELP"); + + if (msg) { + fprintf(stderr, "%s\n", msg); + /* + * A conflict has occured but the porcelain + * (typically rebase --interactive) wants to take care + * of the commit itself so remove CHERRY_PICK_HEAD + */ + unlink(git_path("CHERRY_PICK_HEAD")); + return; + } + + if (show_hint) { + advise("after resolving the conflicts, mark the corrected paths"); + advise("with 'git add <paths>' or 'git rm <paths>'"); + advise("and commit the result with 'git commit'"); + } +} + +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) +{ + return lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN); +} + +static int error_dirty_index(struct replay_opts *opts) +{ + if (read_cache_unmerged()) + return error_resolve_conflict(action_name(opts)); + + /* Different translation strings for cherry-pick and revert */ + if (opts->action == REPLAY_PICK) + error(_("Your local changes would be overwritten by cherry-pick.")); + else + error(_("Your local changes would be overwritten by revert.")); + + 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) +{ + 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 int do_recursive_merge(struct commit *base, struct commit *next, + const char *base_label, const char *next_label, + unsigned char *head, struct strbuf *msgbuf, + struct replay_opts *opts) +{ + struct merge_options o; + struct tree *result, *next_tree, *base_tree, *head_tree; + int clean, index_fd; + const char **xopt; + 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(); + + for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++) + parse_merge_opt(&o, *xopt); + + 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))) + /* TRANSLATORS: %s will be "revert" or "cherry-pick" */ + die(_("%s: Unable to write new index file"), action_name(opts)); + 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++; + } + } + } + + return !clean; +} + +/* + * If we are cherry-pick, and if the merge did not result in + * hand-editing, we will hit this commit and inherit the original + * author date and name. + * If we are revert, or if our cherry-pick results in a hand merge, + * we had better say that the current user is responsible for that. + */ +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]; + int i = 0; + + args[i++] = "commit"; + args[i++] = "-n"; + if (opts->signoff) + args[i++] = "-s"; + if (!opts->edit) { + args[i++] = "-F"; + args[i++] = defmsg; + } + args[i] = NULL; + + return run_command_v_opt(args, RUN_GIT_CMD); +} + +static int do_pick_commit(struct commit *commit, struct replay_opts *opts) +{ + unsigned char head[20]; + struct commit *base, *next, *parent; + const char *base_label, *next_label; + struct commit_message msg = { NULL, NULL, NULL, NULL, NULL }; + char *defmsg = NULL; + struct strbuf msgbuf = STRBUF_INIT; + int res; + + 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 + * that represents the "current" state for merge-recursive + * to work on. + */ + if (write_cache_as_tree(head, 0, NULL)) + die (_("Your index file is unmerged.")); + } else { + if (get_sha1("HEAD", head)) + return error(_("You do not have a valid HEAD")); + if (index_differs_from("HEAD", 0)) + return error_dirty_index(opts); + } + discard_cache(); + + if (!commit->parents) { + parent = NULL; + } + else if (commit->parents->next) { + /* Reverting or cherry-picking a merge commit */ + int cnt; + struct commit_list *p; + + 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 != opts->mainline && p; + cnt++) + p = p->next; + 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 < 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 (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 */ + return error(_("%s: cannot parse parent commit %s"), + action_name(opts), sha1_to_hex(parent->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 + * the difference it introduces since its first parent "prev" + * on top of the current HEAD if we are cherry-pick. Or the + * reverse of it if we are revert. + */ + + defmsg = git_pathdup("MERGE_MSG"); + + if (opts->action == REPLAY_REVERT) { + base = commit; + base_label = msg.label; + next = parent; + 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 && commit->parents->next) { + strbuf_addstr(&msgbuf, ", reversing\nchanges made to "); + strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1)); + } + strbuf_addstr(&msgbuf, ".\n"); + } else { + const char *p; + + base = parent; + base_label = msg.parent_label; + next = commit; + next_label = msg.label; + + /* + * 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 (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REPLAY_REVERT) { + res = do_recursive_merge(base, next, base_label, next_label, + head, &msgbuf, opts); + write_message(&msgbuf, defmsg); + } else { + 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(opts->strategy, opts->xopts_nr, opts->xopts, + common, sha1_to_hex(head), remotes); + free_commit_list(common); + free_commit_list(remotes); + } + + /* + * If the merge was clean or if it failed due to conflict, we write + * CHERRY_PICK_HEAD for the subsequent invocation of commit to use. + * However, if the merge did not even start, then we don't want to + * write it at all. + */ + if (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1)) + write_cherry_pick_head(commit, "CHERRY_PICK_HEAD"); + if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1)) + write_cherry_pick_head(commit, "REVERT_HEAD"); + + if (res) { + error(opts->action == REPLAY_REVERT + ? _("could not revert %s... %s") + : _("could not apply %s... %s"), + find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), + msg.subject); + print_advice(res == 1); + rerere(opts->allow_rerere_auto); + } else { + if (!opts->no_commit) + res = run_git_commit(defmsg, opts); + } + + free_message(&msg); + free(defmsg); + + return res; +} + +static void prepare_revs(struct replay_opts *opts) +{ + if (opts->action != REPLAY_REVERT) + opts->revs->reverse ^= 1; + + if (prepare_revision_walk(opts->revs)) + die(_("revision walk setup failed")); + + if (!opts->revs->commits) + die(_("empty commit set passed")); +} + +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"), 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"), action_name(opts)); + } + rollback_lock_file(&index_lock); +} + +/* + * 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; + const char *sha1_abbrev = NULL; + const char *action_str = opts->action == REPLAY_REVERT ? "revert" : "pick"; + const char *subject; + int subject_len; + + for (cur = todo_list; cur; cur = cur->next) { + sha1_abbrev = find_unique_abbrev(cur->item->object.sha1, DEFAULT_ABBREV); + subject_len = find_commit_subject(cur->item->buffer, &subject); + strbuf_addf(buf, "%s %s %.*s\n", action_str, sha1_abbrev, + subject_len, subject); + } + return 0; +} + +static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts *opts) +{ + unsigned char commit_sha1[20]; + enum replay_action action; + char *end_of_object_name; + int saved, status, padding; + + if (!prefixcmp(bol, "pick")) { + action = REPLAY_PICK; + bol += strlen("pick"); + } else if (!prefixcmp(bol, "revert")) { + action = REPLAY_REVERT; + bol += strlen("revert"); + } else + return NULL; + + /* Eat up extra spaces/ tabs before object name */ + padding = strspn(bol, " \t"); + if (!padding) + return NULL; + bol += padding; + + end_of_object_name = bol + strcspn(bol, " \t\n"); + saved = *end_of_object_name; + *end_of_object_name = '\0'; + status = get_sha1(bol, commit_sha1); + *end_of_object_name = saved; + + /* + * Verify that the action matches up with the one in + * opts; we don't support arbitrary instructions + */ + if (action != opts->action) { + const char *action_str; + action_str = action == REPLAY_REVERT ? "revert" : "cherry-pick"; + error(_("Cannot %s during a %s"), action_str, action_name(opts)); + return NULL; + } + + if (status < 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++) { + char *eol = strchrnul(p, '\n'); + commit = parse_insn_line(p, eol, opts); + if (!commit) + return error(_("Could not parse line %d."), i); + next = commit_list_append(commit, next); + p = *eol ? eol + 1 : eol; + } + 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 commit *commit; + struct commit_list **next; + + prepare_revs(opts); + + next = todo_list; + while ((commit = get_revision(opts->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)) { + error(_("a cherry-pick or revert is already in progress")); + advise(_("try \"git cherry-pick (--continue | --quit | --abort)\"")); + return -1; + } + 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 int reset_for_rollback(const unsigned char *sha1) +{ + const char *argv[4]; /* reset --merge <arg> + NULL */ + argv[0] = "reset"; + argv[1] = "--merge"; + argv[2] = sha1_to_hex(sha1); + argv[3] = NULL; + return run_command_v_opt(argv, RUN_GIT_CMD); +} + +static int rollback_single_pick(void) +{ + unsigned char head_sha1[20]; + + if (!file_exists(git_path("CHERRY_PICK_HEAD")) && + !file_exists(git_path("REVERT_HEAD"))) + return error(_("no cherry-pick or revert in progress")); + if (read_ref_full("HEAD", head_sha1, 0, NULL)) + return error(_("cannot resolve HEAD")); + if (is_null_sha1(head_sha1)) + return error(_("cannot abort from a branch yet to be born")); + return reset_for_rollback(head_sha1); +} + +static int sequencer_rollback(struct replay_opts *opts) +{ + const char *filename; + FILE *f; + unsigned char sha1[20]; + struct strbuf buf = STRBUF_INIT; + + filename = git_path(SEQ_HEAD_FILE); + f = fopen(filename, "r"); + if (!f && errno == ENOENT) { + /* + * There is no multiple-cherry-pick in progress. + * If CHERRY_PICK_HEAD or REVERT_HEAD indicates + * a single-cherry-pick in progress, abort that. + */ + return rollback_single_pick(); + } + if (!f) + return error(_("cannot open %s: %s"), filename, + strerror(errno)); + if (strbuf_getline(&buf, f, '\n')) { + error(_("cannot read %s: %s"), filename, ferror(f) ? + strerror(errno) : _("unexpected end of file")); + fclose(f); + goto fail; + } + fclose(f); + if (get_sha1_hex(buf.buf, sha1) || buf.buf[40] != '\0') { + error(_("stored pre-cherry-pick HEAD file '%s' is corrupt"), + filename); + goto fail; + } + if (reset_for_rollback(sha1)) + goto fail; + remove_sequencer_state(); + strbuf_release(&buf); + return 0; +fail: + strbuf_release(&buf); + return -1; +} + +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) + return res; + } + + /* + * Sequence of picks finished successfully; cleanup by + * removing the .git/sequencer directory + */ + remove_sequencer_state(); + return 0; +} + +static int continue_single_pick(void) +{ + const char *argv[] = { "commit", NULL }; + + if (!file_exists(git_path("CHERRY_PICK_HEAD")) && + !file_exists(git_path("REVERT_HEAD"))) + return error(_("no cherry-pick or revert in progress")); + return run_command_v_opt(argv, RUN_GIT_CMD); +} + +static int sequencer_continue(struct replay_opts *opts) +{ + struct commit_list *todo_list = NULL; + + if (!file_exists(git_path(SEQ_TODO_FILE))) + return continue_single_pick(); + read_populate_opts(&opts); + read_populate_todo(&todo_list, opts); + + /* Verify that the conflict has been resolved */ + if (file_exists(git_path("CHERRY_PICK_HEAD")) || + file_exists(git_path("REVERT_HEAD"))) { + int ret = continue_single_pick(); + if (ret) + return ret; + } + if (index_differs_from("HEAD", 0)) + return error_dirty_index(opts); + todo_list = todo_list->next; + return pick_commits(todo_list, opts); +} + +static int single_pick(struct commit *cmit, struct replay_opts *opts) +{ + setenv(GIT_REFLOG_ACTION, action_name(opts), 0); + return do_pick_commit(cmit, opts); +} + +int sequencer_pick_revisions(struct replay_opts *opts) +{ + struct commit_list *todo_list = NULL; + unsigned char sha1[20]; + + if (opts->subcommand == REPLAY_NONE) + assert(opts->revs); + + 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_REMOVE_STATE) { + remove_sequencer_state(); + return 0; + } + if (opts->subcommand == REPLAY_ROLLBACK) + return sequencer_rollback(opts); + if (opts->subcommand == REPLAY_CONTINUE) + return sequencer_continue(opts); + + /* + * If we were called as "git cherry-pick <commit>", just + * cherry-pick/revert it, set CHERRY_PICK_HEAD / + * REVERT_HEAD, and don't touch the sequencer state. + * This means it is possible to cherry-pick in the middle + * of a cherry-pick sequence. + */ + if (opts->revs->cmdline.nr == 1 && + opts->revs->cmdline.rev->whence == REV_CMD_REV && + opts->revs->no_walk && + !opts->revs->cmdline.rev->flags) { + struct commit *cmit; + if (prepare_revision_walk(opts->revs)) + die(_("revision walk setup failed")); + cmit = get_revision(opts->revs); + if (!cmit || get_revision(opts->revs)) + die("BUG: expected exactly one commit from walk"); + return single_pick(cmit, opts); + } + + /* + * 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) + return -1; + if (get_sha1("HEAD", sha1)) { + if (opts->action == REPLAY_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); +} diff --git a/sequencer.h b/sequencer.h index 2d4528f..bb4b138 100644 --- a/sequencer.h +++ b/sequencer.h @@ -6,7 +6,44 @@ #define SEQ_TODO_FILE "sequencer/todo" #define SEQ_OPTS_FILE "sequencer/opts" +enum replay_action { + REPLAY_REVERT, + REPLAY_PICK +}; + +enum replay_subcommand { + REPLAY_NONE, + REPLAY_REMOVE_STATE, + REPLAY_CONTINUE, + REPLAY_ROLLBACK +}; + +struct replay_opts { + enum replay_action action; + enum replay_subcommand subcommand; + + /* Boolean options */ + int edit; + int record_origin; + int no_commit; + int signoff; + int allow_ff; + int allow_rerere_auto; + + int mainline; + + /* Merge strategy */ + const char *strategy; + const char **xopts; + size_t xopts_nr, xopts_alloc; + + /* Only used by REPLAY_NONE */ + struct rev_info *revs; +}; + /* Removes SEQ_DIR. */ extern void remove_sequencer_state(void); +int sequencer_pick_revisions(struct replay_opts *opts); + #endif -- 1.7.8.2 ^ permalink raw reply related [flat|nested] 64+ messages in thread
* Re: [PATCH v3 0/2] The move to sequencer.c 2012-01-11 18:15 ` [PATCH v3 0/2] The move to sequencer.c Ramkumar Ramachandra 2012-01-11 18:15 ` [PATCH 1/2] revert: prepare to move replay_action to header Ramkumar Ramachandra 2012-01-11 18:15 ` [PATCH 2/2] sequencer: factor code out of revert builtin Ramkumar Ramachandra @ 2012-01-11 18:40 ` Jonathan Nieder 2 siblings, 0 replies; 64+ messages in thread From: Jonathan Nieder @ 2012-01-11 18:40 UTC (permalink / raw) To: Junio C Hamano; +Cc: Ramkumar Ramachandra, Git List, Christian Couder Ramkumar Ramachandra wrote: > Ramkumar Ramachandra (2): > revert: prepare to move replay_action to header > sequencer: factor code out of revert builtin Ah. "git diff HEAD^:builtin/revert.c HEAD:sequencer.c" gives a sane diff, and the remaining stuff in builtin/revert.c feels pleasant. Reviewed-by: Jonathan Nieder <jrnieder@gmail.com> Here's a patchlet for squashing into patch 2/2. --- builtin/revert.c | 2 -- 1 files changed, 0 insertions(+), 2 deletions(-) diff --git a/builtin/revert.c b/builtin/revert.c index 4116f2d3..e6840f23 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -30,8 +30,6 @@ static const char * const cherry_pick_usage[] = { NULL }; -#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" - static const char *action_name(const struct replay_opts *opts) { return opts->action == REPLAY_REVERT ? "revert" : "cherry-pick"; -- ^ permalink raw reply related [flat|nested] 64+ messages in thread
* [PATCH 3/8] revert: allow mixing "pick" and "revert" actions 2012-01-10 16:13 ` [PATCH v2 0/8] " Ramkumar Ramachandra 2012-01-10 16:13 ` [PATCH 1/8] revert: prepare to move replay_action to header Ramkumar Ramachandra 2012-01-10 16:13 ` [PATCH 2/8] revert: decouple sequencer actions from builtin commands Ramkumar Ramachandra @ 2012-01-10 16:13 ` Ramkumar Ramachandra 2012-01-10 16:13 ` [PATCH 4/8] revert: separate out parse errors logically Ramkumar Ramachandra ` (4 subsequent siblings) 7 siblings, 0 replies; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-10 16:13 UTC (permalink / raw) To: Git List; +Cc: Junio C Hamano, Jonathan Nieder Parse the instruction sheet in '.git/sequencer/todo' as a list of (action, operand) pairs, instead of assuming that all lines have the same action. Now, an instruction sheet like the following is perfectly valid: pick fdc0b12 picked revert 965fed4 anotherpick The operator can use this feature by hand-editing the instruction sheet and using '--continue' as appropriate: $ git cherry-pick foo..bar [conflict occurs] $ edit problematicfile $ git add problematicfile $ edit .git/sequencer/todo $ git revert --continue [finishes successfully] Consequently, this means that a 'git cherry-pick --continue' can continue an ongoing 'git revert' operation, and viceversa. Helped-by: Jonathan Nieder <jrnider@gmail.com> Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com> --- builtin/revert.c | 142 ++++++++++++++++++++------------------- t/t3510-cherry-pick-sequence.sh | 46 +++++++++---- 2 files changed, 105 insertions(+), 83 deletions(-) diff --git a/builtin/revert.c b/builtin/revert.c index 9bca9c7..1841ffa 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -56,6 +56,12 @@ enum replay_subcommand { REPLAY_ROLLBACK }; +struct replay_insn_list { + struct commit *operand; + enum replay_action action; + struct replay_insn_list *next; +}; + struct replay_opts { enum replay_command command; enum replay_subcommand subcommand; @@ -487,7 +493,8 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts) return run_command_v_opt(args, RUN_GIT_CMD); } -static int do_pick_commit(struct commit *commit, struct replay_opts *opts) +static int do_pick_commit(struct commit *commit, enum replay_action action, + struct replay_opts *opts) { unsigned char head[20]; struct commit *base, *next, *parent; @@ -562,7 +569,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) defmsg = git_pathdup("MERGE_MSG"); - if (opts->command == REPLAY_CMD_REVERT) { + if (action == REPLAY_REVERT) { base = commit; base_label = msg.label; next = parent; @@ -603,7 +610,7 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) } } - if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->command == REPLAY_CMD_REVERT) { + if (!opts->strategy || !strcmp(opts->strategy, "recursive") || action == REPLAY_REVERT) { res = do_recursive_merge(base, next, base_label, next_label, head, &msgbuf, opts); write_message(&msgbuf, defmsg); @@ -627,13 +634,13 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) * However, if the merge did not even start, then we don't want to * write it at all. */ - if (opts->command == REPLAY_CMD_CHERRY_PICK && !opts->no_commit && (res == 0 || res == 1)) + if (action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1)) write_cherry_pick_head(commit, "CHERRY_PICK_HEAD"); - if (opts->command == REPLAY_CMD_REVERT && ((opts->no_commit && res == 0) || res == 1)) + if (action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1)) write_cherry_pick_head(commit, "REVERT_HEAD"); if (res) { - error(opts->command == REPLAY_CMD_REVERT + error(action == REPLAY_REVERT ? _("could not revert %s... %s") : _("could not apply %s... %s"), find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), @@ -680,70 +687,70 @@ static void read_and_refresh_cache(struct replay_opts *opts) } /* - * Append a commit to the end of the commit_list. + * Append a (commit, action) to the end of the replay_insn_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. + * empty replay_insn_list, and is updated to point to the "next" field of + * the last item on the list as new (commit, action) pairs are appended. * * Usage example: * - * struct commit_list *list; - * struct commit_list **next = &list; + * struct replay_insn_list *list; + * struct replay_insn_list **next = &list; * - * next = commit_list_append(c1, next); - * next = commit_list_append(c2, next); - * assert(commit_list_count(list) == 2); + * next = replay_insn_list_append(c1, a1, next); + * next = replay_insn_list_append(c2, a2, next); + * assert(len(list) == 2); * return list; */ -static struct commit_list **commit_list_append(struct commit *commit, - struct commit_list **next) +static struct replay_insn_list **replay_insn_list_append(struct commit *operand, + enum replay_action action, + struct replay_insn_list **next) { - struct commit_list *new = xmalloc(sizeof(struct commit_list)); - new->item = commit; + struct replay_insn_list *new = xmalloc(sizeof(*new)); + new->action = action; + new->operand = operand; *next = new; new->next = NULL; return &new->next; } -static int format_todo(struct strbuf *buf, struct commit_list *todo_list, - struct replay_opts *opts) +static int format_todo(struct strbuf *buf, struct replay_insn_list *todo_list) { - struct commit_list *cur = NULL; - const char *sha1_abbrev = NULL; - const char *action_str = opts->command == REPLAY_CMD_REVERT ? "revert" : "pick"; - const char *subject; - int subject_len; + struct replay_insn_list *cur; for (cur = todo_list; cur; cur = cur->next) { - sha1_abbrev = find_unique_abbrev(cur->item->object.sha1, DEFAULT_ABBREV); - subject_len = find_commit_subject(cur->item->buffer, &subject); + const char *sha1_abbrev, *action_str, *subject; + int subject_len; + + action_str = cur->action == REPLAY_REVERT ? "revert" : "pick"; + sha1_abbrev = find_unique_abbrev(cur->operand->object.sha1, DEFAULT_ABBREV); + subject_len = find_commit_subject(cur->operand->buffer, &subject); strbuf_addf(buf, "%s %s %.*s\n", action_str, sha1_abbrev, subject_len, subject); } return 0; } -static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts *opts) +static int parse_insn_line(char *bol, char *eol, struct replay_insn_list *item) { unsigned char commit_sha1[20]; - enum replay_action action; char *end_of_object_name; int saved, status, padding; if (!prefixcmp(bol, "pick")) { - action = REPLAY_PICK; + item->action = REPLAY_PICK; bol += strlen("pick"); } else if (!prefixcmp(bol, "revert")) { - action = REPLAY_REVERT; + item->action = REPLAY_REVERT; bol += strlen("revert"); } else - return NULL; + return -1; /* Eat up extra spaces/ tabs before object name */ padding = strspn(bol, " \t"); if (!padding) - return NULL; + return -1; bol += padding; end_of_object_name = bol + strcspn(bol, " \t\n"); @@ -752,38 +759,29 @@ static struct commit *parse_insn_line(char *bol, char *eol, struct replay_opts * status = get_sha1(bol, commit_sha1); *end_of_object_name = saved; - /* - * Verify that the action matches up with the one in - * opts; we don't support arbitrary instructions - */ - if ((action == REPLAY_PICK && opts->command == REPLAY_CMD_REVERT) || - (action == REPLAY_REVERT && opts->command == REPLAY_CMD_CHERRY_PICK)) { - error(_("Cannot %s during a %s"), - action == REPLAY_REVERT ? "revert" : "pick", - command_name(opts)); - return NULL; - } - if (status < 0) - return NULL; + return -1; + + item->operand = lookup_commit_reference(commit_sha1); + if (!item->operand) + return -1; - return lookup_commit_reference(commit_sha1); + item->next = NULL; + return 0; } -static int parse_insn_buffer(char *buf, struct commit_list **todo_list, - struct replay_opts *opts) +static int parse_insn_buffer(char *buf, struct replay_insn_list **todo_list) { - struct commit_list **next = todo_list; - struct commit *commit; + struct replay_insn_list **next = todo_list; + struct replay_insn_list item = { NULL, 0, NULL }; char *p = buf; int i; for (i = 1; *p; i++) { char *eol = strchrnul(p, '\n'); - commit = parse_insn_line(p, eol, opts); - if (!commit) + if (parse_insn_line(p, eol, &item)) return error(_("Could not parse line %d."), i); - next = commit_list_append(commit, next); + next = replay_insn_list_append(item.operand, item.action, next); p = *eol ? eol + 1 : eol; } if (!*todo_list) @@ -791,8 +789,7 @@ static int parse_insn_buffer(char *buf, struct commit_list **todo_list, return 0; } -static void read_populate_todo(struct commit_list **todo_list, - struct replay_opts *opts) +static void read_populate_todo(struct replay_insn_list **todo_list) { const char *todo_file = git_path(SEQ_TODO_FILE); struct strbuf buf = STRBUF_INIT; @@ -808,7 +805,7 @@ static void read_populate_todo(struct commit_list **todo_list, } close(fd); - res = parse_insn_buffer(buf.buf, todo_list, opts); + res = parse_insn_buffer(buf.buf, todo_list); strbuf_release(&buf); if (res) die(_("Unusable instruction sheet: %s"), todo_file); @@ -857,17 +854,19 @@ static void read_populate_opts(struct replay_opts **opts_ptr) die(_("Malformed options sheet: %s"), opts_file); } -static void walk_revs_populate_todo(struct commit_list **todo_list, +static void walk_revs_populate_todo(struct replay_insn_list **todo_list, struct replay_opts *opts) { struct commit *commit; - struct commit_list **next; + struct replay_insn_list **next; + enum replay_action action; prepare_revs(opts); next = todo_list; + action = opts->command == REPLAY_CMD_REVERT ? REPLAY_REVERT : REPLAY_PICK; while ((commit = get_revision(opts->revs))) - next = commit_list_append(commit, next); + next = replay_insn_list_append(commit, action, next); } static int create_seq_dir(void) @@ -965,7 +964,7 @@ fail: return -1; } -static void save_todo(struct commit_list *todo_list, struct replay_opts *opts) +static void save_todo(struct replay_insn_list *todo_list) { const char *todo_file = git_path(SEQ_TODO_FILE); static struct lock_file todo_lock; @@ -973,7 +972,7 @@ static void save_todo(struct commit_list *todo_list, struct replay_opts *opts) int fd; fd = hold_lock_file_for_update(&todo_lock, todo_file, LOCK_DIE_ON_ERROR); - if (format_todo(&buf, todo_list, opts) < 0) + if (format_todo(&buf, todo_list) < 0) die(_("Could not format %s."), todo_file); if (write_in_full(fd, buf.buf, buf.len) < 0) { strbuf_release(&buf); @@ -1017,9 +1016,9 @@ static void save_opts(struct replay_opts *opts) } } -static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts) +static int pick_commits(struct replay_insn_list *todo_list, struct replay_opts *opts) { - struct commit_list *cur; + struct replay_insn_list *cur; int res; setenv(GIT_REFLOG_ACTION, command_name(opts), 0); @@ -1029,8 +1028,8 @@ static int pick_commits(struct commit_list *todo_list, struct replay_opts *opts) read_and_refresh_cache(opts); for (cur = todo_list; cur; cur = cur->next) { - save_todo(cur, opts); - res = do_pick_commit(cur->item, opts); + save_todo(cur); + res = do_pick_commit(cur->operand, cur->action, opts); if (res) return res; } @@ -1055,12 +1054,12 @@ static int continue_single_pick(void) static int sequencer_continue(struct replay_opts *opts) { - struct commit_list *todo_list = NULL; + struct replay_insn_list *todo_list = NULL; if (!file_exists(git_path(SEQ_TODO_FILE))) return continue_single_pick(); read_populate_opts(&opts); - read_populate_todo(&todo_list, opts); + read_populate_todo(&todo_list); /* Verify that the conflict has been resolved */ if (file_exists(git_path("CHERRY_PICK_HEAD")) || @@ -1077,13 +1076,16 @@ static int sequencer_continue(struct replay_opts *opts) static int single_pick(struct commit *cmit, struct replay_opts *opts) { + enum replay_action action; + action = opts->command == REPLAY_CMD_REVERT ? REPLAY_REVERT : REPLAY_PICK; + setenv(GIT_REFLOG_ACTION, command_name(opts), 0); - return do_pick_commit(cmit, opts); + return do_pick_commit(cmit, action, opts); } static int pick_revisions(struct replay_opts *opts) { - struct commit_list *todo_list = NULL; + struct replay_insn_list *todo_list = NULL; unsigned char sha1[20]; if (opts->subcommand == REPLAY_NONE) diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh index 97f3710..af747c8 100755 --- a/t/t3510-cherry-pick-sequence.sh +++ b/t/t3510-cherry-pick-sequence.sh @@ -454,7 +454,7 @@ test_expect_success 'sign-off needs to be reaffirmed after conflict resolution, ! grep Signed-off-by: msg ' -test_expect_success 'malformed instruction sheet 1' ' +test_expect_success 'malformed instruction sheet, action' ' pristine_detach initial && test_expect_code 1 git cherry-pick base..anotherpick && echo "resolved" >foo && @@ -465,23 +465,12 @@ test_expect_success 'malformed instruction sheet 1' ' test_expect_code 128 git cherry-pick --continue ' -test_expect_success 'malformed instruction sheet 2' ' - pristine_detach initial && - test_expect_code 1 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_expect_code 128 git cherry-pick --continue -' - test_expect_success 'empty commit set' ' pristine_detach initial && test_expect_code 128 git cherry-pick base..base ' -test_expect_success 'malformed instruction sheet 3' ' +test_expect_success 'malformed instruction sheet, object name' ' pristine_detach initial && test_expect_code 1 git cherry-pick base..anotherpick && echo "resolved" >foo && @@ -517,4 +506,35 @@ test_expect_success 'commit descriptions in insn sheet are optional' ' test_line_count = 4 commits ' +test_expect_success 'mixed pick and revert instructions' ' + pristine_detach initial && + test_expect_code 1 git cherry-pick base..anotherpick && + echo "c" >foo && + git add foo && + git commit && + oldsha=`git rev-parse --short HEAD~1` && + echo "revert $oldsha unrelatedpick" >>.git/sequencer/todo && + git revert --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 unrelated + 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_done -- 1.7.8.2 ^ permalink raw reply related [flat|nested] 64+ messages in thread
* [PATCH 4/8] revert: separate out parse errors logically 2012-01-10 16:13 ` [PATCH v2 0/8] " Ramkumar Ramachandra ` (2 preceding siblings ...) 2012-01-10 16:13 ` [PATCH 3/8] revert: allow mixing "pick" and "revert" actions Ramkumar Ramachandra @ 2012-01-10 16:13 ` Ramkumar Ramachandra 2012-01-10 19:03 ` Jonathan Nieder 2012-01-10 16:13 ` [PATCH 5/8] revert: report fine-grained errors from insn parser Ramkumar Ramachandra ` (3 subsequent siblings) 7 siblings, 1 reply; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-10 16:13 UTC (permalink / raw) To: Git List; +Cc: Junio C Hamano, Jonathan Nieder Three kinds of errors can arise from parsing the instruction sheet: 1. Unrecognized action 2. Malformed object name 3. Object name does not refer to a valid commit The next patch makes an attempt to make the parser report meaningful errors by replacing the "return -1" with "return error(...)" appropriately. For the first kind of error, it is insufficient to check if the buffer beings with a "pick" or "revert", otherwise the following insn sheet would be interpreted as having a malformed object name: pickle a1fe57~2 In reality, the issue is that "pickle" is an unrecognized instruction. So, check that the buffer starts with ("pick " or "pick\t") and ("revert " or "revert\t"). Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com> --- builtin/revert.c | 15 ++++++--------- 1 files changed, 6 insertions(+), 9 deletions(-) diff --git a/builtin/revert.c b/builtin/revert.c index 1841ffa..9a09471 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -736,22 +736,19 @@ static int parse_insn_line(char *bol, char *eol, struct replay_insn_list *item) { unsigned char commit_sha1[20]; char *end_of_object_name; - int saved, status, padding; + int saved, status; - if (!prefixcmp(bol, "pick")) { + if (!prefixcmp(bol, "pick ") || !prefixcmp(bol, "pick\t")) { item->action = REPLAY_PICK; - bol += strlen("pick"); - } else if (!prefixcmp(bol, "revert")) { + bol += strlen("pick "); + } else if (!prefixcmp(bol, "revert ") || !prefixcmp(bol, "revert\t")) { item->action = REPLAY_REVERT; - bol += strlen("revert"); + bol += strlen("revert "); } else return -1; /* Eat up extra spaces/ tabs before object name */ - padding = strspn(bol, " \t"); - if (!padding) - return -1; - bol += padding; + bol += strspn(bol, " \t"); end_of_object_name = bol + strcspn(bol, " \t\n"); saved = *end_of_object_name; -- 1.7.8.2 ^ permalink raw reply related [flat|nested] 64+ messages in thread
* Re: [PATCH 4/8] revert: separate out parse errors logically 2012-01-10 16:13 ` [PATCH 4/8] revert: separate out parse errors logically Ramkumar Ramachandra @ 2012-01-10 19:03 ` Jonathan Nieder 2012-01-11 12:38 ` Ramkumar Ramachandra 0 siblings, 1 reply; 64+ messages in thread From: Jonathan Nieder @ 2012-01-10 19:03 UTC (permalink / raw) To: Ramkumar Ramachandra; +Cc: Git List, Junio C Hamano Ramkumar Ramachandra wrote: > For the first kind of error, it is insufficient to > check if the buffer beings with a "pick" or "revert", otherwise the > following insn sheet would be interpreted as having a malformed object > name: > pickle a1fe57~2 > > In reality, the issue is that "pickle" is an unrecognized instruction. > So, check that the buffer starts with ("pick " or "pick\t") and > ("revert " or "revert\t"). Sorry, the above description just leaves me more confused than before. What _actual impact_ does this patch have? And why do we want it? And what could be the bad side effects? Everything else is just irrelevant. Before reading the above description, I thought this was just a code cleanup. So either the description or my reading is completely confused. ^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH 4/8] revert: separate out parse errors logically 2012-01-10 19:03 ` Jonathan Nieder @ 2012-01-11 12:38 ` Ramkumar Ramachandra 0 siblings, 0 replies; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-11 12:38 UTC (permalink / raw) To: Jonathan Nieder; +Cc: Git List, Junio C Hamano New commit message. revert: simplify insn parsing logic Our instruction sheet parser first looks for a valid action by checking that the buffer starts with either "pick" or "revert". Then, it looks for either spaces or tabs before looking for the object name, erroring out if it doesn't find any. Simplify this logic without making any functional changes by looking for ("pick " or "pick\t") or ("revert " or "revert\t") in the first place. -- Ram ^ permalink raw reply [flat|nested] 64+ messages in thread
* [PATCH 5/8] revert: report fine-grained errors from insn parser 2012-01-10 16:13 ` [PATCH v2 0/8] " Ramkumar Ramachandra ` (3 preceding siblings ...) 2012-01-10 16:13 ` [PATCH 4/8] revert: separate out parse errors logically Ramkumar Ramachandra @ 2012-01-10 16:13 ` Ramkumar Ramachandra 2012-01-11 12:44 ` Jonathan Nieder 2012-01-10 16:13 ` [PATCH 6/8] sha1_name: introduce getn_sha1() to take length Ramkumar Ramachandra ` (2 subsequent siblings) 7 siblings, 1 reply; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-10 16:13 UTC (permalink / raw) To: Git List; +Cc: Junio C Hamano, Jonathan Nieder The infrastructure that parses '.git/sequencer/todo' is meant to handle arbitrary user input some day, so it can be used as the implementation of 'git rebase --interactive' and 'git sequence --edit'. It is currently sub-optimal for that purpose because the parse error messages just say: error: Could not parse line 5. This patch shifts responsibility to parse_insn_line(), which can come up with a more detailed message like: error: .git/sequencer/todo:5: unrecognized action: frobnicate Once the operator is allowed to edit the sequence, the message might be adjusted to something like: error: <sequence you just gave me>:5: unrecognized action: frobnicate instead of exposing an implementation detail. Some day "git sequence --edit" could even re-launch the editor with an error message in a comment before the problematic line and the cursor pointing there. For now, pointing to the explicit filename is useful since this should only happen if there was filesystem corruption, tampering, or a git bug. Helped-by: Junio C Hamano <gitster@pobox.com> Helped-by: Jonathan Nieder <jrnieder@gmail.com> Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com> --- builtin/revert.c | 30 ++++++++++++++++++++++++------ 1 files changed, 24 insertions(+), 6 deletions(-) diff --git a/builtin/revert.c b/builtin/revert.c index 9a09471..0954d22 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -732,7 +732,22 @@ static int format_todo(struct strbuf *buf, struct replay_insn_list *todo_list) return 0; } -static int parse_insn_line(char *bol, char *eol, struct replay_insn_list *item) +static int parse_error(const char *message, const char *file, + int lineno, char *error_line) +{ + const char *suffix = ""; + int error_len = strcspn(error_line, " \t\n"); + + if (error_len > 20) { + error_len = 20; + suffix = "..."; + } + return error(_("%s:%d: %s: %.*s%s"), file, lineno, message, + error_len, error_line, suffix); +} + +static int parse_insn_line(char *bol, char *eol, + struct replay_insn_list *item, int lineno) { unsigned char commit_sha1[20]; char *end_of_object_name; @@ -745,7 +760,8 @@ static int parse_insn_line(char *bol, char *eol, struct replay_insn_list *item) item->action = REPLAY_REVERT; bol += strlen("revert "); } else - return -1; + return parse_error(_("unrecognized action"), + git_path(SEQ_TODO_FILE), lineno, bol); /* Eat up extra spaces/ tabs before object name */ bol += strspn(bol, " \t"); @@ -757,11 +773,13 @@ static int parse_insn_line(char *bol, char *eol, struct replay_insn_list *item) *end_of_object_name = saved; if (status < 0) - return -1; + return parse_error(_("malformed object name"), + git_path(SEQ_TODO_FILE), lineno, bol); item->operand = lookup_commit_reference(commit_sha1); if (!item->operand) - return -1; + return parse_error(_("not a valid commit"), + git_path(SEQ_TODO_FILE), lineno, bol); item->next = NULL; return 0; @@ -776,8 +794,8 @@ static int parse_insn_buffer(char *buf, struct replay_insn_list **todo_list) for (i = 1; *p; i++) { char *eol = strchrnul(p, '\n'); - if (parse_insn_line(p, eol, &item)) - return error(_("Could not parse line %d."), i); + if (parse_insn_line(p, eol, &item, i)) + return -1; next = replay_insn_list_append(item.operand, item.action, next); p = *eol ? eol + 1 : eol; } -- 1.7.8.2 ^ permalink raw reply related [flat|nested] 64+ messages in thread
* Re: [PATCH 5/8] revert: report fine-grained errors from insn parser 2012-01-10 16:13 ` [PATCH 5/8] revert: report fine-grained errors from insn parser Ramkumar Ramachandra @ 2012-01-11 12:44 ` Jonathan Nieder 0 siblings, 0 replies; 64+ messages in thread From: Jonathan Nieder @ 2012-01-11 12:44 UTC (permalink / raw) To: Ramkumar Ramachandra; +Cc: Git List, Junio C Hamano Ramkumar Ramachandra wrote: > --- a/builtin/revert.c > +++ b/builtin/revert.c > @@ -732,7 +732,22 @@ static int format_todo(struct strbuf *buf, struct replay_insn_list *todo_list) > return 0; > } > > -static int parse_insn_line(char *bol, char *eol, struct replay_insn_list *item) > +static int parse_error(const char *message, const char *file, > + int lineno, char *error_line) > +{ > + const char *suffix = ""; > + int error_len = strcspn(error_line, " \t\n"); > + > + if (error_len > 20) { > + error_len = 20; > + suffix = "..."; > + } > + return error(_("%s:%d: %s: %.*s%s"), file, lineno, message, > + error_len, error_line, suffix); Since the snippet used in an error message is a single word, why is it called error_line? (And why is the signature written in a way that implies we might modify it, by the way?) Missing /* TRANSLATORS: ... */ comment. [...] > @@ -757,11 +773,13 @@ static int parse_insn_line(char *bol, char *eol, struct replay_insn_list *item) > *end_of_object_name = saved; > > if (status < 0) > - return -1; > + return parse_error(_("malformed object name"), > + git_path(SEQ_TODO_FILE), lineno, bol); This is the message I'll get if I misspell "master" as "mister" or try to cherry-pick HEAD~100000 when the history is not that deep. When I read "malformed object name", I'll look for syntax errors and be confused. (They are valid syntax denoting commits that just happen not to exist.) ^ permalink raw reply [flat|nested] 64+ messages in thread
* [PATCH 6/8] sha1_name: introduce getn_sha1() to take length 2012-01-10 16:13 ` [PATCH v2 0/8] " Ramkumar Ramachandra ` (4 preceding siblings ...) 2012-01-10 16:13 ` [PATCH 5/8] revert: report fine-grained errors from insn parser Ramkumar Ramachandra @ 2012-01-10 16:13 ` Ramkumar Ramachandra 2012-01-10 16:13 ` [PATCH 7/8] revert: use getn_sha1() to simplify insn parsing Ramkumar Ramachandra 2012-01-10 16:13 ` [PATCH 8/8] sequencer: factor code out of revert builtin Ramkumar Ramachandra 7 siblings, 0 replies; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-10 16:13 UTC (permalink / raw) To: Git List; +Cc: Junio C Hamano, Jonathan Nieder Introduce a variant of get_sha1() that additionally takes the length of the buffer, so it can parse object names from buffers that don't necessarily terminate with NUL. Suggested-by: Jonathan Nieder <jrnieder@gmail.com> Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com> --- cache.h | 1 + sha1_name.c | 23 +++++++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/cache.h b/cache.h index 10afd71..8bb6759 100644 --- a/cache.h +++ b/cache.h @@ -812,6 +812,7 @@ struct object_context { }; extern int get_sha1(const char *str, unsigned char *sha1); +extern int getn_sha1(const char *name, int namelen, unsigned char *sha1); extern int get_sha1_with_mode_1(const char *str, unsigned char *sha1, unsigned *mode, int only_to_die, const char *prefix); static inline int get_sha1_with_mode(const char *str, unsigned char *sha1, unsigned *mode) { diff --git a/sha1_name.c b/sha1_name.c index 03ffc2c..31d412e 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -1019,12 +1019,11 @@ static char *resolve_relative_path(const char *rel) rel); } -int get_sha1_with_context_1(const char *name, unsigned char *sha1, - struct object_context *oc, - int only_to_die, const char *prefix) +static int getn_sha1_with_context_1(const char *name, int namelen, + unsigned char *sha1, struct object_context *oc, + int only_to_die, const char *prefix) { int ret, bracket_depth; - int namelen = strlen(name); const char *cp; memset(oc, 0, sizeof(*oc)); @@ -1134,3 +1133,19 @@ int get_sha1_with_context_1(const char *name, unsigned char *sha1, } return ret; } + +int get_sha1_with_context_1(const char *name, unsigned char *sha1, + struct object_context *oc, + int only_to_die, const char *prefix) +{ + int namelen = strlen(name); + return getn_sha1_with_context_1(name, namelen, sha1, + oc, only_to_die, prefix); +} + +/* A variant of get_sha1 that takes a length. */ +int getn_sha1(const char *name, int namelen, unsigned char *sha1) +{ + struct object_context unused; + return getn_sha1_with_context_1(name, namelen, sha1, &unused, 0, NULL); +} -- 1.7.8.2 ^ permalink raw reply related [flat|nested] 64+ messages in thread
* [PATCH 7/8] revert: use getn_sha1() to simplify insn parsing 2012-01-10 16:13 ` [PATCH v2 0/8] " Ramkumar Ramachandra ` (5 preceding siblings ...) 2012-01-10 16:13 ` [PATCH 6/8] sha1_name: introduce getn_sha1() to take length Ramkumar Ramachandra @ 2012-01-10 16:13 ` Ramkumar Ramachandra 2012-01-10 16:13 ` [PATCH 8/8] sequencer: factor code out of revert builtin Ramkumar Ramachandra 7 siblings, 0 replies; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-10 16:13 UTC (permalink / raw) To: Git List; +Cc: Junio C Hamano, Jonathan Nieder To read the object name in the instruction sheet, we currently manipulate the buffer to artificially introduce a NUL after the supposed object name, and then use get_sha1() to read the object name before restoring the buffer. Get rid of this ugliness by using getn_sha1(), a function introduced in the previous patch. Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com> --- builtin/revert.c | 12 +++--------- 1 files changed, 3 insertions(+), 9 deletions(-) diff --git a/builtin/revert.c b/builtin/revert.c index 0954d22..187c317 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -750,8 +750,7 @@ static int parse_insn_line(char *bol, char *eol, struct replay_insn_list *item, int lineno) { unsigned char commit_sha1[20]; - char *end_of_object_name; - int saved, status; + int namelen; if (!prefixcmp(bol, "pick ") || !prefixcmp(bol, "pick\t")) { item->action = REPLAY_PICK; @@ -766,13 +765,8 @@ static int parse_insn_line(char *bol, char *eol, /* Eat up extra spaces/ tabs before object name */ bol += strspn(bol, " \t"); - end_of_object_name = bol + strcspn(bol, " \t\n"); - saved = *end_of_object_name; - *end_of_object_name = '\0'; - status = get_sha1(bol, commit_sha1); - *end_of_object_name = saved; - - if (status < 0) + namelen = strcspn(bol, " \t\n"); + if (getn_sha1(bol, namelen, commit_sha1)) return parse_error(_("malformed object name"), git_path(SEQ_TODO_FILE), lineno, bol); -- 1.7.8.2 ^ permalink raw reply related [flat|nested] 64+ messages in thread
* [PATCH 8/8] sequencer: factor code out of revert builtin 2012-01-10 16:13 ` [PATCH v2 0/8] " Ramkumar Ramachandra ` (6 preceding siblings ...) 2012-01-10 16:13 ` [PATCH 7/8] revert: use getn_sha1() to simplify insn parsing Ramkumar Ramachandra @ 2012-01-10 16:13 ` Ramkumar Ramachandra 7 siblings, 0 replies; 64+ messages in thread From: Ramkumar Ramachandra @ 2012-01-10 16:13 UTC (permalink / raw) To: Git List; +Cc: Junio C Hamano, Jonathan Nieder Expose the cherry-picking machinery through a public sequencer_pick_revisions() (renamed from pick_revisions() in builtin/revert.c), so that cherry-picking and reverting are special cases of a general sequencer operation. The cherry-pick builtin is now a thin wrapper that does command-line argument parsing before calling into sequencer_pick_revisions(). So now, your "foo" builtin can use the sequencer machinery by implementing a parse_args_populate_opts() function and then running the following: memset(&opts, 0, sizeof(opts)); opts.command = REPLAY_CMD_FOO; opts.revisions = xmalloc(sizeof(*opts.revs)); parse_args_populate_opts(argc, argv, &opts); init_revisions(opts.revs); sequencer_pick_revisions(&opts); This patch does not intend to make any functional changes. Check with: $ git blame -s -C HEAD^..HEAD -- sequencer.c | grep -C3 '^[^^]' Signed-off-by: Ramkumar Ramachandra <artagnon@gmail.com> --- Documentation/technical/api-sequencer.txt | 22 + builtin/revert.c | 966 +---------------------------- sequencer.c | 925 +++++++++++++++++++++++++++- sequencer.h | 48 ++ 4 files changed, 996 insertions(+), 965 deletions(-) create mode 100644 Documentation/technical/api-sequencer.txt diff --git a/Documentation/technical/api-sequencer.txt b/Documentation/technical/api-sequencer.txt new file mode 100644 index 0000000..22e9a95 --- /dev/null +++ b/Documentation/technical/api-sequencer.txt @@ -0,0 +1,22 @@ +sequencer API +============= + +The sequencer API makes it easy for builtins to execute a sequence of +commands operating on a range of commits. It provides uniform +semantics to '--continue', '--quit', and '--abort' the sequence when a +conflict is encountered. + +Currently, the 'git cherry-pick' and 'git revert' builtins utilize the +API. A new builtin "foo" can use the sequencer machinery by +implementing a parse_args_populate_opts() function and then running +the following: + + memset(&opts, 0, sizeof(opts)); + opts.command = REPLAY_CMD_FOO; + opts.revisions = xmalloc(sizeof(*opts.revs)); + parse_args_populate_opts(argc, argv, &opts); + init_revisions(opts.revs); + sequencer_pick_revisions(&opts); + + +(Ram) diff --git a/builtin/revert.c b/builtin/revert.c index 187c317..54a1c50 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -1,18 +1,9 @@ #include "cache.h" #include "builtin.h" -#include "object.h" -#include "commit.h" -#include "tag.h" -#include "run-command.h" -#include "exec_cmd.h" -#include "utf8.h" #include "parse-options.h" -#include "cache-tree.h" #include "diff.h" #include "revision.h" #include "rerere.h" -#include "merge-recursive.h" -#include "refs.h" #include "dir.h" #include "sequencer.h" @@ -39,61 +30,11 @@ static const char * const cherry_pick_usage[] = { NULL }; -enum replay_action { - REPLAY_REVERT, - REPLAY_PICK -}; - -enum replay_command { - REPLAY_CMD_REVERT, - REPLAY_CMD_CHERRY_PICK -}; - -enum replay_subcommand { - REPLAY_NONE, - REPLAY_REMOVE_STATE, - REPLAY_CONTINUE, - REPLAY_ROLLBACK -}; - -struct replay_insn_list { - struct commit *operand; - enum replay_action action; - struct replay_insn_list *next; -}; - -struct replay_opts { - enum replay_command command; - 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; - - /* Merge strategy */ - const char *strategy; - const char **xopts; - size_t xopts_nr, xopts_alloc; - - /* Only used by REPLAY_NONE */ - struct rev_info *revs; -}; - -#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" - static const char *command_name(struct replay_opts *opts) { return opts->command == REPLAY_CMD_REVERT ? "revert" : "cherry-pick"; } -static char *get_encoding(const char *message); - static const char * const *revert_or_cherry_pick_usage(struct replay_opts *opts) { return opts->command == REPLAY_CMD_REVERT ? revert_usage : cherry_pick_usage; @@ -252,909 +193,6 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts) usage_with_options(usage_str, options); } -struct commit_message { - char *parent_label; - const char *label; - const char *subject; - char *reencoded_message; - const char *message; -}; - -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 (!commit->buffer) - return -1; - 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 = commit->buffer; - if (strcmp(encoding, git_commit_encoding)) - out->reencoded_message = reencode_string(commit->buffer, - 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); - - subject_len = find_commit_subject(out->message, &subject); - - out->parent_label = xmalloc(strlen("parent of ") + abbrev_len + - strlen("... ") + subject_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, subject, subject_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) -{ - const char *p = message, *eol; - - while (*p && *p != '\n') { - for (eol = p + 1; *eol && *eol != '\n'; eol++) - ; /* do nothing */ - if (!prefixcmp(p, "encoding ")) { - char *result = xmalloc(eol - 8 - p); - strlcpy(result, p + 9, eol - 8 - p); - return result; - } - p = eol; - if (*p == '\n') - p++; - } - return NULL; -} - -static void write_cherry_pick_head(struct commit *commit, const char *pseudoref) -{ - const char *filename; - int fd; - struct strbuf buf = STRBUF_INIT; - - strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1)); - - filename = git_path("%s", pseudoref); - fd = open(filename, O_WRONLY | O_CREAT, 0666); - if (fd < 0) - die_errno(_("Could not open '%s' for writing"), filename); - if (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd)) - die_errno(_("Could not write to '%s'"), filename); - strbuf_release(&buf); -} - -static void print_advice(int show_hint) -{ - char *msg = getenv("GIT_CHERRY_PICK_HELP"); - - if (msg) { - fprintf(stderr, "%s\n", msg); - /* - * A conflict has occured but the porcelain - * (typically rebase --interactive) wants to take care - * of the commit itself so remove CHERRY_PICK_HEAD - */ - unlink(git_path("CHERRY_PICK_HEAD")); - return; - } - - if (show_hint) { - advise("after resolving the conflicts, mark the corrected paths"); - advise("with 'git add <paths>' or 'git rm <paths>'"); - advise("and commit the result with 'git commit'"); - } -} - -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) -{ - return lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN); -} - -static int error_dirty_index(struct replay_opts *opts) -{ - if (read_cache_unmerged()) - return error_resolve_conflict(command_name(opts)); - - /* Different translation strings for cherry-pick and revert */ - if (opts->command == REPLAY_CMD_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) -{ - 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 int do_recursive_merge(struct commit *base, struct commit *next, - const char *base_label, const char *next_label, - unsigned char *head, struct strbuf *msgbuf, - struct replay_opts *opts) -{ - struct merge_options o; - struct tree *result, *next_tree, *base_tree, *head_tree; - int clean, index_fd; - const char **xopt; - 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(); - - for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++) - parse_merge_opt(&o, *xopt); - - 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))) - /* TRANSLATORS: %s will be "revert" or "cherry-pick" */ - die(_("%s: Unable to write new index file"), command_name(opts)); - 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++; - } - } - } - - return !clean; -} - -/* - * If we are cherry-pick, and if the merge did not result in - * hand-editing, we will hit this commit and inherit the original - * author date and name. - * If we are revert, or if our cherry-pick results in a hand merge, - * we had better say that the current user is responsible for that. - */ -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]; - int i = 0; - - args[i++] = "commit"; - args[i++] = "-n"; - if (opts->signoff) - args[i++] = "-s"; - if (!opts->edit) { - args[i++] = "-F"; - args[i++] = defmsg; - } - args[i] = NULL; - - return run_command_v_opt(args, RUN_GIT_CMD); -} - -static int do_pick_commit(struct commit *commit, enum replay_action action, - struct replay_opts *opts) -{ - unsigned char head[20]; - struct commit *base, *next, *parent; - const char *base_label, *next_label; - struct commit_message msg = { NULL, NULL, NULL, NULL, NULL }; - char *defmsg = NULL; - struct strbuf msgbuf = STRBUF_INIT; - int res; - - 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 - * that represents the "current" state for merge-recursive - * to work on. - */ - if (write_cache_as_tree(head, 0, NULL)) - die (_("Your index file is unmerged.")); - } else { - if (get_sha1("HEAD", head)) - return error(_("You do not have a valid HEAD")); - if (index_differs_from("HEAD", 0)) - return error_dirty_index(opts); - } - discard_cache(); - - if (!commit->parents) { - parent = NULL; - } - else if (commit->parents->next) { - /* Reverting or cherry-picking a merge commit */ - int cnt; - struct commit_list *p; - - 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 != opts->mainline && p; - cnt++) - p = p->next; - 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 < 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 (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 */ - return error(_("%s: cannot parse parent commit %s"), - command_name(opts), sha1_to_hex(parent->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 - * the difference it introduces since its first parent "prev" - * on top of the current HEAD if we are cherry-pick. Or the - * reverse of it if we are revert. - */ - - defmsg = git_pathdup("MERGE_MSG"); - - if (action == REPLAY_REVERT) { - base = commit; - base_label = msg.label; - next = parent; - 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 && commit->parents->next) { - strbuf_addstr(&msgbuf, ", reversing\nchanges made to "); - strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1)); - } - strbuf_addstr(&msgbuf, ".\n"); - } else { - const char *p; - - base = parent; - base_label = msg.parent_label; - next = commit; - next_label = msg.label; - - /* - * 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 (!opts->strategy || !strcmp(opts->strategy, "recursive") || action == REPLAY_REVERT) { - res = do_recursive_merge(base, next, base_label, next_label, - head, &msgbuf, opts); - write_message(&msgbuf, defmsg); - } else { - 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(opts->strategy, opts->xopts_nr, opts->xopts, - common, sha1_to_hex(head), remotes); - free_commit_list(common); - free_commit_list(remotes); - } - - /* - * If the merge was clean or if it failed due to conflict, we write - * CHERRY_PICK_HEAD for the subsequent invocation of commit to use. - * However, if the merge did not even start, then we don't want to - * write it at all. - */ - if (action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1)) - write_cherry_pick_head(commit, "CHERRY_PICK_HEAD"); - if (action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1)) - write_cherry_pick_head(commit, "REVERT_HEAD"); - - if (res) { - error(action == REPLAY_REVERT - ? _("could not revert %s... %s") - : _("could not apply %s... %s"), - find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), - msg.subject); - print_advice(res == 1); - rerere(opts->allow_rerere_auto); - } else { - if (!opts->no_commit) - res = run_git_commit(defmsg, opts); - } - - free_message(&msg); - free(defmsg); - - return res; -} - -static void prepare_revs(struct replay_opts *opts) -{ - if (opts->command != REPLAY_CMD_REVERT) - opts->revs->reverse ^= 1; - - if (prepare_revision_walk(opts->revs)) - die(_("revision walk setup failed")); - - if (!opts->revs->commits) - die(_("empty commit set passed")); -} - -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"), command_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"), - command_name(opts)); - } - rollback_lock_file(&index_lock); -} - -/* - * Append a (commit, action) to the end of the replay_insn_list. - * - * next starts by pointing to the variable that holds the head of an - * empty replay_insn_list, and is updated to point to the "next" field of - * the last item on the list as new (commit, action) pairs are appended. - * - * Usage example: - * - * struct replay_insn_list *list; - * struct replay_insn_list **next = &list; - * - * next = replay_insn_list_append(c1, a1, next); - * next = replay_insn_list_append(c2, a2, next); - * assert(len(list) == 2); - * return list; - */ -static struct replay_insn_list **replay_insn_list_append(struct commit *operand, - enum replay_action action, - struct replay_insn_list **next) -{ - struct replay_insn_list *new = xmalloc(sizeof(*new)); - new->action = action; - new->operand = operand; - *next = new; - new->next = NULL; - return &new->next; -} - -static int format_todo(struct strbuf *buf, struct replay_insn_list *todo_list) -{ - struct replay_insn_list *cur; - - for (cur = todo_list; cur; cur = cur->next) { - const char *sha1_abbrev, *action_str, *subject; - int subject_len; - - action_str = cur->action == REPLAY_REVERT ? "revert" : "pick"; - sha1_abbrev = find_unique_abbrev(cur->operand->object.sha1, DEFAULT_ABBREV); - subject_len = find_commit_subject(cur->operand->buffer, &subject); - strbuf_addf(buf, "%s %s %.*s\n", action_str, sha1_abbrev, - subject_len, subject); - } - return 0; -} - -static int parse_error(const char *message, const char *file, - int lineno, char *error_line) -{ - const char *suffix = ""; - int error_len = strcspn(error_line, " \t\n"); - - if (error_len > 20) { - error_len = 20; - suffix = "..."; - } - return error(_("%s:%d: %s: %.*s%s"), file, lineno, message, - error_len, error_line, suffix); -} - -static int parse_insn_line(char *bol, char *eol, - struct replay_insn_list *item, int lineno) -{ - unsigned char commit_sha1[20]; - int namelen; - - if (!prefixcmp(bol, "pick ") || !prefixcmp(bol, "pick\t")) { - item->action = REPLAY_PICK; - bol += strlen("pick "); - } else if (!prefixcmp(bol, "revert ") || !prefixcmp(bol, "revert\t")) { - item->action = REPLAY_REVERT; - bol += strlen("revert "); - } else - return parse_error(_("unrecognized action"), - git_path(SEQ_TODO_FILE), lineno, bol); - - /* Eat up extra spaces/ tabs before object name */ - bol += strspn(bol, " \t"); - - namelen = strcspn(bol, " \t\n"); - if (getn_sha1(bol, namelen, commit_sha1)) - return parse_error(_("malformed object name"), - git_path(SEQ_TODO_FILE), lineno, bol); - - item->operand = lookup_commit_reference(commit_sha1); - if (!item->operand) - return parse_error(_("not a valid commit"), - git_path(SEQ_TODO_FILE), lineno, bol); - - item->next = NULL; - return 0; -} - -static int parse_insn_buffer(char *buf, struct replay_insn_list **todo_list) -{ - struct replay_insn_list **next = todo_list; - struct replay_insn_list item = { NULL, 0, NULL }; - char *p = buf; - int i; - - for (i = 1; *p; i++) { - char *eol = strchrnul(p, '\n'); - if (parse_insn_line(p, eol, &item, i)) - return -1; - next = replay_insn_list_append(item.operand, item.action, next); - p = *eol ? eol + 1 : eol; - } - if (!*todo_list) - return error(_("No commits parsed.")); - return 0; -} - -static void read_populate_todo(struct replay_insn_list **todo_list) -{ - 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); - 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 replay_insn_list **todo_list, - struct replay_opts *opts) -{ - struct commit *commit; - struct replay_insn_list **next; - enum replay_action action; - - prepare_revs(opts); - - next = todo_list; - action = opts->command == REPLAY_CMD_REVERT ? REPLAY_REVERT : REPLAY_PICK; - while ((commit = get_revision(opts->revs))) - next = replay_insn_list_append(commit, action, next); -} - -static int create_seq_dir(void) -{ - const char *seq_dir = git_path(SEQ_DIR); - - if (file_exists(seq_dir)) { - error(_("a cherry-pick or revert is already in progress")); - advise(_("try \"git cherry-pick (--continue | --quit | --abort)\"")); - return -1; - } - 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 int reset_for_rollback(const unsigned char *sha1) -{ - const char *argv[4]; /* reset --merge <arg> + NULL */ - argv[0] = "reset"; - argv[1] = "--merge"; - argv[2] = sha1_to_hex(sha1); - argv[3] = NULL; - return run_command_v_opt(argv, RUN_GIT_CMD); -} - -static int rollback_single_pick(void) -{ - unsigned char head_sha1[20]; - - if (!file_exists(git_path("CHERRY_PICK_HEAD")) && - !file_exists(git_path("REVERT_HEAD"))) - return error(_("no cherry-pick or revert in progress")); - if (read_ref_full("HEAD", head_sha1, 0, NULL)) - return error(_("cannot resolve HEAD")); - if (is_null_sha1(head_sha1)) - return error(_("cannot abort from a branch yet to be born")); - return reset_for_rollback(head_sha1); -} - -static int sequencer_rollback(struct replay_opts *opts) -{ - const char *filename; - FILE *f; - unsigned char sha1[20]; - struct strbuf buf = STRBUF_INIT; - - filename = git_path(SEQ_HEAD_FILE); - f = fopen(filename, "r"); - if (!f && errno == ENOENT) { - /* - * There is no multiple-cherry-pick in progress. - * If CHERRY_PICK_HEAD or REVERT_HEAD indicates - * a single-cherry-pick in progress, abort that. - */ - return rollback_single_pick(); - } - if (!f) - return error(_("cannot open %s: %s"), filename, - strerror(errno)); - if (strbuf_getline(&buf, f, '\n')) { - error(_("cannot read %s: %s"), filename, ferror(f) ? - strerror(errno) : _("unexpected end of file")); - fclose(f); - goto fail; - } - fclose(f); - if (get_sha1_hex(buf.buf, sha1) || buf.buf[40] != '\0') { - error(_("stored pre-cherry-pick HEAD file '%s' is corrupt"), - filename); - goto fail; - } - if (reset_for_rollback(sha1)) - goto fail; - remove_sequencer_state(); - strbuf_release(&buf); - return 0; -fail: - strbuf_release(&buf); - return -1; -} - -static void save_todo(struct replay_insn_list *todo_list) -{ - 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) < 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 replay_insn_list *todo_list, struct replay_opts *opts) -{ - struct replay_insn_list *cur; - int res; - - setenv(GIT_REFLOG_ACTION, command_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); - res = do_pick_commit(cur->operand, cur->action, opts); - if (res) - return res; - } - - /* - * Sequence of picks finished successfully; cleanup by - * removing the .git/sequencer directory - */ - remove_sequencer_state(); - return 0; -} - -static int continue_single_pick(void) -{ - const char *argv[] = { "commit", NULL }; - - if (!file_exists(git_path("CHERRY_PICK_HEAD")) && - !file_exists(git_path("REVERT_HEAD"))) - return error(_("no cherry-pick or revert in progress")); - return run_command_v_opt(argv, RUN_GIT_CMD); -} - -static int sequencer_continue(struct replay_opts *opts) -{ - struct replay_insn_list *todo_list = NULL; - - if (!file_exists(git_path(SEQ_TODO_FILE))) - return continue_single_pick(); - read_populate_opts(&opts); - read_populate_todo(&todo_list); - - /* Verify that the conflict has been resolved */ - if (file_exists(git_path("CHERRY_PICK_HEAD")) || - file_exists(git_path("REVERT_HEAD"))) { - int ret = continue_single_pick(); - if (ret) - return ret; - } - if (index_differs_from("HEAD", 0)) - return error_dirty_index(opts); - todo_list = todo_list->next; - return pick_commits(todo_list, opts); -} - -static int single_pick(struct commit *cmit, struct replay_opts *opts) -{ - enum replay_action action; - action = opts->command == REPLAY_CMD_REVERT ? REPLAY_REVERT : REPLAY_PICK; - - setenv(GIT_REFLOG_ACTION, command_name(opts), 0); - return do_pick_commit(cmit, action, opts); -} - -static int pick_revisions(struct replay_opts *opts) -{ - struct replay_insn_list *todo_list = NULL; - unsigned char sha1[20]; - - if (opts->subcommand == REPLAY_NONE) - assert(opts->revs); - - 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_REMOVE_STATE) { - remove_sequencer_state(); - return 0; - } - if (opts->subcommand == REPLAY_ROLLBACK) - return sequencer_rollback(opts); - if (opts->subcommand == REPLAY_CONTINUE) - return sequencer_continue(opts); - - /* - * If we were called as "git cherry-pick <commit>", just - * cherry-pick/revert it, set CHERRY_PICK_HEAD / - * REVERT_HEAD, and don't touch the sequencer state. - * This means it is possible to cherry-pick in the middle - * of a cherry-pick sequence. - */ - if (opts->revs->cmdline.nr == 1 && - opts->revs->cmdline.rev->whence == REV_CMD_REV && - opts->revs->no_walk && - !opts->revs->cmdline.rev->flags) { - struct commit *cmit; - if (prepare_revision_walk(opts->revs)) - die(_("revision walk setup failed")); - cmit = get_revision(opts->revs); - if (!cmit || get_revision(opts->revs)) - die("BUG: expected exactly one commit from walk"); - return single_pick(cmit, opts); - } - - /* - * 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) - return -1; - if (get_sha1("HEAD", sha1)) { - if (opts->command == REPLAY_CMD_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); -} - int cmd_revert(int argc, const char **argv, const char *prefix) { struct replay_opts opts; @@ -1166,7 +204,7 @@ int cmd_revert(int argc, const char **argv, const char *prefix) opts.command = REPLAY_CMD_REVERT; git_config(git_default_config, NULL); parse_args(argc, argv, &opts); - res = pick_revisions(&opts); + res = sequencer_pick_revisions(&opts); if (res < 0) die(_("revert failed")); return res; @@ -1181,7 +219,7 @@ int cmd_cherry_pick(int argc, const char **argv, const char *prefix) opts.command = REPLAY_CMD_CHERRY_PICK; git_config(git_default_config, NULL); parse_args(argc, argv, &opts); - res = pick_revisions(&opts); + res = sequencer_pick_revisions(&opts); if (res < 0) die(_("cherry-pick failed")); return res; diff --git a/sequencer.c b/sequencer.c index d1f28a6..a9c3bfa 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1,7 +1,20 @@ #include "cache.h" #include "sequencer.h" -#include "strbuf.h" #include "dir.h" +#include "object.h" +#include "commit.h" +#include "tag.h" +#include "run-command.h" +#include "exec_cmd.h" +#include "utf8.h" +#include "cache-tree.h" +#include "diff.h" +#include "revision.h" +#include "rerere.h" +#include "merge-recursive.h" +#include "refs.h" + +#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" void remove_sequencer_state(void) { @@ -11,3 +24,913 @@ void remove_sequencer_state(void) remove_dir_recursively(&seq_dir, 0); strbuf_release(&seq_dir); } + +static const char *command_name(struct replay_opts *opts) +{ + return opts->command == REPLAY_CMD_REVERT ? "revert" : "cherry-pick"; +} + +static char *get_encoding(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(struct commit *commit, struct commit_message *out) +{ + const char *encoding; + const char *abbrev, *subject; + int abbrev_len, subject_len; + char *q; + + if (!commit->buffer) + return -1; + 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 = commit->buffer; + if (strcmp(encoding, git_commit_encoding)) + out->reencoded_message = reencode_string(commit->buffer, + 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); + + subject_len = find_commit_subject(out->message, &subject); + + out->parent_label = xmalloc(strlen("parent of ") + abbrev_len + + strlen("... ") + subject_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, subject, subject_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) +{ + const char *p = message, *eol; + + while (*p && *p != '\n') { + for (eol = p + 1; *eol && *eol != '\n'; eol++) + ; /* do nothing */ + if (!prefixcmp(p, "encoding ")) { + char *result = xmalloc(eol - 8 - p); + strlcpy(result, p + 9, eol - 8 - p); + return result; + } + p = eol; + if (*p == '\n') + p++; + } + return NULL; +} + +static void write_cherry_pick_head(struct commit *commit, const char *pseudoref) +{ + const char *filename; + int fd; + struct strbuf buf = STRBUF_INIT; + + strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1)); + + filename = git_path("%s", pseudoref); + fd = open(filename, O_WRONLY | O_CREAT, 0666); + if (fd < 0) + die_errno(_("Could not open '%s' for writing"), filename); + if (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd)) + die_errno(_("Could not write to '%s'"), filename); + strbuf_release(&buf); +} + +static void print_advice(int show_hint) +{ + char *msg = getenv("GIT_CHERRY_PICK_HELP"); + + if (msg) { + fprintf(stderr, "%s\n", msg); + /* + * A conflict has occured but the porcelain + * (typically rebase --interactive) wants to take care + * of the commit itself so remove CHERRY_PICK_HEAD + */ + unlink(git_path("CHERRY_PICK_HEAD")); + return; + } + + if (show_hint) { + advise("after resolving the conflicts, mark the corrected paths"); + advise("with 'git add <paths>' or 'git rm <paths>'"); + advise("and commit the result with 'git commit'"); + } +} + +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) +{ + return lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN); +} + +static int error_dirty_index(struct replay_opts *opts) +{ + if (read_cache_unmerged()) + return error_resolve_conflict(command_name(opts)); + + /* Different translation strings for cherry-pick and revert */ + if (opts->command == REPLAY_CMD_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) +{ + 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 int do_recursive_merge(struct commit *base, struct commit *next, + const char *base_label, const char *next_label, + unsigned char *head, struct strbuf *msgbuf, + struct replay_opts *opts) +{ + struct merge_options o; + struct tree *result, *next_tree, *base_tree, *head_tree; + int clean, index_fd; + const char **xopt; + 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(); + + for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++) + parse_merge_opt(&o, *xopt); + + 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))) + /* TRANSLATORS: %s will be "revert" or "cherry-pick" */ + die(_("%s: Unable to write new index file"), command_name(opts)); + 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++; + } + } + } + + return !clean; +} + +/* + * If we are cherry-pick, and if the merge did not result in + * hand-editing, we will hit this commit and inherit the original + * author date and name. + * If we are revert, or if our cherry-pick results in a hand merge, + * we had better say that the current user is responsible for that. + */ +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]; + int i = 0; + + args[i++] = "commit"; + args[i++] = "-n"; + if (opts->signoff) + args[i++] = "-s"; + if (!opts->edit) { + args[i++] = "-F"; + args[i++] = defmsg; + } + args[i] = NULL; + + return run_command_v_opt(args, RUN_GIT_CMD); +} + +static int do_pick_commit(struct commit *commit, enum replay_action action, + struct replay_opts *opts) +{ + unsigned char head[20]; + struct commit *base, *next, *parent; + const char *base_label, *next_label; + struct commit_message msg = { NULL, NULL, NULL, NULL, NULL }; + char *defmsg = NULL; + struct strbuf msgbuf = STRBUF_INIT; + int res; + + 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 + * that represents the "current" state for merge-recursive + * to work on. + */ + if (write_cache_as_tree(head, 0, NULL)) + die (_("Your index file is unmerged.")); + } else { + if (get_sha1("HEAD", head)) + return error(_("You do not have a valid HEAD")); + if (index_differs_from("HEAD", 0)) + return error_dirty_index(opts); + } + discard_cache(); + + if (!commit->parents) { + parent = NULL; + } + else if (commit->parents->next) { + /* Reverting or cherry-picking a merge commit */ + int cnt; + struct commit_list *p; + + 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 != opts->mainline && p; + cnt++) + p = p->next; + 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 < 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 (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 */ + return error(_("%s: cannot parse parent commit %s"), + command_name(opts), sha1_to_hex(parent->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 + * the difference it introduces since its first parent "prev" + * on top of the current HEAD if we are cherry-pick. Or the + * reverse of it if we are revert. + */ + + defmsg = git_pathdup("MERGE_MSG"); + + if (action == REPLAY_REVERT) { + base = commit; + base_label = msg.label; + next = parent; + 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 && commit->parents->next) { + strbuf_addstr(&msgbuf, ", reversing\nchanges made to "); + strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1)); + } + strbuf_addstr(&msgbuf, ".\n"); + } else { + const char *p; + + base = parent; + base_label = msg.parent_label; + next = commit; + next_label = msg.label; + + /* + * 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 (!opts->strategy || !strcmp(opts->strategy, "recursive") || action == REPLAY_REVERT) { + res = do_recursive_merge(base, next, base_label, next_label, + head, &msgbuf, opts); + write_message(&msgbuf, defmsg); + } else { + 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(opts->strategy, opts->xopts_nr, opts->xopts, + common, sha1_to_hex(head), remotes); + free_commit_list(common); + free_commit_list(remotes); + } + + /* + * If the merge was clean or if it failed due to conflict, we write + * CHERRY_PICK_HEAD for the subsequent invocation of commit to use. + * However, if the merge did not even start, then we don't want to + * write it at all. + */ + if (action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1)) + write_cherry_pick_head(commit, "CHERRY_PICK_HEAD"); + if (action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1)) + write_cherry_pick_head(commit, "REVERT_HEAD"); + + if (res) { + error(action == REPLAY_REVERT + ? _("could not revert %s... %s") + : _("could not apply %s... %s"), + find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), + msg.subject); + print_advice(res == 1); + rerere(opts->allow_rerere_auto); + } else { + if (!opts->no_commit) + res = run_git_commit(defmsg, opts); + } + + free_message(&msg); + free(defmsg); + + return res; +} + +static void prepare_revs(struct replay_opts *opts) +{ + if (opts->command != REPLAY_CMD_REVERT) + opts->revs->reverse ^= 1; + + if (prepare_revision_walk(opts->revs)) + die(_("revision walk setup failed")); + + if (!opts->revs->commits) + die(_("empty commit set passed")); +} + +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"), command_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"), + command_name(opts)); + } + rollback_lock_file(&index_lock); +} + +/* + * Append a (commit, action) to the end of the replay_insn_list. + * + * next starts by pointing to the variable that holds the head of an + * empty replay_insn_list, and is updated to point to the "next" field of + * the last item on the list as new (commit, action) pairs are appended. + * + * Usage example: + * + * struct replay_insn_list *list; + * struct replay_insn_list **next = &list; + * + * next = replay_insn_list_append(c1, a1, next); + * next = replay_insn_list_append(c2, a2, next); + * assert(len(list) == 2); + * return list; + */ +static struct replay_insn_list **replay_insn_list_append(struct commit *operand, + enum replay_action action, + struct replay_insn_list **next) +{ + struct replay_insn_list *new = xmalloc(sizeof(*new)); + new->action = action; + new->operand = operand; + *next = new; + new->next = NULL; + return &new->next; +} + +static int format_todo(struct strbuf *buf, struct replay_insn_list *todo_list) +{ + struct replay_insn_list *cur; + + for (cur = todo_list; cur; cur = cur->next) { + const char *sha1_abbrev, *action_str, *subject; + int subject_len; + + action_str = cur->action == REPLAY_REVERT ? "revert" : "pick"; + sha1_abbrev = find_unique_abbrev(cur->operand->object.sha1, DEFAULT_ABBREV); + subject_len = find_commit_subject(cur->operand->buffer, &subject); + strbuf_addf(buf, "%s %s %.*s\n", action_str, sha1_abbrev, + subject_len, subject); + } + return 0; +} + +static int parse_error(const char *message, const char *file, + int lineno, char *error_line) +{ + const char *suffix = ""; + int error_len = strcspn(error_line, " \t\n"); + + if (error_len > 20) { + error_len = 20; + suffix = "..."; + } + return error(_("%s:%d: %s: %.*s%s"), file, lineno, message, + error_len, error_line, suffix); +} + +static int parse_insn_line(char *bol, char *eol, + struct replay_insn_list *item, int lineno) +{ + unsigned char commit_sha1[20]; + int namelen; + + if (!prefixcmp(bol, "pick ") || !prefixcmp(bol, "pick\t")) { + item->action = REPLAY_PICK; + bol += strlen("pick "); + } else if (!prefixcmp(bol, "revert ") || !prefixcmp(bol, "revert\t")) { + item->action = REPLAY_REVERT; + bol += strlen("revert "); + } else + return parse_error(_("unrecognized action"), + git_path(SEQ_TODO_FILE), lineno, bol); + + /* Eat up extra spaces/ tabs before object name */ + bol += strspn(bol, " \t"); + + namelen = strcspn(bol, " \t\n"); + if (getn_sha1(bol, namelen, commit_sha1)) + return parse_error(_("malformed object name"), + git_path(SEQ_TODO_FILE), lineno, bol); + + item->operand = lookup_commit_reference(commit_sha1); + if (!item->operand) + return parse_error(_("not a valid commit"), + git_path(SEQ_TODO_FILE), lineno, bol); + + item->next = NULL; + return 0; +} + +static int parse_insn_buffer(char *buf, struct replay_insn_list **todo_list) +{ + struct replay_insn_list **next = todo_list; + struct replay_insn_list item = { NULL, 0, NULL }; + char *p = buf; + int i; + + for (i = 1; *p; i++) { + char *eol = strchrnul(p, '\n'); + if (parse_insn_line(p, eol, &item, i)) + return -1; + next = replay_insn_list_append(item.operand, item.action, next); + p = *eol ? eol + 1 : eol; + } + if (!*todo_list) + return error(_("No commits parsed.")); + return 0; +} + +static void read_populate_todo(struct replay_insn_list **todo_list) +{ + 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); + 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 replay_insn_list **todo_list, + struct replay_opts *opts) +{ + struct commit *commit; + struct replay_insn_list **next; + enum replay_action action; + + prepare_revs(opts); + + next = todo_list; + action = opts->command == REPLAY_CMD_REVERT ? REPLAY_REVERT : REPLAY_PICK; + while ((commit = get_revision(opts->revs))) + next = replay_insn_list_append(commit, action, next); +} + +static int create_seq_dir(void) +{ + const char *seq_dir = git_path(SEQ_DIR); + + if (file_exists(seq_dir)) { + error(_("a cherry-pick or revert is already in progress")); + advise(_("try \"git cherry-pick (--continue | --quit | --abort)\"")); + return -1; + } + 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 int reset_for_rollback(const unsigned char *sha1) +{ + const char *argv[4]; /* reset --merge <arg> + NULL */ + argv[0] = "reset"; + argv[1] = "--merge"; + argv[2] = sha1_to_hex(sha1); + argv[3] = NULL; + return run_command_v_opt(argv, RUN_GIT_CMD); +} + +static int rollback_single_pick(void) +{ + unsigned char head_sha1[20]; + + if (!file_exists(git_path("CHERRY_PICK_HEAD")) && + !file_exists(git_path("REVERT_HEAD"))) + return error(_("no cherry-pick or revert in progress")); + if (read_ref_full("HEAD", head_sha1, 0, NULL)) + return error(_("cannot resolve HEAD")); + if (is_null_sha1(head_sha1)) + return error(_("cannot abort from a branch yet to be born")); + return reset_for_rollback(head_sha1); +} + +static int sequencer_rollback(struct replay_opts *opts) +{ + const char *filename; + FILE *f; + unsigned char sha1[20]; + struct strbuf buf = STRBUF_INIT; + + filename = git_path(SEQ_HEAD_FILE); + f = fopen(filename, "r"); + if (!f && errno == ENOENT) { + /* + * There is no multiple-cherry-pick in progress. + * If CHERRY_PICK_HEAD or REVERT_HEAD indicates + * a single-cherry-pick in progress, abort that. + */ + return rollback_single_pick(); + } + if (!f) + return error(_("cannot open %s: %s"), filename, + strerror(errno)); + if (strbuf_getline(&buf, f, '\n')) { + error(_("cannot read %s: %s"), filename, ferror(f) ? + strerror(errno) : _("unexpected end of file")); + fclose(f); + goto fail; + } + fclose(f); + if (get_sha1_hex(buf.buf, sha1) || buf.buf[40] != '\0') { + error(_("stored pre-cherry-pick HEAD file '%s' is corrupt"), + filename); + goto fail; + } + if (reset_for_rollback(sha1)) + goto fail; + remove_sequencer_state(); + strbuf_release(&buf); + return 0; +fail: + strbuf_release(&buf); + return -1; +} + +static void save_todo(struct replay_insn_list *todo_list) +{ + 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) < 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 replay_insn_list *todo_list, struct replay_opts *opts) +{ + struct replay_insn_list *cur; + int res; + + setenv(GIT_REFLOG_ACTION, command_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); + res = do_pick_commit(cur->operand, cur->action, opts); + if (res) + return res; + } + + /* + * Sequence of picks finished successfully; cleanup by + * removing the .git/sequencer directory + */ + remove_sequencer_state(); + return 0; +} + +static int continue_single_pick(void) +{ + const char *argv[] = { "commit", NULL }; + + if (!file_exists(git_path("CHERRY_PICK_HEAD")) && + !file_exists(git_path("REVERT_HEAD"))) + return error(_("no cherry-pick or revert in progress")); + return run_command_v_opt(argv, RUN_GIT_CMD); +} + +static int sequencer_continue(struct replay_opts *opts) +{ + struct replay_insn_list *todo_list = NULL; + + if (!file_exists(git_path(SEQ_TODO_FILE))) + return continue_single_pick(); + read_populate_opts(&opts); + read_populate_todo(&todo_list); + + /* Verify that the conflict has been resolved */ + if (file_exists(git_path("CHERRY_PICK_HEAD")) || + file_exists(git_path("REVERT_HEAD"))) { + int ret = continue_single_pick(); + if (ret) + return ret; + } + if (index_differs_from("HEAD", 0)) + return error_dirty_index(opts); + todo_list = todo_list->next; + return pick_commits(todo_list, opts); +} + +static int single_pick(struct commit *cmit, struct replay_opts *opts) +{ + enum replay_action action; + action = opts->command == REPLAY_CMD_REVERT ? REPLAY_REVERT : REPLAY_PICK; + + setenv(GIT_REFLOG_ACTION, command_name(opts), 0); + return do_pick_commit(cmit, action, opts); +} + +int sequencer_pick_revisions(struct replay_opts *opts) +{ + struct replay_insn_list *todo_list = NULL; + unsigned char sha1[20]; + + if (opts->subcommand == REPLAY_NONE) + assert(opts->revs); + + 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_REMOVE_STATE) { + remove_sequencer_state(); + return 0; + } + if (opts->subcommand == REPLAY_ROLLBACK) + return sequencer_rollback(opts); + if (opts->subcommand == REPLAY_CONTINUE) + return sequencer_continue(opts); + + /* + * If we were called as "git cherry-pick <commit>", just + * cherry-pick/revert it, set CHERRY_PICK_HEAD / + * REVERT_HEAD, and don't touch the sequencer state. + * This means it is possible to cherry-pick in the middle + * of a cherry-pick sequence. + */ + if (opts->revs->cmdline.nr == 1 && + opts->revs->cmdline.rev->whence == REV_CMD_REV && + opts->revs->no_walk && + !opts->revs->cmdline.rev->flags) { + struct commit *cmit; + if (prepare_revision_walk(opts->revs)) + die(_("revision walk setup failed")); + cmit = get_revision(opts->revs); + if (!cmit || get_revision(opts->revs)) + die("BUG: expected exactly one commit from walk"); + return single_pick(cmit, opts); + } + + /* + * 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) + return -1; + if (get_sha1("HEAD", sha1)) { + if (opts->command == REPLAY_CMD_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); +} diff --git a/sequencer.h b/sequencer.h index 2d4528f..7035908 100644 --- a/sequencer.h +++ b/sequencer.h @@ -6,7 +6,55 @@ #define SEQ_TODO_FILE "sequencer/todo" #define SEQ_OPTS_FILE "sequencer/opts" +enum replay_action { + REPLAY_REVERT, + REPLAY_PICK +}; + +enum replay_command { + REPLAY_CMD_REVERT, + REPLAY_CMD_CHERRY_PICK +}; + +enum replay_subcommand { + REPLAY_NONE, + REPLAY_REMOVE_STATE, + REPLAY_CONTINUE, + REPLAY_ROLLBACK +}; + +struct replay_insn_list { + struct commit *operand; + enum replay_action action; + struct replay_insn_list *next; +}; + +struct replay_opts { + enum replay_command command; + 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; + + /* Merge strategy */ + const char *strategy; + const char **xopts; + size_t xopts_nr, xopts_alloc; + + /* Only used by REPLAY_NONE */ + struct rev_info *revs; +}; + /* Removes SEQ_DIR. */ extern void remove_sequencer_state(void); +extern int sequencer_pick_revisions(struct replay_opts *opts); + #endif -- 1.7.8.2 ^ permalink raw reply related [flat|nested] 64+ messages in thread
end of thread, other threads:[~2012-01-11 18:35 UTC | newest] Thread overview: 64+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2012-01-08 12:27 [PATCH 0/6] The move to sequencer.c Ramkumar Ramachandra 2012-01-08 12:27 ` [PATCH 1/6] revert: move replay_action, replay_subcommand to header Ramkumar Ramachandra 2012-01-08 19:31 ` Jonathan Nieder 2012-01-08 12:27 ` [PATCH 2/6] revert: decouple sequencer actions from builtin commands Ramkumar Ramachandra 2012-01-08 19:34 ` Jonathan Nieder 2012-01-08 19:53 ` Ramkumar Ramachandra 2012-01-08 20:09 ` Jonathan Nieder 2012-01-08 20:07 ` Ramkumar Ramachandra 2012-01-08 20:48 ` Jonathan Nieder 2012-01-08 12:27 ` [PATCH 3/6] revert: don't let revert continue a cherry-pick Ramkumar Ramachandra 2012-01-08 19:37 ` Jonathan Nieder 2012-01-08 20:03 ` Ramkumar Ramachandra 2012-01-08 20:22 ` Jonathan Nieder 2012-01-08 20:28 ` Ramkumar Ramachandra 2012-01-08 20:45 ` Jonathan Nieder 2012-01-08 12:27 ` [PATCH 4/6] revert: allow mixing "pick" and "revert" actions Ramkumar Ramachandra 2012-01-08 19:40 ` Jonathan Nieder 2012-01-08 20:17 ` Ramkumar Ramachandra 2012-01-08 21:40 ` Jonathan Nieder 2012-01-08 21:55 ` Jonathan Nieder 2012-01-10 3:40 ` Ramkumar Ramachandra 2012-01-08 12:27 ` [PATCH 5/6] revert: report fine-grained error messages from insn parser Ramkumar Ramachandra 2012-01-08 20:07 ` Jonathan Nieder 2012-01-08 20:16 ` Ramkumar Ramachandra 2012-01-08 21:33 ` Jonathan Nieder 2012-01-10 15:24 ` Ramkumar Ramachandra 2012-01-08 12:27 ` [PATCH 6/6] sequencer: factor code out of revert builtin Ramkumar Ramachandra 2012-01-08 20:38 ` Jonathan Nieder 2012-01-10 15:21 ` Ramkumar Ramachandra 2012-01-08 19:28 ` [PATCH 0/6] The move to sequencer.c Jonathan Nieder 2012-01-08 19:51 ` Ramkumar Ramachandra 2012-01-08 20:43 ` Jonathan Nieder 2012-01-10 16:13 ` [PATCH v2 0/8] " Ramkumar Ramachandra 2012-01-10 16:13 ` [PATCH 1/8] revert: prepare to move replay_action to header Ramkumar Ramachandra 2012-01-10 18:27 ` Jonathan Nieder 2012-01-10 16:13 ` [PATCH 2/8] revert: decouple sequencer actions from builtin commands Ramkumar Ramachandra 2012-01-10 18:38 ` Jonathan Nieder 2012-01-11 4:02 ` Ramkumar Ramachandra 2012-01-11 4:17 ` Ramkumar Ramachandra 2012-01-11 5:04 ` Jonathan Nieder 2012-01-11 5:14 ` Ramkumar Ramachandra 2012-01-11 5:26 ` Ramkumar Ramachandra 2012-01-11 5:49 ` Jonathan Nieder 2012-01-11 9:19 ` Ramkumar Ramachandra 2012-01-11 9:52 ` Jonathan Nieder 2012-01-11 10:11 ` Ramkumar Ramachandra 2012-01-11 13:40 ` Jonathan Nieder 2012-01-11 13:18 ` Jonathan Nieder 2012-01-11 16:39 ` Ramkumar Ramachandra 2012-01-11 16:47 ` Jonathan Nieder 2012-01-11 16:52 ` Ramkumar Ramachandra 2012-01-11 18:15 ` [PATCH v3 0/2] The move to sequencer.c Ramkumar Ramachandra 2012-01-11 18:15 ` [PATCH 1/2] revert: prepare to move replay_action to header Ramkumar Ramachandra 2012-01-11 18:15 ` [PATCH 2/2] sequencer: factor code out of revert builtin Ramkumar Ramachandra 2012-01-11 18:40 ` [PATCH v3 0/2] The move to sequencer.c Jonathan Nieder 2012-01-10 16:13 ` [PATCH 3/8] revert: allow mixing "pick" and "revert" actions Ramkumar Ramachandra 2012-01-10 16:13 ` [PATCH 4/8] revert: separate out parse errors logically Ramkumar Ramachandra 2012-01-10 19:03 ` Jonathan Nieder 2012-01-11 12:38 ` Ramkumar Ramachandra 2012-01-10 16:13 ` [PATCH 5/8] revert: report fine-grained errors from insn parser Ramkumar Ramachandra 2012-01-11 12:44 ` Jonathan Nieder 2012-01-10 16:13 ` [PATCH 6/8] sha1_name: introduce getn_sha1() to take length Ramkumar Ramachandra 2012-01-10 16:13 ` [PATCH 7/8] revert: use getn_sha1() to simplify insn parsing Ramkumar Ramachandra 2012-01-10 16:13 ` [PATCH 8/8] sequencer: factor code out of revert builtin Ramkumar Ramachandra
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).