Merge branch 'pk/rebase-in-c-3-acts'
Rewrite "git rebase" in C. * pk/rebase-in-c-3-acts: builtin rebase: stop if `git am` is in progress builtin rebase: actions require a rebase in progress builtin rebase: support --edit-todo and --show-current-patch builtin rebase: support --quit builtin rebase: support --abort builtin rebase: support --skip builtin rebase: support --continue
This commit is contained in:
193
builtin/rebase.c
193
builtin/rebase.c
@ -22,6 +22,7 @@
|
|||||||
#include "wt-status.h"
|
#include "wt-status.h"
|
||||||
#include "revision.h"
|
#include "revision.h"
|
||||||
#include "commit-reach.h"
|
#include "commit-reach.h"
|
||||||
|
#include "rerere.h"
|
||||||
|
|
||||||
static char const * const builtin_rebase_usage[] = {
|
static char const * const builtin_rebase_usage[] = {
|
||||||
N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] "
|
N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] "
|
||||||
@ -92,6 +93,7 @@ struct rebase_options {
|
|||||||
REBASE_INTERACTIVE_EXPLICIT = 1<<4,
|
REBASE_INTERACTIVE_EXPLICIT = 1<<4,
|
||||||
} flags;
|
} flags;
|
||||||
struct strbuf git_am_opt;
|
struct strbuf git_am_opt;
|
||||||
|
const char *action;
|
||||||
};
|
};
|
||||||
|
|
||||||
static int is_interactive(struct rebase_options *opts)
|
static int is_interactive(struct rebase_options *opts)
|
||||||
@ -116,6 +118,62 @@ static const char *state_dir_path(const char *filename, struct rebase_options *o
|
|||||||
return path.buf;
|
return path.buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Read one file, then strip line endings */
|
||||||
|
static int read_one(const char *path, struct strbuf *buf)
|
||||||
|
{
|
||||||
|
if (strbuf_read_file(buf, path, 0) < 0)
|
||||||
|
return error_errno(_("could not read '%s'"), path);
|
||||||
|
strbuf_trim_trailing_newline(buf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Initialize the rebase options from the state directory. */
|
||||||
|
static int read_basic_state(struct rebase_options *opts)
|
||||||
|
{
|
||||||
|
struct strbuf head_name = STRBUF_INIT;
|
||||||
|
struct strbuf buf = STRBUF_INIT;
|
||||||
|
struct object_id oid;
|
||||||
|
|
||||||
|
if (read_one(state_dir_path("head-name", opts), &head_name) ||
|
||||||
|
read_one(state_dir_path("onto", opts), &buf))
|
||||||
|
return -1;
|
||||||
|
opts->head_name = starts_with(head_name.buf, "refs/") ?
|
||||||
|
xstrdup(head_name.buf) : NULL;
|
||||||
|
strbuf_release(&head_name);
|
||||||
|
if (get_oid(buf.buf, &oid))
|
||||||
|
return error(_("could not get 'onto': '%s'"), buf.buf);
|
||||||
|
opts->onto = lookup_commit_or_die(&oid, buf.buf);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We always write to orig-head, but interactive rebase used to write to
|
||||||
|
* head. Fall back to reading from head to cover for the case that the
|
||||||
|
* user upgraded git with an ongoing interactive rebase.
|
||||||
|
*/
|
||||||
|
strbuf_reset(&buf);
|
||||||
|
if (file_exists(state_dir_path("orig-head", opts))) {
|
||||||
|
if (read_one(state_dir_path("orig-head", opts), &buf))
|
||||||
|
return -1;
|
||||||
|
} else if (read_one(state_dir_path("head", opts), &buf))
|
||||||
|
return -1;
|
||||||
|
if (get_oid(buf.buf, &opts->orig_head))
|
||||||
|
return error(_("invalid orig-head: '%s'"), buf.buf);
|
||||||
|
|
||||||
|
strbuf_reset(&buf);
|
||||||
|
if (read_one(state_dir_path("quiet", opts), &buf))
|
||||||
|
return -1;
|
||||||
|
if (buf.len)
|
||||||
|
opts->flags &= ~REBASE_NO_QUIET;
|
||||||
|
else
|
||||||
|
opts->flags |= REBASE_NO_QUIET;
|
||||||
|
|
||||||
|
if (file_exists(state_dir_path("verbose", opts)))
|
||||||
|
opts->flags |= REBASE_VERBOSE;
|
||||||
|
|
||||||
|
strbuf_release(&buf);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int finish_rebase(struct rebase_options *opts)
|
static int finish_rebase(struct rebase_options *opts)
|
||||||
{
|
{
|
||||||
struct strbuf dir = STRBUF_INIT;
|
struct strbuf dir = STRBUF_INIT;
|
||||||
@ -169,12 +227,13 @@ static int run_specific_rebase(struct rebase_options *opts)
|
|||||||
add_var(&script_snippet, "state_dir", opts->state_dir);
|
add_var(&script_snippet, "state_dir", opts->state_dir);
|
||||||
|
|
||||||
add_var(&script_snippet, "upstream_name", opts->upstream_name);
|
add_var(&script_snippet, "upstream_name", opts->upstream_name);
|
||||||
add_var(&script_snippet, "upstream",
|
add_var(&script_snippet, "upstream", opts->upstream ?
|
||||||
oid_to_hex(&opts->upstream->object.oid));
|
oid_to_hex(&opts->upstream->object.oid) : NULL);
|
||||||
add_var(&script_snippet, "head_name",
|
add_var(&script_snippet, "head_name",
|
||||||
opts->head_name ? opts->head_name : "detached HEAD");
|
opts->head_name ? opts->head_name : "detached HEAD");
|
||||||
add_var(&script_snippet, "orig_head", oid_to_hex(&opts->orig_head));
|
add_var(&script_snippet, "orig_head", oid_to_hex(&opts->orig_head));
|
||||||
add_var(&script_snippet, "onto", oid_to_hex(&opts->onto->object.oid));
|
add_var(&script_snippet, "onto", opts->onto ?
|
||||||
|
oid_to_hex(&opts->onto->object.oid) : NULL);
|
||||||
add_var(&script_snippet, "onto_name", opts->onto_name);
|
add_var(&script_snippet, "onto_name", opts->onto_name);
|
||||||
add_var(&script_snippet, "revisions", opts->revisions);
|
add_var(&script_snippet, "revisions", opts->revisions);
|
||||||
add_var(&script_snippet, "restrict_revision", opts->restrict_revision ?
|
add_var(&script_snippet, "restrict_revision", opts->restrict_revision ?
|
||||||
@ -190,6 +249,7 @@ static int run_specific_rebase(struct rebase_options *opts)
|
|||||||
opts->flags & REBASE_FORCE ? "t" : "");
|
opts->flags & REBASE_FORCE ? "t" : "");
|
||||||
if (opts->switch_to)
|
if (opts->switch_to)
|
||||||
add_var(&script_snippet, "switch_to", opts->switch_to);
|
add_var(&script_snippet, "switch_to", opts->switch_to);
|
||||||
|
add_var(&script_snippet, "action", opts->action ? opts->action : "");
|
||||||
|
|
||||||
switch (opts->type) {
|
switch (opts->type) {
|
||||||
case REBASE_AM:
|
case REBASE_AM:
|
||||||
@ -401,12 +461,21 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
|
|||||||
.git_am_opt = STRBUF_INIT,
|
.git_am_opt = STRBUF_INIT,
|
||||||
};
|
};
|
||||||
const char *branch_name;
|
const char *branch_name;
|
||||||
int ret, flags, in_progress = 0;
|
int ret, flags, total_argc, in_progress = 0;
|
||||||
int ok_to_skip_pre_rebase = 0;
|
int ok_to_skip_pre_rebase = 0;
|
||||||
struct strbuf msg = STRBUF_INIT;
|
struct strbuf msg = STRBUF_INIT;
|
||||||
struct strbuf revisions = STRBUF_INIT;
|
struct strbuf revisions = STRBUF_INIT;
|
||||||
struct strbuf buf = STRBUF_INIT;
|
struct strbuf buf = STRBUF_INIT;
|
||||||
struct object_id merge_base;
|
struct object_id merge_base;
|
||||||
|
enum {
|
||||||
|
NO_ACTION,
|
||||||
|
ACTION_CONTINUE,
|
||||||
|
ACTION_SKIP,
|
||||||
|
ACTION_ABORT,
|
||||||
|
ACTION_QUIT,
|
||||||
|
ACTION_EDIT_TODO,
|
||||||
|
ACTION_SHOW_CURRENT_PATCH,
|
||||||
|
} action = NO_ACTION;
|
||||||
struct option builtin_rebase_options[] = {
|
struct option builtin_rebase_options[] = {
|
||||||
OPT_STRING(0, "onto", &options.onto_name,
|
OPT_STRING(0, "onto", &options.onto_name,
|
||||||
N_("revision"),
|
N_("revision"),
|
||||||
@ -428,6 +497,20 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
|
|||||||
OPT_BIT(0, "no-ff", &options.flags,
|
OPT_BIT(0, "no-ff", &options.flags,
|
||||||
N_("cherry-pick all commits, even if unchanged"),
|
N_("cherry-pick all commits, even if unchanged"),
|
||||||
REBASE_FORCE),
|
REBASE_FORCE),
|
||||||
|
OPT_CMDMODE(0, "continue", &action, N_("continue"),
|
||||||
|
ACTION_CONTINUE),
|
||||||
|
OPT_CMDMODE(0, "skip", &action,
|
||||||
|
N_("skip current patch and continue"), ACTION_SKIP),
|
||||||
|
OPT_CMDMODE(0, "abort", &action,
|
||||||
|
N_("abort and check out the original branch"),
|
||||||
|
ACTION_ABORT),
|
||||||
|
OPT_CMDMODE(0, "quit", &action,
|
||||||
|
N_("abort but keep HEAD where it is"), ACTION_QUIT),
|
||||||
|
OPT_CMDMODE(0, "edit-todo", &action, N_("edit the todo list "
|
||||||
|
"during an interactive rebase"), ACTION_EDIT_TODO),
|
||||||
|
OPT_CMDMODE(0, "show-current-patch", &action,
|
||||||
|
N_("show the patch file being applied or merged"),
|
||||||
|
ACTION_SHOW_CURRENT_PATCH),
|
||||||
OPT_END(),
|
OPT_END(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -457,6 +540,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
|
|||||||
|
|
||||||
git_config(rebase_config, &options);
|
git_config(rebase_config, &options);
|
||||||
|
|
||||||
|
strbuf_reset(&buf);
|
||||||
|
strbuf_addf(&buf, "%s/applying", apply_dir());
|
||||||
|
if(file_exists(buf.buf))
|
||||||
|
die(_("It looks like 'git am' is in progress. Cannot rebase."));
|
||||||
|
|
||||||
if (is_directory(apply_dir())) {
|
if (is_directory(apply_dir())) {
|
||||||
options.type = REBASE_AM;
|
options.type = REBASE_AM;
|
||||||
options.state_dir = apply_dir();
|
options.state_dir = apply_dir();
|
||||||
@ -481,14 +569,110 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
|
|||||||
if (options.type != REBASE_UNSPECIFIED)
|
if (options.type != REBASE_UNSPECIFIED)
|
||||||
in_progress = 1;
|
in_progress = 1;
|
||||||
|
|
||||||
|
total_argc = argc;
|
||||||
argc = parse_options(argc, argv, prefix,
|
argc = parse_options(argc, argv, prefix,
|
||||||
builtin_rebase_options,
|
builtin_rebase_options,
|
||||||
builtin_rebase_usage, 0);
|
builtin_rebase_usage, 0);
|
||||||
|
|
||||||
|
if (action != NO_ACTION && total_argc != 2) {
|
||||||
|
usage_with_options(builtin_rebase_usage,
|
||||||
|
builtin_rebase_options);
|
||||||
|
}
|
||||||
|
|
||||||
if (argc > 2)
|
if (argc > 2)
|
||||||
usage_with_options(builtin_rebase_usage,
|
usage_with_options(builtin_rebase_usage,
|
||||||
builtin_rebase_options);
|
builtin_rebase_options);
|
||||||
|
|
||||||
|
if (action != NO_ACTION && !in_progress)
|
||||||
|
die(_("No rebase in progress?"));
|
||||||
|
|
||||||
|
if (action == ACTION_EDIT_TODO && !is_interactive(&options))
|
||||||
|
die(_("The --edit-todo action can only be used during "
|
||||||
|
"interactive rebase."));
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case ACTION_CONTINUE: {
|
||||||
|
struct object_id head;
|
||||||
|
struct lock_file lock_file = LOCK_INIT;
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
options.action = "continue";
|
||||||
|
|
||||||
|
/* Sanity check */
|
||||||
|
if (get_oid("HEAD", &head))
|
||||||
|
die(_("Cannot read HEAD"));
|
||||||
|
|
||||||
|
fd = hold_locked_index(&lock_file, 0);
|
||||||
|
if (read_index(the_repository->index) < 0)
|
||||||
|
die(_("could not read index"));
|
||||||
|
refresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL,
|
||||||
|
NULL);
|
||||||
|
if (0 <= fd)
|
||||||
|
update_index_if_able(the_repository->index,
|
||||||
|
&lock_file);
|
||||||
|
rollback_lock_file(&lock_file);
|
||||||
|
|
||||||
|
if (has_unstaged_changes(1)) {
|
||||||
|
puts(_("You must edit all merge conflicts and then\n"
|
||||||
|
"mark them as resolved using git add"));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if (read_basic_state(&options))
|
||||||
|
exit(1);
|
||||||
|
goto run_rebase;
|
||||||
|
}
|
||||||
|
case ACTION_SKIP: {
|
||||||
|
struct string_list merge_rr = STRING_LIST_INIT_DUP;
|
||||||
|
|
||||||
|
options.action = "skip";
|
||||||
|
|
||||||
|
rerere_clear(&merge_rr);
|
||||||
|
string_list_clear(&merge_rr, 1);
|
||||||
|
|
||||||
|
if (reset_head(NULL, "reset", NULL, 0) < 0)
|
||||||
|
die(_("could not discard worktree changes"));
|
||||||
|
if (read_basic_state(&options))
|
||||||
|
exit(1);
|
||||||
|
goto run_rebase;
|
||||||
|
}
|
||||||
|
case ACTION_ABORT: {
|
||||||
|
struct string_list merge_rr = STRING_LIST_INIT_DUP;
|
||||||
|
options.action = "abort";
|
||||||
|
|
||||||
|
rerere_clear(&merge_rr);
|
||||||
|
string_list_clear(&merge_rr, 1);
|
||||||
|
|
||||||
|
if (read_basic_state(&options))
|
||||||
|
exit(1);
|
||||||
|
if (reset_head(&options.orig_head, "reset",
|
||||||
|
options.head_name, 0) < 0)
|
||||||
|
die(_("could not move back to %s"),
|
||||||
|
oid_to_hex(&options.orig_head));
|
||||||
|
ret = finish_rebase(&options);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
case ACTION_QUIT: {
|
||||||
|
strbuf_reset(&buf);
|
||||||
|
strbuf_addstr(&buf, options.state_dir);
|
||||||
|
ret = !!remove_dir_recursively(&buf, 0);
|
||||||
|
if (ret)
|
||||||
|
die(_("could not remove '%s'"), options.state_dir);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
case ACTION_EDIT_TODO:
|
||||||
|
options.action = "edit-todo";
|
||||||
|
options.dont_finish_rebase = 1;
|
||||||
|
goto run_rebase;
|
||||||
|
case ACTION_SHOW_CURRENT_PATCH:
|
||||||
|
options.action = "show-current-patch";
|
||||||
|
options.dont_finish_rebase = 1;
|
||||||
|
goto run_rebase;
|
||||||
|
case NO_ACTION:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
BUG("action: %d", action);
|
||||||
|
}
|
||||||
|
|
||||||
/* Make sure no rebase is in progress */
|
/* Make sure no rebase is in progress */
|
||||||
if (in_progress) {
|
if (in_progress) {
|
||||||
const char *last_slash = strrchr(options.state_dir, '/');
|
const char *last_slash = strrchr(options.state_dir, '/');
|
||||||
@ -720,6 +904,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
|
|||||||
|
|
||||||
options.revisions = revisions.buf;
|
options.revisions = revisions.buf;
|
||||||
|
|
||||||
|
run_rebase:
|
||||||
ret = !!run_specific_rebase(&options);
|
ret = !!run_specific_rebase(&options);
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
|
9
strbuf.c
9
strbuf.c
@ -120,6 +120,15 @@ void strbuf_trim_trailing_dir_sep(struct strbuf *sb)
|
|||||||
sb->buf[sb->len] = '\0';
|
sb->buf[sb->len] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void strbuf_trim_trailing_newline(struct strbuf *sb)
|
||||||
|
{
|
||||||
|
if (sb->len > 0 && sb->buf[sb->len - 1] == '\n') {
|
||||||
|
if (--sb->len > 0 && sb->buf[sb->len - 1] == '\r')
|
||||||
|
--sb->len;
|
||||||
|
sb->buf[sb->len] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void strbuf_ltrim(struct strbuf *sb)
|
void strbuf_ltrim(struct strbuf *sb)
|
||||||
{
|
{
|
||||||
char *b = sb->buf;
|
char *b = sb->buf;
|
||||||
|
3
strbuf.h
3
strbuf.h
@ -190,6 +190,9 @@ void strbuf_ltrim(struct strbuf *sb);
|
|||||||
/* Strip trailing directory separators */
|
/* Strip trailing directory separators */
|
||||||
void strbuf_trim_trailing_dir_sep(struct strbuf *sb);
|
void strbuf_trim_trailing_dir_sep(struct strbuf *sb);
|
||||||
|
|
||||||
|
/* Strip trailing LF or CR/LF */
|
||||||
|
void strbuf_trim_trailing_newline(struct strbuf *sb);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replace the contents of the strbuf with a reencoded form. Returns -1
|
* Replace the contents of the strbuf with a reencoded form. Returns -1
|
||||||
* on error, 0 on success.
|
* on error, 0 on success.
|
||||||
|
Reference in New Issue
Block a user