Merge branch 'pk/rebase-in-c-2-basic'
Rewrite "git rebase" in C. * pk/rebase-in-c-2-basic: builtin rebase: support `git rebase <upstream> <switch-to>` builtin rebase: only store fully-qualified refs in `options.head_name` builtin rebase: start a new rebase only if none is in progress builtin rebase: support --force-rebase builtin rebase: try to fast forward when possible builtin rebase: require a clean worktree builtin rebase: support the `verbose` and `diffstat` options builtin rebase: support --quiet builtin rebase: handle the pre-rebase hook and --no-verify builtin rebase: support `git rebase --onto A...B` builtin rebase: support --ontomaint
commit
e0720a3867
334
builtin/rebase.c
334
builtin/rebase.c
|
@ -16,6 +16,21 @@
|
||||||
#include "cache-tree.h"
|
#include "cache-tree.h"
|
||||||
#include "unpack-trees.h"
|
#include "unpack-trees.h"
|
||||||
#include "lockfile.h"
|
#include "lockfile.h"
|
||||||
|
#include "parse-options.h"
|
||||||
|
#include "commit.h"
|
||||||
|
#include "diff.h"
|
||||||
|
#include "wt-status.h"
|
||||||
|
#include "revision.h"
|
||||||
|
#include "commit-reach.h"
|
||||||
|
|
||||||
|
static char const * const builtin_rebase_usage[] = {
|
||||||
|
N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] "
|
||||||
|
"[<upstream>] [<branch>]"),
|
||||||
|
N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] "
|
||||||
|
"--root [<branch>]"),
|
||||||
|
N_("git rebase --continue | --abort | --skip | --edit-todo"),
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
static GIT_PATH_FUNC(apply_dir, "rebase-apply")
|
static GIT_PATH_FUNC(apply_dir, "rebase-apply")
|
||||||
static GIT_PATH_FUNC(merge_dir, "rebase-merge")
|
static GIT_PATH_FUNC(merge_dir, "rebase-merge")
|
||||||
|
@ -59,16 +74,32 @@ struct rebase_options {
|
||||||
const char *state_dir;
|
const char *state_dir;
|
||||||
struct commit *upstream;
|
struct commit *upstream;
|
||||||
const char *upstream_name;
|
const char *upstream_name;
|
||||||
|
const char *upstream_arg;
|
||||||
char *head_name;
|
char *head_name;
|
||||||
struct object_id orig_head;
|
struct object_id orig_head;
|
||||||
struct commit *onto;
|
struct commit *onto;
|
||||||
const char *onto_name;
|
const char *onto_name;
|
||||||
const char *revisions;
|
const char *revisions;
|
||||||
|
const char *switch_to;
|
||||||
int root;
|
int root;
|
||||||
struct commit *restrict_revision;
|
struct commit *restrict_revision;
|
||||||
int dont_finish_rebase;
|
int dont_finish_rebase;
|
||||||
|
enum {
|
||||||
|
REBASE_NO_QUIET = 1<<0,
|
||||||
|
REBASE_VERBOSE = 1<<1,
|
||||||
|
REBASE_DIFFSTAT = 1<<2,
|
||||||
|
REBASE_FORCE = 1<<3,
|
||||||
|
REBASE_INTERACTIVE_EXPLICIT = 1<<4,
|
||||||
|
} flags;
|
||||||
|
struct strbuf git_am_opt;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static int is_interactive(struct rebase_options *opts)
|
||||||
|
{
|
||||||
|
return opts->type == REBASE_INTERACTIVE ||
|
||||||
|
opts->type == REBASE_PRESERVE_MERGES;
|
||||||
|
}
|
||||||
|
|
||||||
/* Returns the filename prefixed by the state_dir */
|
/* Returns the filename prefixed by the state_dir */
|
||||||
static const char *state_dir_path(const char *filename, struct rebase_options *opts)
|
static const char *state_dir_path(const char *filename, struct rebase_options *opts)
|
||||||
{
|
{
|
||||||
|
@ -140,13 +171,25 @@ static int run_specific_rebase(struct rebase_options *opts)
|
||||||
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",
|
||||||
oid_to_hex(&opts->upstream->object.oid));
|
oid_to_hex(&opts->upstream->object.oid));
|
||||||
add_var(&script_snippet, "head_name", opts->head_name);
|
add_var(&script_snippet, "head_name",
|
||||||
|
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", oid_to_hex(&opts->onto->object.oid));
|
||||||
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 ?
|
||||||
oid_to_hex(&opts->restrict_revision->object.oid) : NULL);
|
oid_to_hex(&opts->restrict_revision->object.oid) : NULL);
|
||||||
|
add_var(&script_snippet, "GIT_QUIET",
|
||||||
|
opts->flags & REBASE_NO_QUIET ? "" : "t");
|
||||||
|
add_var(&script_snippet, "git_am_opt", opts->git_am_opt.buf);
|
||||||
|
add_var(&script_snippet, "verbose",
|
||||||
|
opts->flags & REBASE_VERBOSE ? "t" : "");
|
||||||
|
add_var(&script_snippet, "diffstat",
|
||||||
|
opts->flags & REBASE_DIFFSTAT ? "t" : "");
|
||||||
|
add_var(&script_snippet, "force_rebase",
|
||||||
|
opts->flags & REBASE_FORCE ? "t" : "");
|
||||||
|
if (opts->switch_to)
|
||||||
|
add_var(&script_snippet, "switch_to", opts->switch_to);
|
||||||
|
|
||||||
switch (opts->type) {
|
switch (opts->type) {
|
||||||
case REBASE_AM:
|
case REBASE_AM:
|
||||||
|
@ -213,6 +256,9 @@ static int reset_head(struct object_id *oid, const char *action,
|
||||||
*old_orig = NULL, oid_old_orig;
|
*old_orig = NULL, oid_old_orig;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
|
if (switch_to_branch && !starts_with(switch_to_branch, "refs/"))
|
||||||
|
BUG("Not a fully qualified branch: '%s'", switch_to_branch);
|
||||||
|
|
||||||
if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)
|
if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
@ -292,15 +338,98 @@ static int reset_head(struct object_id *oid, const char *action,
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int rebase_config(const char *var, const char *value, void *data)
|
||||||
|
{
|
||||||
|
struct rebase_options *opts = data;
|
||||||
|
|
||||||
|
if (!strcmp(var, "rebase.stat")) {
|
||||||
|
if (git_config_bool(var, value))
|
||||||
|
opts->flags |= REBASE_DIFFSTAT;
|
||||||
|
else
|
||||||
|
opts->flags &= !REBASE_DIFFSTAT;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return git_default_config(var, value, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Determines whether the commits in from..to are linear, i.e. contain
|
||||||
|
* no merge commits. This function *expects* `from` to be an ancestor of
|
||||||
|
* `to`.
|
||||||
|
*/
|
||||||
|
static int is_linear_history(struct commit *from, struct commit *to)
|
||||||
|
{
|
||||||
|
while (to && to != from) {
|
||||||
|
parse_commit(to);
|
||||||
|
if (!to->parents)
|
||||||
|
return 1;
|
||||||
|
if (to->parents->next)
|
||||||
|
return 0;
|
||||||
|
to = to->parents->item;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int can_fast_forward(struct commit *onto, struct object_id *head_oid,
|
||||||
|
struct object_id *merge_base)
|
||||||
|
{
|
||||||
|
struct commit *head = lookup_commit(the_repository, head_oid);
|
||||||
|
struct commit_list *merge_bases;
|
||||||
|
int res;
|
||||||
|
|
||||||
|
if (!head)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
merge_bases = get_merge_bases(onto, head);
|
||||||
|
if (merge_bases && !merge_bases->next) {
|
||||||
|
oidcpy(merge_base, &merge_bases->item->object.oid);
|
||||||
|
res = !oidcmp(merge_base, &onto->object.oid);
|
||||||
|
} else {
|
||||||
|
oidcpy(merge_base, &null_oid);
|
||||||
|
res = 0;
|
||||||
|
}
|
||||||
|
free_commit_list(merge_bases);
|
||||||
|
return res && is_linear_history(onto, head);
|
||||||
|
}
|
||||||
|
|
||||||
int cmd_rebase(int argc, const char **argv, const char *prefix)
|
int cmd_rebase(int argc, const char **argv, const char *prefix)
|
||||||
{
|
{
|
||||||
struct rebase_options options = {
|
struct rebase_options options = {
|
||||||
.type = REBASE_UNSPECIFIED,
|
.type = REBASE_UNSPECIFIED,
|
||||||
|
.flags = REBASE_NO_QUIET,
|
||||||
|
.git_am_opt = STRBUF_INIT,
|
||||||
};
|
};
|
||||||
const char *branch_name;
|
const char *branch_name;
|
||||||
int ret, flags;
|
int ret, flags, in_progress = 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 object_id merge_base;
|
||||||
|
struct option builtin_rebase_options[] = {
|
||||||
|
OPT_STRING(0, "onto", &options.onto_name,
|
||||||
|
N_("revision"),
|
||||||
|
N_("rebase onto given branch instead of upstream")),
|
||||||
|
OPT_BOOL(0, "no-verify", &ok_to_skip_pre_rebase,
|
||||||
|
N_("allow pre-rebase hook to run")),
|
||||||
|
OPT_NEGBIT('q', "quiet", &options.flags,
|
||||||
|
N_("be quiet. implies --no-stat"),
|
||||||
|
REBASE_NO_QUIET| REBASE_VERBOSE | REBASE_DIFFSTAT),
|
||||||
|
OPT_BIT('v', "verbose", &options.flags,
|
||||||
|
N_("display a diffstat of what changed upstream"),
|
||||||
|
REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),
|
||||||
|
{OPTION_NEGBIT, 'n', "no-stat", &options.flags, NULL,
|
||||||
|
N_("do not show diffstat of what changed upstream"),
|
||||||
|
PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT },
|
||||||
|
OPT_BIT('f', "force-rebase", &options.flags,
|
||||||
|
N_("cherry-pick all commits, even if unchanged"),
|
||||||
|
REBASE_FORCE),
|
||||||
|
OPT_BIT(0, "no-ff", &options.flags,
|
||||||
|
N_("cherry-pick all commits, even if unchanged"),
|
||||||
|
REBASE_FORCE),
|
||||||
|
OPT_END(),
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* NEEDSWORK: Once the builtin rebase has been tested enough
|
* NEEDSWORK: Once the builtin rebase has been tested enough
|
||||||
|
@ -318,13 +447,70 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
|
||||||
BUG("sane_execvp() returned???");
|
BUG("sane_execvp() returned???");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argc != 2)
|
if (argc == 2 && !strcmp(argv[1], "-h"))
|
||||||
die(_("Usage: %s <base>"), argv[0]);
|
usage_with_options(builtin_rebase_usage,
|
||||||
|
builtin_rebase_options);
|
||||||
|
|
||||||
prefix = setup_git_directory();
|
prefix = setup_git_directory();
|
||||||
trace_repo_setup(prefix);
|
trace_repo_setup(prefix);
|
||||||
setup_work_tree();
|
setup_work_tree();
|
||||||
|
|
||||||
git_config(git_default_config, NULL);
|
git_config(rebase_config, &options);
|
||||||
|
|
||||||
|
if (is_directory(apply_dir())) {
|
||||||
|
options.type = REBASE_AM;
|
||||||
|
options.state_dir = apply_dir();
|
||||||
|
} else if (is_directory(merge_dir())) {
|
||||||
|
strbuf_reset(&buf);
|
||||||
|
strbuf_addf(&buf, "%s/rewritten", merge_dir());
|
||||||
|
if (is_directory(buf.buf)) {
|
||||||
|
options.type = REBASE_PRESERVE_MERGES;
|
||||||
|
options.flags |= REBASE_INTERACTIVE_EXPLICIT;
|
||||||
|
} else {
|
||||||
|
strbuf_reset(&buf);
|
||||||
|
strbuf_addf(&buf, "%s/interactive", merge_dir());
|
||||||
|
if(file_exists(buf.buf)) {
|
||||||
|
options.type = REBASE_INTERACTIVE;
|
||||||
|
options.flags |= REBASE_INTERACTIVE_EXPLICIT;
|
||||||
|
} else
|
||||||
|
options.type = REBASE_MERGE;
|
||||||
|
}
|
||||||
|
options.state_dir = merge_dir();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.type != REBASE_UNSPECIFIED)
|
||||||
|
in_progress = 1;
|
||||||
|
|
||||||
|
argc = parse_options(argc, argv, prefix,
|
||||||
|
builtin_rebase_options,
|
||||||
|
builtin_rebase_usage, 0);
|
||||||
|
|
||||||
|
if (argc > 2)
|
||||||
|
usage_with_options(builtin_rebase_usage,
|
||||||
|
builtin_rebase_options);
|
||||||
|
|
||||||
|
/* Make sure no rebase is in progress */
|
||||||
|
if (in_progress) {
|
||||||
|
const char *last_slash = strrchr(options.state_dir, '/');
|
||||||
|
const char *state_dir_base =
|
||||||
|
last_slash ? last_slash + 1 : options.state_dir;
|
||||||
|
const char *cmd_live_rebase =
|
||||||
|
"git rebase (--continue | --abort | --skip)";
|
||||||
|
strbuf_reset(&buf);
|
||||||
|
strbuf_addf(&buf, "rm -fr \"%s\"", options.state_dir);
|
||||||
|
die(_("It seems that there is already a %s directory, and\n"
|
||||||
|
"I wonder if you are in the middle of another rebase. "
|
||||||
|
"If that is the\n"
|
||||||
|
"case, please try\n\t%s\n"
|
||||||
|
"If that is not the case, please\n\t%s\n"
|
||||||
|
"and run me again. I am stopping in case you still "
|
||||||
|
"have something\n"
|
||||||
|
"valuable there.\n"),
|
||||||
|
state_dir_base, cmd_live_rebase, buf.buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(options.flags & REBASE_NO_QUIET))
|
||||||
|
strbuf_addstr(&options.git_am_opt, " -q");
|
||||||
|
|
||||||
switch (options.type) {
|
switch (options.type) {
|
||||||
case REBASE_MERGE:
|
case REBASE_MERGE:
|
||||||
|
@ -343,10 +529,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.root) {
|
if (!options.root) {
|
||||||
if (argc < 2)
|
if (argc < 1)
|
||||||
die("TODO: handle @{upstream}");
|
die("TODO: handle @{upstream}");
|
||||||
else {
|
else {
|
||||||
options.upstream_name = argv[1];
|
options.upstream_name = argv[0];
|
||||||
argc--;
|
argc--;
|
||||||
argv++;
|
argv++;
|
||||||
if (!strcmp(options.upstream_name, "-"))
|
if (!strcmp(options.upstream_name, "-"))
|
||||||
|
@ -355,6 +541,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
|
||||||
options.upstream = peel_committish(options.upstream_name);
|
options.upstream = peel_committish(options.upstream_name);
|
||||||
if (!options.upstream)
|
if (!options.upstream)
|
||||||
die(_("invalid upstream '%s'"), options.upstream_name);
|
die(_("invalid upstream '%s'"), options.upstream_name);
|
||||||
|
options.upstream_arg = options.upstream_name;
|
||||||
} else
|
} else
|
||||||
die("TODO: upstream for --root");
|
die("TODO: upstream for --root");
|
||||||
|
|
||||||
|
@ -362,7 +549,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
|
||||||
if (!options.onto_name)
|
if (!options.onto_name)
|
||||||
options.onto_name = options.upstream_name;
|
options.onto_name = options.upstream_name;
|
||||||
if (strstr(options.onto_name, "...")) {
|
if (strstr(options.onto_name, "...")) {
|
||||||
die("TODO");
|
if (get_oid_mb(options.onto_name, &merge_base) < 0)
|
||||||
|
die(_("'%s': need exactly one merge base"),
|
||||||
|
options.onto_name);
|
||||||
|
options.onto = lookup_commit_or_die(&merge_base,
|
||||||
|
options.onto_name);
|
||||||
} else {
|
} else {
|
||||||
options.onto = peel_committish(options.onto_name);
|
options.onto = peel_committish(options.onto_name);
|
||||||
if (!options.onto)
|
if (!options.onto)
|
||||||
|
@ -375,11 +566,25 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
|
||||||
* branch_name -- branch/commit being rebased, or
|
* branch_name -- branch/commit being rebased, or
|
||||||
* HEAD (already detached)
|
* HEAD (already detached)
|
||||||
* orig_head -- commit object name of tip of the branch before rebasing
|
* orig_head -- commit object name of tip of the branch before rebasing
|
||||||
* head_name -- refs/heads/<that-branch> or "detached HEAD"
|
* head_name -- refs/heads/<that-branch> or NULL (detached HEAD)
|
||||||
*/
|
*/
|
||||||
if (argc > 1)
|
if (argc == 1) {
|
||||||
die("TODO: handle switch_to");
|
/* Is it "rebase other branchname" or "rebase other commit"? */
|
||||||
else {
|
branch_name = argv[0];
|
||||||
|
options.switch_to = argv[0];
|
||||||
|
|
||||||
|
/* Is it a local branch? */
|
||||||
|
strbuf_reset(&buf);
|
||||||
|
strbuf_addf(&buf, "refs/heads/%s", branch_name);
|
||||||
|
if (!read_ref(buf.buf, &options.orig_head))
|
||||||
|
options.head_name = xstrdup(buf.buf);
|
||||||
|
/* If not is it a valid ref (branch or commit)? */
|
||||||
|
else if (!get_oid(branch_name, &options.orig_head))
|
||||||
|
options.head_name = NULL;
|
||||||
|
else
|
||||||
|
die(_("fatal: no such branch/commit '%s'"),
|
||||||
|
branch_name);
|
||||||
|
} else if (argc == 0) {
|
||||||
/* Do not need to switch branches, we are already on it. */
|
/* Do not need to switch branches, we are already on it. */
|
||||||
options.head_name =
|
options.head_name =
|
||||||
xstrdup_or_null(resolve_ref_unsafe("HEAD", 0, NULL,
|
xstrdup_or_null(resolve_ref_unsafe("HEAD", 0, NULL,
|
||||||
|
@ -392,13 +597,115 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
|
||||||
branch_name = options.head_name;
|
branch_name = options.head_name;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
options.head_name = xstrdup("detached HEAD");
|
free(options.head_name);
|
||||||
|
options.head_name = NULL;
|
||||||
branch_name = "HEAD";
|
branch_name = "HEAD";
|
||||||
}
|
}
|
||||||
if (get_oid("HEAD", &options.orig_head))
|
if (get_oid("HEAD", &options.orig_head))
|
||||||
die(_("Could not resolve HEAD to a revision"));
|
die(_("Could not resolve HEAD to a revision"));
|
||||||
|
} else
|
||||||
|
BUG("unexpected number of arguments left to parse");
|
||||||
|
|
||||||
|
if (read_index(the_repository->index) < 0)
|
||||||
|
die(_("could not read index"));
|
||||||
|
|
||||||
|
if (require_clean_work_tree("rebase",
|
||||||
|
_("Please commit or stash them."), 1, 1)) {
|
||||||
|
ret = 1;
|
||||||
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now we are rebasing commits upstream..orig_head (or with --root,
|
||||||
|
* everything leading up to orig_head) on top of onto.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check if we are already based on onto with linear history,
|
||||||
|
* but this should be done only when upstream and onto are the same
|
||||||
|
* and if this is not an interactive rebase.
|
||||||
|
*/
|
||||||
|
if (can_fast_forward(options.onto, &options.orig_head, &merge_base) &&
|
||||||
|
!is_interactive(&options) && !options.restrict_revision &&
|
||||||
|
!oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) {
|
||||||
|
int flag;
|
||||||
|
|
||||||
|
if (!(options.flags & REBASE_FORCE)) {
|
||||||
|
/* Lazily switch to the target branch if needed... */
|
||||||
|
if (options.switch_to) {
|
||||||
|
struct object_id oid;
|
||||||
|
|
||||||
|
if (get_oid(options.switch_to, &oid) < 0) {
|
||||||
|
ret = !!error(_("could not parse '%s'"),
|
||||||
|
options.switch_to);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
strbuf_reset(&buf);
|
||||||
|
strbuf_addf(&buf, "rebase: checkout %s",
|
||||||
|
options.switch_to);
|
||||||
|
if (reset_head(&oid, "checkout",
|
||||||
|
options.head_name, 0) < 0) {
|
||||||
|
ret = !!error(_("could not switch to "
|
||||||
|
"%s"),
|
||||||
|
options.switch_to);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(options.flags & REBASE_NO_QUIET))
|
||||||
|
; /* be quiet */
|
||||||
|
else if (!strcmp(branch_name, "HEAD") &&
|
||||||
|
resolve_ref_unsafe("HEAD", 0, NULL, &flag))
|
||||||
|
puts(_("HEAD is up to date."));
|
||||||
|
else
|
||||||
|
printf(_("Current branch %s is up to date.\n"),
|
||||||
|
branch_name);
|
||||||
|
ret = !!finish_rebase(&options);
|
||||||
|
goto cleanup;
|
||||||
|
} else if (!(options.flags & REBASE_NO_QUIET))
|
||||||
|
; /* be quiet */
|
||||||
|
else if (!strcmp(branch_name, "HEAD") &&
|
||||||
|
resolve_ref_unsafe("HEAD", 0, NULL, &flag))
|
||||||
|
puts(_("HEAD is up to date, rebase forced."));
|
||||||
|
else
|
||||||
|
printf(_("Current branch %s is up to date, rebase "
|
||||||
|
"forced.\n"), branch_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If a hook exists, give it a chance to interrupt*/
|
||||||
|
if (!ok_to_skip_pre_rebase &&
|
||||||
|
run_hook_le(NULL, "pre-rebase", options.upstream_arg,
|
||||||
|
argc ? argv[0] : NULL, NULL))
|
||||||
|
die(_("The pre-rebase hook refused to rebase."));
|
||||||
|
|
||||||
|
if (options.flags & REBASE_DIFFSTAT) {
|
||||||
|
struct diff_options opts;
|
||||||
|
|
||||||
|
if (options.flags & REBASE_VERBOSE)
|
||||||
|
printf(_("Changes from %s to %s:\n"),
|
||||||
|
oid_to_hex(&merge_base),
|
||||||
|
oid_to_hex(&options.onto->object.oid));
|
||||||
|
|
||||||
|
/* We want color (if set), but no pager */
|
||||||
|
diff_setup(&opts);
|
||||||
|
opts.stat_width = -1; /* use full terminal width */
|
||||||
|
opts.stat_graph_width = -1; /* respect statGraphWidth config */
|
||||||
|
opts.output_format |=
|
||||||
|
DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
|
||||||
|
opts.detect_rename = DIFF_DETECT_RENAME;
|
||||||
|
diff_setup_done(&opts);
|
||||||
|
diff_tree_oid(&merge_base, &options.onto->object.oid,
|
||||||
|
"", &opts);
|
||||||
|
diffcore_std(&opts);
|
||||||
|
diff_flush(&opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Detach HEAD and reset the tree */
|
||||||
|
if (options.flags & REBASE_NO_QUIET)
|
||||||
|
printf(_("First, rewinding head to replay your work on top of "
|
||||||
|
"it...\n"));
|
||||||
|
|
||||||
strbuf_addf(&msg, "rebase: checkout %s", options.onto_name);
|
strbuf_addf(&msg, "rebase: checkout %s", options.onto_name);
|
||||||
if (reset_head(&options.onto->object.oid, "checkout", NULL, 1))
|
if (reset_head(&options.onto->object.oid, "checkout", NULL, 1))
|
||||||
die(_("Could not detach HEAD"));
|
die(_("Could not detach HEAD"));
|
||||||
|
@ -415,6 +722,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
|
||||||
|
|
||||||
ret = !!run_specific_rebase(&options);
|
ret = !!run_specific_rebase(&options);
|
||||||
|
|
||||||
|
cleanup:
|
||||||
strbuf_release(&revisions);
|
strbuf_release(&revisions);
|
||||||
free(options.head_name);
|
free(options.head_name);
|
||||||
return ret;
|
return ret;
|
||||||
|
|
Loading…
Reference in New Issue