mirror of
https://github.com/git/git
synced 2024-08-25 10:45:59 +00:00
sequencer (rebase -i): implement the 'edit' command
This patch is a straight-forward reimplementation of the `edit` operation of the interactive rebase command. Well, not *quite* straight-forward: when stopping, the `edit` command wants to write the `patch` file (which is not only the patch, but includes the commit message and author information). To that end, this patch requires the earlier work that taught the log-tree machinery to respect the `file` setting of rev_info->diffopt to write to a file stream different than stdout. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
parent
25c4366782
commit
56dc3ab04b
117
sequencer.c
117
sequencer.c
|
@ -17,6 +17,7 @@
|
||||||
#include "argv-array.h"
|
#include "argv-array.h"
|
||||||
#include "quote.h"
|
#include "quote.h"
|
||||||
#include "trailer.h"
|
#include "trailer.h"
|
||||||
|
#include "log-tree.h"
|
||||||
|
|
||||||
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
|
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
|
||||||
|
|
||||||
|
@ -44,6 +45,20 @@ static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo")
|
||||||
* being rebased.
|
* being rebased.
|
||||||
*/
|
*/
|
||||||
static GIT_PATH_FUNC(rebase_path_author_script, "rebase-merge/author-script")
|
static GIT_PATH_FUNC(rebase_path_author_script, "rebase-merge/author-script")
|
||||||
|
/*
|
||||||
|
* When an "edit" rebase command is being processed, the SHA1 of the
|
||||||
|
* commit to be edited is recorded in this file. When "git rebase
|
||||||
|
* --continue" is executed, if there are any staged changes then they
|
||||||
|
* will be amended to the HEAD commit, but only provided the HEAD
|
||||||
|
* commit is still the commit to be edited. When any other rebase
|
||||||
|
* command is processed, this file is deleted.
|
||||||
|
*/
|
||||||
|
static GIT_PATH_FUNC(rebase_path_amend, "rebase-merge/amend")
|
||||||
|
/*
|
||||||
|
* When we stop at a given patch via the "edit" command, this file contains
|
||||||
|
* the abbreviated commit name of the corresponding patch.
|
||||||
|
*/
|
||||||
|
static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha")
|
||||||
/*
|
/*
|
||||||
* The following files are written by git-rebase just after parsing the
|
* The following files are written by git-rebase just after parsing the
|
||||||
* command-line (and are only consumed, not modified, by the sequencer).
|
* command-line (and are only consumed, not modified, by the sequencer).
|
||||||
|
@ -616,6 +631,7 @@ enum todo_command {
|
||||||
/* commands that handle commits */
|
/* commands that handle commits */
|
||||||
TODO_PICK = 0,
|
TODO_PICK = 0,
|
||||||
TODO_REVERT,
|
TODO_REVERT,
|
||||||
|
TODO_EDIT,
|
||||||
/* commands that do nothing but are counted for reporting progress */
|
/* commands that do nothing but are counted for reporting progress */
|
||||||
TODO_NOOP
|
TODO_NOOP
|
||||||
};
|
};
|
||||||
|
@ -623,6 +639,7 @@ enum todo_command {
|
||||||
static const char *todo_command_strings[] = {
|
static const char *todo_command_strings[] = {
|
||||||
"pick",
|
"pick",
|
||||||
"revert",
|
"revert",
|
||||||
|
"edit",
|
||||||
"noop"
|
"noop"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1302,9 +1319,87 @@ static int save_opts(struct replay_opts *opts)
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int make_patch(struct commit *commit, struct replay_opts *opts)
|
||||||
|
{
|
||||||
|
struct strbuf buf = STRBUF_INIT;
|
||||||
|
struct rev_info log_tree_opt;
|
||||||
|
const char *subject, *p;
|
||||||
|
int res = 0;
|
||||||
|
|
||||||
|
p = short_commit_name(commit);
|
||||||
|
if (write_message(p, strlen(p), rebase_path_stopped_sha(), 1) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
strbuf_addf(&buf, "%s/patch", get_dir(opts));
|
||||||
|
memset(&log_tree_opt, 0, sizeof(log_tree_opt));
|
||||||
|
init_revisions(&log_tree_opt, NULL);
|
||||||
|
log_tree_opt.abbrev = 0;
|
||||||
|
log_tree_opt.diff = 1;
|
||||||
|
log_tree_opt.diffopt.output_format = DIFF_FORMAT_PATCH;
|
||||||
|
log_tree_opt.disable_stdin = 1;
|
||||||
|
log_tree_opt.no_commit_id = 1;
|
||||||
|
log_tree_opt.diffopt.file = fopen(buf.buf, "w");
|
||||||
|
log_tree_opt.diffopt.use_color = GIT_COLOR_NEVER;
|
||||||
|
if (!log_tree_opt.diffopt.file)
|
||||||
|
res |= error_errno(_("could not open '%s'"), buf.buf);
|
||||||
|
else {
|
||||||
|
res |= log_tree_commit(&log_tree_opt, commit);
|
||||||
|
fclose(log_tree_opt.diffopt.file);
|
||||||
|
}
|
||||||
|
strbuf_reset(&buf);
|
||||||
|
|
||||||
|
strbuf_addf(&buf, "%s/message", get_dir(opts));
|
||||||
|
if (!file_exists(buf.buf)) {
|
||||||
|
const char *commit_buffer = get_commit_buffer(commit, NULL);
|
||||||
|
find_commit_subject(commit_buffer, &subject);
|
||||||
|
res |= write_message(subject, strlen(subject), buf.buf, 1);
|
||||||
|
unuse_commit_buffer(commit, commit_buffer);
|
||||||
|
}
|
||||||
|
strbuf_release(&buf);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int intend_to_amend(void)
|
||||||
|
{
|
||||||
|
unsigned char head[20];
|
||||||
|
char *p;
|
||||||
|
|
||||||
|
if (get_sha1("HEAD", head))
|
||||||
|
return error(_("cannot read HEAD"));
|
||||||
|
|
||||||
|
p = sha1_to_hex(head);
|
||||||
|
return write_message(p, strlen(p), rebase_path_amend(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int error_with_patch(struct commit *commit,
|
||||||
|
const char *subject, int subject_len,
|
||||||
|
struct replay_opts *opts, int exit_code, int to_amend)
|
||||||
|
{
|
||||||
|
if (make_patch(commit, opts))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (to_amend) {
|
||||||
|
if (intend_to_amend())
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
fprintf(stderr, "You can amend the commit now, with\n"
|
||||||
|
"\n"
|
||||||
|
" git commit --amend %s\n"
|
||||||
|
"\n"
|
||||||
|
"Once you are satisfied with your changes, run\n"
|
||||||
|
"\n"
|
||||||
|
" git rebase --continue\n", gpg_sign_opt_quoted(opts));
|
||||||
|
} else if (exit_code)
|
||||||
|
fprintf(stderr, "Could not apply %s... %.*s\n",
|
||||||
|
short_commit_name(commit), subject_len, subject);
|
||||||
|
|
||||||
|
return exit_code;
|
||||||
|
}
|
||||||
|
|
||||||
static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
|
static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
|
||||||
{
|
{
|
||||||
int res;
|
int res = 0;
|
||||||
|
|
||||||
setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
|
setenv(GIT_REFLOG_ACTION, action_name(opts), 0);
|
||||||
if (opts->allow_ff)
|
if (opts->allow_ff)
|
||||||
|
@ -1317,10 +1412,20 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
|
||||||
struct todo_item *item = todo_list->items + todo_list->current;
|
struct todo_item *item = todo_list->items + todo_list->current;
|
||||||
if (save_todo(todo_list, opts))
|
if (save_todo(todo_list, opts))
|
||||||
return -1;
|
return -1;
|
||||||
if (item->command <= TODO_REVERT)
|
if (item->command <= TODO_EDIT) {
|
||||||
res = do_pick_commit(item->command, item->commit,
|
res = do_pick_commit(item->command, item->commit,
|
||||||
opts);
|
opts);
|
||||||
else if (!is_noop(item->command))
|
if (item->command == TODO_EDIT) {
|
||||||
|
struct commit *commit = item->commit;
|
||||||
|
if (!res)
|
||||||
|
warning(_("stopped at %s... %.*s"),
|
||||||
|
short_commit_name(commit),
|
||||||
|
item->arg_len, item->arg);
|
||||||
|
return error_with_patch(commit,
|
||||||
|
item->arg, item->arg_len, opts, res,
|
||||||
|
!res);
|
||||||
|
}
|
||||||
|
} else if (!is_noop(item->command))
|
||||||
return error(_("unknown command %d"), item->command);
|
return error(_("unknown command %d"), item->command);
|
||||||
|
|
||||||
todo_list->current++;
|
todo_list->current++;
|
||||||
|
@ -1328,6 +1433,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_rebase_i(opts)) {
|
||||||
|
/* Stopped in the middle, as planned? */
|
||||||
|
if (todo_list->current < todo_list->nr)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Sequence of picks finished successfully; cleanup by
|
* Sequence of picks finished successfully; cleanup by
|
||||||
* removing the .git/sequencer directory
|
* removing the .git/sequencer directory
|
||||||
|
|
Loading…
Reference in a new issue