--- Makefile | 2 +- builtin.h | 1 + builtin/rebase.c (new) | 752 ++++++++++++++++++++++++++++++++ commit.c | 4 +- commit.h | 4 +- contrib/examples/git-rebase.sh (new +x) | 532 ++++++++++++++++++++++ git-rebase.sh (gone) | 532 ---------------------- git.c | 1 + 8 files changed, 1291 insertions(+), 537 deletions(-) create mode 100644 builtin/rebase.c create mode 100755 contrib/examples/git-rebase.sh delete mode 100755 git-rebase.sh diff --git a/Makefile b/Makefile index 83972a2..223d50b 100644 --- a/Makefile +++ b/Makefile @@ -444,7 +444,6 @@ SCRIPT_SH += git-merge-resolve.sh SCRIPT_SH += git-mergetool.sh SCRIPT_SH += git-pull.sh SCRIPT_SH += git-quiltimport.sh -SCRIPT_SH += git-rebase.sh SCRIPT_SH += git-rebase--am.sh SCRIPT_SH += git-rebase--interactive.sh SCRIPT_SH += git-rebase--merge.sh @@ -903,6 +902,7 @@ BUILTIN_OBJS += builtin/prune-packed.o BUILTIN_OBJS += builtin/prune.o BUILTIN_OBJS += builtin/push.o BUILTIN_OBJS += builtin/read-tree.o +BUILTIN_OBJS += builtin/rebase.o BUILTIN_OBJS += builtin/receive-pack.o BUILTIN_OBJS += builtin/reflog.o BUILTIN_OBJS += builtin/remote.o diff --git a/builtin.h b/builtin.h index 7e7bbd6..431bbbf 100644 --- a/builtin.h +++ b/builtin.h @@ -108,6 +108,7 @@ extern int cmd_prune(int argc, const char **argv, const char *prefix); extern int cmd_prune_packed(int argc, const char **argv, const char *prefix); extern int cmd_push(int argc, const char **argv, const char *prefix); extern int cmd_read_tree(int argc, const char **argv, const char *prefix); +extern int cmd_rebase(int argc, const char **argv, const char *prefix); extern int cmd_receive_pack(int argc, const char **argv, const char *prefix); extern int cmd_reflog(int argc, const char **argv, const char *prefix); extern int cmd_remote(int argc, const char **argv, const char *prefix); diff --git a/builtin/rebase.c b/builtin/rebase.c new file mode 100644 index 0000000..29eff0e --- /dev/null +++ b/builtin/rebase.c @@ -0,0 +1,752 @@ +#include "cache.h" +#include "parse-options.h" +#include "argv-array.h" +#include "run-command.h" +#include "tree-walk.h" +#include "unpack-trees.h" +#include "diff.h" +#include "commit.h" +#include "revision.h" +#include "submodule.h" +#include "commit.h" +#include "dir.h" + +#define REBASE_AM 1 +#define REBASE_MERGE 2 +#define REBASE_INTERACTIVE 3 + +static const char * 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 +}; + +/* These are exported as environment variables for git-rebase--*.sh */ +static int action_abort; +static int action_continue; +static int action_skip; +static int autosquash; +static int force_rebase; +static int keep_empty; +static int preserve_merges; +static int quiet; +static int rerere_autoupdate; +static int root; +static int verbose; +static const char *exec_cmd; +static const char *head_name; +static const char *onto; +static const char *orig_head; +static struct strbuf revisions = STRBUF_INIT; +static const char *state_basedir; +static const char *strategy; +static const char *strategy_opts; +static const char *switch_to; +static const char *squash_onto; + +static int do_merge; +static int edit_todo; +static int interactive_rebase; +static int rebase_type; +static int show_stat; + +static char *read_file(const char *name) +{ + struct strbuf sb = STRBUF_INIT; + if (strbuf_read_file(&sb, + git_path("%s/%s", state_basedir, name), + 0) >= 0) + return strbuf_detach(&sb, NULL); + else + return NULL; +} + +static char *read_file_or_die(const char *name) +{ + struct strbuf sb = STRBUF_INIT; + strbuf_read_file_or_die(&sb, + git_path("%s/%s", state_basedir, name), + 0); + return strbuf_detach(&sb, NULL); +} + +static void read_bool(const char *name, int *var) +{ + char *buf = read_file(name); + if (buf) { + *var = buf[0] && !isspace(buf[0]); + free(buf); + } +} + +static int read_bool_or_die(const char *name) +{ + char *buf = read_file_or_die(name); + int ret = buf[0] && !isspace(buf[0]); + free(buf); + return ret; +} + +/* + * Note: + * + * After git-rebase--*.sh are integrated, we should probably adopt + * git-config format and store everything in one file instead of so + * many like this. state_dir will be something different to avoid + * misuse by old rebase versions. This code will stay for a few major + * releases before being phased out. + */ +static void read_basic_state() +{ + head_name = read_file_or_die("head-name"); + onto = read_file_or_die("onto"); + /* + * 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. + */ + if ((orig_head = read_file("orig-head")) == NULL) + orig_head = read_file_or_die("head"); + quiet = read_bool_or_die("quiet"); + read_bool("verbose", &verbose); + strategy = read_file("strategy"); + strategy_opts = read_file("strategy_opts"); + read_bool("allow_rerere_autoupdate", &rerere_autoupdate); +} + +static void push_env_string(struct argv_array *argv, + const char *name, + const char *value) +{ + struct strbuf sb = STRBUF_INIT; + strbuf_addf(&sb, "%s=%s", name, value); + argv_array_push(argv, sb.buf); + strbuf_release(&sb); +} + +static void push_env_bool(struct argv_array *argv, + const char *name, + int value) +{ + struct strbuf sb = STRBUF_INIT; + strbuf_addf(&sb, "%s=%s", name, value ? "t" : ""); + argv_array_push(argv, sb.buf); + strbuf_release(&sb); +} + +static int run_specific_rebase() +{ + struct child_process cmd; + struct argv_array env = ARGV_ARRAY_INIT; + const char *argv[2]; + int ret; + + if (interactive_rebase == -1) { + argv_array_push(&env, "GIT_EDITOR=:"); + autosquash = 0; + } + push_env_bool(&env, "git_quiet", quiet); + if (action_continue) + argv_array_push(&env, "action=continue"); + else if (action_skip) + argv_array_push(&env, "action=skip"); + else if (action_abort) + argv_array_push(&env, "action=abort"); + else if (edit_todo) + argv_array_push(&env, "action=edit-todo"); + else + argv_array_push(&env, "action="); + push_env_bool(&env, "allow_rerere_autoupdate", rerere_autoupdate); + push_env_bool(&env, "autosquash", autosquash); + push_env_string(&env, "cmd", exec_cmd); + push_env_bool(&env, "force_rebase", force_rebase); + push_env_string(&env, "git_am_opt", ""/* git_am_opt */); + push_env_string(&env, "head_name", head_name); + push_env_bool(&env, "keep_empty", keep_empty); + push_env_string(&env, "onto", onto); + push_env_string(&env, "orig_head", orig_head); + push_env_bool(&env, "preserve_merges", preserve_merges); + push_env_bool(&env, "rebase_root", root); + push_env_string(&env, "revisions", revisions.buf); + push_env_string(&env, "squash_onto", squash_onto); + push_env_string(&env, "state_dir", git_path("%s", state_basedir)); + push_env_string(&env, "strategy", strategy); + push_env_string(&env, "strategy_opts", strategy_opts); + push_env_string(&env, "switch_to", switch_to); + /* upstream */ + push_env_bool(&env, "verbose", verbose); + + switch (rebase_type) { + case REBASE_INTERACTIVE: argv[0] = "rebase--interactive"; break; + case REBASE_AM: argv[0] = "rebase--am"; break; + case REBASE_MERGE: argv[0] = "rebase--merge"; break; + default: + die("BUG: unsupported rebase type %d", rebase_type); + } + argv[1] = NULL; + + memset(&cmd, 0, sizeof(cmd)); + cmd.in = 0; + cmd.out = 1; + cmd.err = 2; + cmd.git_cmd = 1; + cmd.env = env.argv; + cmd.argv = argv; + ret = run_command(&cmd); + if (ret) + die_errno("failed to run %s", argv[0]); + + argv_array_clear(&env); + return ret; +} + +static int do_continue() +{ + unsigned char sha1[20]; + if (get_sha1("HEAD", sha1)) + die(_("Cannot read HEAD")); + if (read_cache_unmerged()) { + printf(_("You must edit all merge conflicts and then\n" + "mark them as resolved using 'git add'\n")); + return 1; + } + read_basic_state(); + return run_specific_rebase(); +} + +static int do_skip() +{ + int ret, error = 0; + unsigned char sha1[20]; + if (get_sha1("HEAD", sha1)) + die(_("Cannot read HEAD")); + + ret = reset_tree(lookup_commit(sha1)->tree, quiet, 1, &error); + if (ret) + return ret; + read_basic_state(); + return run_specific_rebase(); +} + +static int do_abort() +{ + int ret, error = 0; + unsigned char sha1[20]; + const char *rerere[] = { "rerere", "clear", NULL }; + struct strbuf path = STRBUF_INIT; + + ret = run_command_v_opt(rerere, RUN_GIT_CMD); + if (ret) + return ret; + read_basic_state(); + if (!prefixcmp(head_name, "refs/")) + create_symref("HEAD", head_name, "rebase: aborting"); + + if (get_sha1(orig_head, sha1)) + die(_("Cannot read %s"), orig_head); + + ret = reset_tree(lookup_commit(sha1)->tree, quiet, 1, &error); + if (ret) + return ret; + + strbuf_addstr(&path, git_path("%s", state_basedir)); + remove_dir_recursively(&path, 0); + strbuf_release(&path); + return 0; +} + +static int do_edit_todo() +{ + if (rebase_type != REBASE_INTERACTIVE) + die(_("The --edit-todo action can only be " + "used during interactive rebase.")); + return run_specific_rebase(); +} + +static void error_on_missing_default_upstream() +{ + unsigned char sha1[20]; + const char *head = resolve_ref_unsafe("HEAD", sha1, 0, NULL); + if (!head) + die(_("Failed to resolve HEAD as a valid ref.")); + if (!strcmp(head, "HEAD")) { + printf(_("You are not currently on a branch. Please specify which\n" + "branch you want to rebase against. See git-rebase(1) for details.\n" + "\n" + " git rebase <branch>\n")); + return; + } + + if (!prefixcmp(head, "refs/heads/")) + head += strlen("refs/heads/"); + printf(_("There is no tracking information for the current branch.\n" + "Please specify which branch you want to rebase against.\n" + "See git-rebase(1) for details\n" + "\n" + " git rebase <branch>\n" + "\n" + "If you wish to set tracking information for this branch you can do so with:\n" + "\n" + " git branch --set-upstream-to=$remote/<branch> %s\n"), + head); +} + +static int determine_upstream(int argc, const char **argv, + const struct option *options, + const char **upstream_name, + const char **upstream_arg) +{ + int consumed = 0; + if (!root) { + unsigned char sha1[20]; + if (argc) { + *upstream_name = argv[0]; + argv++; + argc--; + consumed++; + } else { + *upstream_name = "@{upstream}"; + if (get_sha1(*upstream_name, sha1)) { + error_on_missing_default_upstream(); + exit(1); + } + } + if (get_sha1(*upstream_name, sha1)) + die(_("Failed to resolve %s."), *upstream_name); + lookup_commit_or_die(sha1, *upstream_name); + *upstream_arg = *upstream_name; + return consumed; + } + + if (!onto) { + unsigned char sha1[20]; + struct strbuf sb = STRBUF_INIT; + if (commit_tree(&sb, EMPTY_TREE_SHA1_BIN, NULL, sha1, NULL, NULL)) + die(_("failed to create empty-tree commit")); + onto = xstrdup(sha1_to_hex(sha1)); + squash_onto = onto; + } + if (argc > 1) + usage_with_options(builtin_rebase_usage, + options); + *upstream_arg = "--root"; + return consumed; +} + +static void require_clean_work_tree() +{ + struct rev_info rev; + unsigned char sha1[20]; + int err = 0; + + if (get_sha1("HEAD", sha1)) + die("HEAD is not a valid ref"); + + if (read_cache() < 0) + die(_("unable to read index file")); + refresh_cache(REFRESH_QUIET | REFRESH_IGNORE_SUBMODULES); + + init_revisions(&rev, NULL); + DIFF_OPT_SET(&rev.diffopt, QUICK); + DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG); + handle_ignore_submodules_arg(&rev.diffopt, "all"); + diff_setup_done(&rev.diffopt); + run_diff_files(&rev, 0); + if (DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES)) { + fputs(_("Cannot rebase: You have unstaged changes.\n"), stderr); + err = 1; + } + + init_revisions(&rev, NULL); + DIFF_OPT_SET(&rev.diffopt, QUICK); + DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG); + handle_ignore_submodules_arg(&rev.diffopt, "all"); + diff_setup_done(&rev.diffopt); + add_head_to_pending(&rev); + run_diff_index(&rev, 1); + if (DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES)) { + const char *msg = err ? + _("Additionally, your index contains uncommitted changes.\n") : + _("Cannot rebase: Your index contains uncommitted changes.\n"); + fputs(msg, stderr); + err = 1; + } + + if (err) { + fputs(_("Please commit or stash them.\n"), stderr); + exit(1); + } +} + +static void run_pre_rebase_hook(int argc, const char **argv, + const char *upstream_arg) +{ + /* + if test -z "$ok_to_skip_pre_rebase" && + test -x "$GIT_DIR/hooks/pre-rebase" + then + "$GIT_DIR/hooks/pre-rebase" ${1+"$@"} || + die "$(gettext "The pre-rebase hook refused to rebase.")" + fi + */ +} + +static int do_rebase(int argc, const char **argv, + const struct option *options) +{ + const char *upstream_name = NULL; + const char *upstream_arg = NULL; + const char *branch_name; + const char *onto_name; + unsigned char onto_sha1[20]; + + int n; + if (root && !onto && !interactive_rebase) + interactive_rebase = -1; /* implied */ + + if (interactive_rebase) { + rebase_type = REBASE_INTERACTIVE; + state_basedir = "rebase-merge"; + } else if (do_merge) { + rebase_type = REBASE_MERGE; + state_basedir = "rebase-merge"; + } else { + rebase_type = REBASE_AM; + state_basedir = "rebase-apply"; + } + + n = determine_upstream(argc, argv, options, + &upstream_name, &upstream_arg); + argc += n; + argv += n; + + if (root) + upstream_arg = "--root"; + + /* Make sure the branch to rebase onto is valid. */ + onto_name = onto ? onto : upstream_name; + if (strstr(onto, "...")) { + /* +case "$onto_name" in +*...*) + if left=${onto_name%...*} right=${onto_name#*...} && + onto=$(git merge-base --all ${left:-HEAD} ${right:-HEAD}) + then + case "$onto" in + ?*"$LF"?*) + die "$(eval_gettext "\$onto_name: there are more than one merge bases")" + ;; + '') + die "$(eval_gettext "\$onto_name: there is no merge base")" + ;; + esac + else + die "$(eval_gettext "\$onto_name: there is no merge base")" + fi + ;; + */ + } else { + if (get_sha1(onto_name, onto_sha1) || !lookup_commit(onto_sha1)) + die(_("Does not point to a valid commit: %s"), onto_name); + } + + /* + * If the branch to rebase is given, that is the branch we + * will rebase + * + * $branch_name -- branch being rebased, or HEAD (already + * detached) + * + * $orig_head -- commit object name of tip of the branch + * before rebasing + * + * $head_name -- refs/heads/<that-branch> or "detached HEAD" + */ + switch (argc) { + case 1: + /* Is it "rebase other $branchname" or "rebase other $commit"? */ + branch_name = argv[0]; + switch_to = argv[0]; + /* + if git show-ref --verify --quiet -- "refs/heads/$1" && + orig_head=$(git rev-parse -q --verify "refs/heads/$1") + then + head_name="refs/heads/$1" + elif orig_head=$(git rev-parse -q --verify "$1") + then + head_name="detached HEAD" + else + die "$(eval_gettext "fatal: no such branch: \$branch_name")" + fi + */ + break; + + case 0: + /* + # Do not need to switch branches, we are already on it. + if branch_name=`git symbolic-ref -q HEAD` + then + head_name=$branch_name + branch_name=`expr "z$branch_name" : 'zrefs/heads/\(.*\)'` + else + head_name="detached HEAD" + branch_name=HEAD ;# detached + fi + orig_head=$(git rev-parse --verify "${branch_name}^0") || exit + */ + break; + default: + die("BUG: unexpected number of arguments left to parse"); + } + + require_clean_work_tree(); + + /* + * 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. + */ + + /* +mb=$(git merge-base "$onto" "$orig_head") +if test "$type" != interactive && test "$upstream" = "$onto" && + test "$mb" = "$onto" && + # linear history? + ! (git rev-list --parents "$onto".."$orig_head" | sane_grep " .* ") > /dev/null +then + if test -z "$force_rebase" + then + # Lazily switch to the target branch if needed... + test -z "$switch_to" || git checkout "$switch_to" -- + say "$(eval_gettext "Current branch \$branch_name is up to date.")" + exit 0 + else + say "$(eval_gettext "Current branch \$branch_name is up to date, rebase forced.")" + fi +fi + */ + + /* If a hook exists, give it a chance to interrupt */ + run_pre_rebase_hook(argc, argv, upstream_arg); + + /* +if test -n "$diffstat" +then + if test -n "$verbose" + then + echo "$(eval_gettext "Changes from \$mb to \$onto:")" + fi + # We want color (if set), but no pager + GIT_PAGER='' git diff --stat --summary "$mb" "$onto" +fi + */ + + if (rebase_type == REBASE_INTERACTIVE) + return run_specific_rebase(); + + /* Detach HEAD and reset the tree */ + /* +say "$(gettext "First, rewinding head to replay your work on top of it...")" +git checkout -q "$onto^0" || die "could not detach HEAD" +git update-ref ORIG_HEAD $orig_head + */ + + /* + * If the $onto is a proper descendant of the tip of the + * branch, then we just fast-forwarded. + */ + if (!hashcmp(mb, orig_head)) { + printf(_("Fast-forwarded %s to %s."), branch_name, onto_name); + move_to_original_branch(); + return 0; + } + + if (root) + strbuf_addf(&revisions, "%s..%s", onto, orig_head); + else + strbuf_addf(&revisions, "%s..%s", upstream, orig_head); + + return run_specific_rebase(); +} + +static int git_rebase_config(const char *name, const char *value, void *data) +{ + if (!strcmp(name, "rebase.stat")) { + show_stat = git_config_bool(name, value); + return 0; + } else if (!strcmp(name, "rebase.autosquash")) { + autosquash = git_config_bool(name, value); + return 0; + } + return git_default_config(name, value, data); +} + +int cmd_rebase(int argc, const char **argv, const char *prefix) +{ + const char *whitespace_opt = NULL; + int committer_date_is_author_date = 0; + int context_opt = 0; + int ignore_date = 0; + int ignore_whitespace = 0; + int no_ff = 0; + int pre_rebase = 1; + struct option options[] = { + OPT__VERBOSE(&verbose, + N_("display a diffstat of what changed upstream")), + OPT__QUIET(&quiet, + N_("be quiet. implies --no-stat")), + OPT_STRING(0, "onto", &onto, "ref", + N_("rebase onto given branch instead of upstream")), + OPT_BOOL('p', "preserve-merges", &preserve_merges, + N_("try to recreate merges instead of ignoring them")), + OPT_STRING('s', "stragegy", &strategy, "strategy", + N_("use the given merge strategy")), + { OPTION_SET_INT, 0, "no-ff", &no_ff, NULL, + N_("cherry-pick all commits, even if unchanged"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 }, + OPT_BOOL('m', "merge", &do_merge, + N_("use merging strategies to rebase")), + OPT_BOOL('i', "interactive", &interactive_rebase, + N_("let the user edit the list of commits to rebase")), + OPT_STRING('x', "exec", &exec_cmd, "command", + N_("add exec lines after each commit of the editable list")), + OPT_BOOL('k', "keep-empty", &keep_empty, + N_("preserve empty commits during rebase")), + OPT_BOOL('f', "force-rebase", &force_rebase, + N_("force rebase even if branch is up to date")), + OPT_STRING('X', "strategy-options", &strategy_opts, "options", + N_("pass the argument through to the merge strategy")), + { OPTION_SET_INT, 0, "stat", &show_stat, NULL, + N_("display a diffstat of what changed upstream"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 }, + OPT_SET_INT('n', "no-stat", &show_stat, + N_("do not show diffstat of what changed upstream"), 0), + OPT_BOOL(0, "verify", &pre_rebase, + N_("allow pre-rebase hook to run")), + OPT_BOOL(0, "rerere-autoupdate", &rerere_autoupdate, + N_("allow rerere to update index with resolved conflicts")), + OPT_BOOL(0, "root", &root, + N_("rebase all reachable commits up to the root(s)")), + OPT_BOOL(0, "autosquash", &autosquash, + N_("move commits that begin with squash!/fixup! under -i")), + OPT_BOOL(0, "committer-date-is-author-date", &committer_date_is_author_date, + N_("lie about committer date")), + OPT_BOOL(0, "ignore-date", &ignore_date, + N_("use current timestamp for author date")), + OPT_STRING(0, "whitespace", &whitespace_opt, "mode", + N_("detect new or modified lines that have whitespace errors")), + OPT_INTEGER('C', NULL, &context_opt, + N_("ensure at least <n> lines of context match by 'git apply'")), + OPT_BOOL(0, "ignore-whitespace", &ignore_whitespace, + N_("ignore changes in whitespace when finding context")), + + OPT_GROUP("Actions"), + OPT_BOOL(0, "continue", &action_continue, + N_("continue")), + OPT_BOOL(0, "abort", &action_abort, + N_("abort and check out the original branch")), + OPT_BOOL(0, "skip", &action_skip, + N_("skip current patch and continue")), + OPT_BOOL(0, "edit-todo", &edit_todo, + N_("edit the todo list during an interactive rebase")), + OPT_END() + }; + struct stat st; + int action_nr, in_progress; + + if (!stat(git_path("rebase-apply"), &st) && S_ISDIR(st.st_mode)) { + if (!access(git_path("rebase-apply/applying"), F_OK)) + die(_("It looks like git-am is in progress. Cannot rebase.")); + rebase_type = REBASE_AM; + state_basedir = "rebase-apply"; + in_progress = 1; + } else if (!stat(git_path("rebase-merge"), &st) && + S_ISDIR(st.st_mode)) { + if (!access(git_path("rebase-merge/interactive"), F_OK)) { + rebase_type = REBASE_INTERACTIVE; + interactive_rebase = 1; /* explicit */ + } else + rebase_type = REBASE_MERGE; + state_basedir = "rebase-merge"; + in_progress = 1; + } else + in_progress = 0; + + git_config(git_rebase_config, NULL); + gitmodules_config(); + + argc = parse_options(argc, argv, prefix, options, + builtin_rebase_usage, 0); + + action_nr = action_continue + action_skip + action_abort + edit_todo; + if ((action_nr != 1 && action_nr != 0) || argc > 2) + usage_with_options(builtin_rebase_usage, options); + + if (verbose && quiet) + die(_("--quiet and --verbose are incompatible")); + else if (verbose) + show_stat = 1; + else if (quiet) + show_stat = 0; + + if (preserve_merges && !interactive_rebase) + interactive_rebase = -1; /* implied */ + + if (strategy_opts) { + if (!strategy) + strategy = "recursive"; + /* validate strategy options */ + } + + if (strategy) + do_merge = 1; + + if (!strcmp(whitespace_opt, "fix") || + !strcmp(whitespace_opt, "strip") || + committer_date_is_author_date || + no_ff) + force_rebase = 1; + + if (exec_cmd && interactive_rebase != 1) + die(_("The --exec option must be used with the --interactive option")); + + if (action_nr) { + if (!in_progress) + die(_("No rebase in progress?")); + if (rebase_type == REBASE_INTERACTIVE) + ; /* GIT_REFLOG_ACTION = "rebase -i ($action)" */ + + if (action_continue) + return do_continue(); + else if (action_skip) + return do_skip(); + else if (action_abort) + return do_abort(); + else if (edit_todo) + return do_edit_todo(); + else + die("BUG: how do you get here?"); + } + + if (in_progress) + 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" + "\tgit rebase (--continue | --abort | --skip)\n" + "If that is not the case, please\n" + "\trm -rf \"%s/%s\"" + "and run me again. I am stopping in case you still have something\n" + "valuable there."), + state_basedir, + get_git_dir(), state_basedir); + + return do_rebase(argc, argv, options); +} diff --git a/commit.c b/commit.c index e8eb0ae..395a860 100644 --- a/commit.c +++ b/commit.c @@ -1135,7 +1135,7 @@ void free_commit_extra_headers(struct commit_extra_header *extra) } } -int commit_tree(const struct strbuf *msg, unsigned char *tree, +int commit_tree(const struct strbuf *msg, const unsigned char *tree, struct commit_list *parents, unsigned char *ret, const char *author, const char *sign_commit) { @@ -1238,7 +1238,7 @@ static const char commit_utf8_warn[] = "You may want to amend it after fixing the message, or set the config\n" "variable i18n.commitencoding to the encoding your project uses.\n"; -int commit_tree_extended(const struct strbuf *msg, unsigned char *tree, +int commit_tree_extended(const struct strbuf *msg, const unsigned char *tree, struct commit_list *parents, unsigned char *ret, const char *author, const char *sign_commit, struct commit_extra_header *extra) diff --git a/commit.h b/commit.h index 0f469e5..058886f 100644 --- a/commit.h +++ b/commit.h @@ -193,11 +193,11 @@ struct commit_extra_header { extern void append_merge_tag_headers(struct commit_list *parents, struct commit_extra_header ***tail); -extern int commit_tree(const struct strbuf *msg, unsigned char *tree, +extern int commit_tree(const struct strbuf *msg, const unsigned char *tree, struct commit_list *parents, unsigned char *ret, const char *author, const char *sign_commit); -extern int commit_tree_extended(const struct strbuf *msg, unsigned char *tree, +extern int commit_tree_extended(const struct strbuf *msg, const unsigned char *tree, struct commit_list *parents, unsigned char *ret, const char *author, const char *sign_commit, struct commit_extra_header *); diff --git a/contrib/examples/git-rebase.sh b/contrib/examples/git-rebase.sh new file mode 100755 index 0000000..38530e8 --- /dev/null +++ b/contrib/examples/git-rebase.sh @@ -0,0 +1,532 @@ +#!/bin/sh +# +# Copyright (c) 2005 Junio C Hamano. +# + +SUBDIRECTORY_OK=Yes +OPTIONS_KEEPDASHDASH= +OPTIONS_SPEC="\ +git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] [<upstream>] [<branch>] +git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] --root [<branch>] +git-rebase --continue | --abort | --skip | --edit-todo +-- + Available options are +v,verbose! display a diffstat of what changed upstream +q,quiet! be quiet. implies --no-stat +onto=! rebase onto given branch instead of upstream +p,preserve-merges! try to recreate merges instead of ignoring them +s,strategy=! use the given merge strategy +no-ff! cherry-pick all commits, even if unchanged +m,merge! use merging strategies to rebase +i,interactive! let the user edit the list of commits to rebase +x,exec=! add exec lines after each commit of the editable list +k,keep-empty preserve empty commits during rebase +f,force-rebase! force rebase even if branch is up to date +X,strategy-option=! pass the argument through to the merge strategy +stat! display a diffstat of what changed upstream +n,no-stat! do not show diffstat of what changed upstream +verify allow pre-rebase hook to run +rerere-autoupdate allow rerere to update index with resolved conflicts +root! rebase all reachable commits up to the root(s) +autosquash move commits that begin with squash!/fixup! under -i +committer-date-is-author-date! passed to 'git am' +ignore-date! passed to 'git am' +whitespace=! passed to 'git apply' +ignore-whitespace! passed to 'git apply' +C=! passed to 'git apply' + Actions: +continue! continue +abort! abort and check out the original branch +skip! skip current patch and continue +edit-todo! edit the todo list during an interactive rebase +" +. git-sh-setup +. git-sh-i18n +set_reflog_action rebase +require_work_tree_exists +cd_to_toplevel + +LF=' +' +ok_to_skip_pre_rebase= +unset onto +cmd= +strategy= +strategy_opts= +do_merge= +merge_dir="$GIT_DIR"/rebase-merge +apply_dir="$GIT_DIR"/rebase-apply +verbose= +diffstat= +test "$(git config --bool rebase.stat)" = true && diffstat=t +git_am_opt= +rebase_root= +force_rebase= +allow_rerere_autoupdate= +# Non-empty if a rebase was in progress when 'git rebase' was invoked +in_progress= +# One of {am, merge, interactive} +type= +# One of {"$GIT_DIR"/rebase-apply, "$GIT_DIR"/rebase-merge} +state_dir= +# One of {'', continue, skip, abort}, as parsed from command line +action= +preserve_merges= +autosquash= +keep_empty= +test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t + +read_basic_state () { + head_name=$(cat "$state_dir"/head-name) && + onto=$(cat "$state_dir"/onto) && + # 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. + if test -f "$state_dir"/orig-head + then + orig_head=$(cat "$state_dir"/orig-head) + else + orig_head=$(cat "$state_dir"/head) + fi && + GIT_QUIET=$(cat "$state_dir"/quiet) && + test -f "$state_dir"/verbose && verbose=t + test -f "$state_dir"/strategy && strategy="$(cat "$state_dir"/strategy)" + test -f "$state_dir"/strategy_opts && + strategy_opts="$(cat "$state_dir"/strategy_opts)" + test -f "$state_dir"/allow_rerere_autoupdate && + allow_rerere_autoupdate="$(cat "$state_dir"/allow_rerere_autoupdate)" +} + +output () { + case "$verbose" in + '') + output=$("$@" 2>&1 ) + status=$? + test $status != 0 && printf "%s\n" "$output" + return $status + ;; + *) + "$@" + ;; + esac +} + +move_to_original_branch () { + case "$head_name" in + refs/*) + message="rebase finished: $head_name onto $onto" + git update-ref -m "$message" \ + $head_name $(git rev-parse HEAD) $orig_head && + git symbolic-ref \ + -m "rebase finished: returning to $head_name" \ + HEAD $head_name || + die "$(gettext "Could not move back to $head_name")" + ;; + esac +} + +run_specific_rebase () { + if [ "$interactive_rebase" = implied ]; then + GIT_EDITOR=: + export GIT_EDITOR + autosquash= + fi + git_quiet=$GIT_QUIET + export GIT_PAGER + export action allow_rerere_autoupdate git_am_opt git_quiet head_name keep_empty + export onto orig_head rebase_root revisions + export state_dir verbose strategy strategy_opts + export autosquash cmd force_rebase preserve_merges squash_onto switch_to upstream + exec git-rebase--$type +} + +run_pre_rebase_hook () { + if test -z "$ok_to_skip_pre_rebase" && + test -x "$GIT_DIR/hooks/pre-rebase" + then + "$GIT_DIR/hooks/pre-rebase" ${1+"$@"} || + die "$(gettext "The pre-rebase hook refused to rebase.")" + fi +} + +test -f "$apply_dir"/applying && + die "$(gettext "It looks like git-am is in progress. Cannot rebase.")" + +if test -d "$apply_dir" +then + type=am + state_dir="$apply_dir" +elif test -d "$merge_dir" +then + if test -f "$merge_dir"/interactive + then + type=interactive + interactive_rebase=explicit + else + type=merge + fi + state_dir="$merge_dir" +fi +test -n "$type" && in_progress=t + +total_argc=$# +while test $# != 0 +do + case "$1" in + --no-verify) + ok_to_skip_pre_rebase=yes + ;; + --verify) + ok_to_skip_pre_rebase= + ;; + --continue|--skip|--abort|--edit-todo) + test $total_argc -eq 2 || usage + action=${1##--} + ;; + --onto) + test 2 -le "$#" || usage + onto="$2" + shift + ;; + -x) + test 2 -le "$#" || usage + cmd="${cmd}exec $2${LF}" + shift + ;; + -i) + interactive_rebase=explicit + ;; + -k) + keep_empty=yes + ;; + -p) + preserve_merges=t + test -z "$interactive_rebase" && interactive_rebase=implied + ;; + --autosquash) + autosquash=t + ;; + --no-autosquash) + autosquash= + ;; + -M|-m) + do_merge=t + ;; + -X) + shift + strategy_opts="$strategy_opts $(git rev-parse --sq-quote "--$1")" + do_merge=t + test -z "$strategy" && strategy=recursive + ;; + -s) + shift + strategy="$1" + do_merge=t + ;; + -n) + diffstat= + ;; + --stat) + diffstat=t + ;; + -v) + verbose=t + diffstat=t + GIT_QUIET= + ;; + -q) + GIT_QUIET=t + git_am_opt="$git_am_opt -q" + verbose= + diffstat= + ;; + --whitespace) + shift + git_am_opt="$git_am_opt --whitespace=$1" + case "$1" in + fix|strip) + force_rebase=t + ;; + esac + ;; + --ignore-whitespace) + git_am_opt="$git_am_opt $1" + ;; + --committer-date-is-author-date|--ignore-date) + git_am_opt="$git_am_opt $1" + force_rebase=t + ;; + -C) + shift + git_am_opt="$git_am_opt -C$1" + ;; + --root) + rebase_root=t + ;; + -f|--no-ff) + force_rebase=t + ;; + --rerere-autoupdate|--no-rerere-autoupdate) + allow_rerere_autoupdate="$1" + ;; + --) + shift + break + ;; + esac + shift +done +test $# -gt 2 && usage + +if test -n "$cmd" && + test "$interactive_rebase" != explicit +then + die "$(gettext "The --exec option must be used with the --interactive option")" +fi + +if test -n "$action" +then + test -z "$in_progress" && die "$(gettext "No rebase in progress?")" + # Only interactive rebase uses detailed reflog messages + if test "$type" = interactive && test "$GIT_REFLOG_ACTION" = rebase + then + GIT_REFLOG_ACTION="rebase -i ($action)" + export GIT_REFLOG_ACTION + fi +fi + +if test "$action" = "edit-todo" && test "$type" != "interactive" +then + die "$(gettext "The --edit-todo action can only be used during interactive rebase.")" +fi + +case "$action" in +continue) + # Sanity check + git rev-parse --verify HEAD >/dev/null || + die "$(gettext "Cannot read HEAD")" + git update-index --ignore-submodules --refresh && + git diff-files --quiet --ignore-submodules || { + echo "$(gettext "You must edit all merge conflicts and then +mark them as resolved using git add")" + exit 1 + } + read_basic_state + run_specific_rebase + ;; +skip) + output git reset --hard HEAD || exit $? + read_basic_state + run_specific_rebase + ;; +abort) + git rerere clear + read_basic_state + case "$head_name" in + refs/*) + git symbolic-ref -m "rebase: aborting" HEAD $head_name || + die "$(eval_gettext "Could not move back to \$head_name")" + ;; + esac + output git reset --hard $orig_head + rm -r "$state_dir" + exit + ;; +edit-todo) + run_specific_rebase + ;; +esac + +# Make sure no rebase is in progress +if test -n "$in_progress" +then + state_dir_base=${state_dir##*/} + cmd_live_rebase="git rebase (--continue | --abort | --skip)" + cmd_clear_stale_rebase="rm -fr \"$state_dir\"" + die " +$(eval_gettext 'It seems that there is already a $state_dir_base directory, and +I wonder if you are in the middle of another rebase. If that is the +case, please try + $cmd_live_rebase +If that is not the case, please + $cmd_clear_stale_rebase +and run me again. I am stopping in case you still have something +valuable there.')" +fi + +if test -n "$rebase_root" && test -z "$onto" +then + test -z "$interactive_rebase" && interactive_rebase=implied +fi + +if test -n "$interactive_rebase" +then + type=interactive + state_dir="$merge_dir" +elif test -n "$do_merge" +then + type=merge + state_dir="$merge_dir" +else + type=am + state_dir="$apply_dir" +fi + +if test -z "$rebase_root" +then + case "$#" in + 0) + if ! upstream_name=$(git rev-parse --symbolic-full-name \ + --verify -q @{upstream} 2>/dev/null) + then + . git-parse-remote + error_on_missing_default_upstream "rebase" "rebase" \ + "against" "git rebase <branch>" + fi + ;; + *) upstream_name="$1" + shift + ;; + esac + upstream=`git rev-parse --verify "${upstream_name}^0"` || + die "$(eval_gettext "invalid upstream \$upstream_name")" + upstream_arg="$upstream_name" +else + if test -z "$onto" + then + empty_tree=`git hash-object -t tree /dev/null` + onto=`git commit-tree $empty_tree </dev/null` + squash_onto="$onto" + fi + unset upstream_name + unset upstream + test $# -gt 1 && usage + upstream_arg=--root +fi + +# Make sure the branch to rebase onto is valid. +onto_name=${onto-"$upstream_name"} +case "$onto_name" in +*...*) + if left=${onto_name%...*} right=${onto_name#*...} && + onto=$(git merge-base --all ${left:-HEAD} ${right:-HEAD}) + then + case "$onto" in + ?*"$LF"?*) + die "$(eval_gettext "\$onto_name: there are more than one merge bases")" + ;; + '') + die "$(eval_gettext "\$onto_name: there is no merge base")" + ;; + esac + else + die "$(eval_gettext "\$onto_name: there is no merge base")" + fi + ;; +*) + onto=$(git rev-parse --verify "${onto_name}^0") || + die "$(eval_gettext "Does not point to a valid commit: \$onto_name")" + ;; +esac + +# If the branch to rebase is given, that is the branch we will rebase +# $branch_name -- branch being rebased, or HEAD (already detached) +# $orig_head -- commit object name of tip of the branch before rebasing +# $head_name -- refs/heads/<that-branch> or "detached HEAD" +switch_to= +case "$#" in +1) + # Is it "rebase other $branchname" or "rebase other $commit"? + branch_name="$1" + switch_to="$1" + + if git show-ref --verify --quiet -- "refs/heads/$1" && + orig_head=$(git rev-parse -q --verify "refs/heads/$1") + then + head_name="refs/heads/$1" + elif orig_head=$(git rev-parse -q --verify "$1") + then + head_name="detached HEAD" + else + die "$(eval_gettext "fatal: no such branch: \$branch_name")" + fi + ;; +0) + # Do not need to switch branches, we are already on it. + if branch_name=`git symbolic-ref -q HEAD` + then + head_name=$branch_name + branch_name=`expr "z$branch_name" : 'zrefs/heads/\(.*\)'` + else + head_name="detached HEAD" + branch_name=HEAD ;# detached + fi + orig_head=$(git rev-parse --verify "${branch_name}^0") || exit + ;; +*) + die "BUG: unexpected number of arguments left to parse" + ;; +esac + +require_clean_work_tree "rebase" "$(gettext "Please commit or stash them.")" + +# 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. +mb=$(git merge-base "$onto" "$orig_head") +if test "$type" != interactive && test "$upstream" = "$onto" && + test "$mb" = "$onto" && + # linear history? + ! (git rev-list --parents "$onto".."$orig_head" | sane_grep " .* ") > /dev/null +then + if test -z "$force_rebase" + then + # Lazily switch to the target branch if needed... + test -z "$switch_to" || git checkout "$switch_to" -- + say "$(eval_gettext "Current branch \$branch_name is up to date.")" + exit 0 + else + say "$(eval_gettext "Current branch \$branch_name is up to date, rebase forced.")" + fi +fi + +# If a hook exists, give it a chance to interrupt +run_pre_rebase_hook "$upstream_arg" "$@" + +if test -n "$diffstat" +then + if test -n "$verbose" + then + echo "$(eval_gettext "Changes from \$mb to \$onto:")" + fi + # We want color (if set), but no pager + GIT_PAGER='' git diff --stat --summary "$mb" "$onto" +fi + +test "$type" = interactive && run_specific_rebase + +# Detach HEAD and reset the tree +say "$(gettext "First, rewinding head to replay your work on top of it...")" +git checkout -q "$onto^0" || die "could not detach HEAD" +git update-ref ORIG_HEAD $orig_head + +# If the $onto is a proper descendant of the tip of the branch, then +# we just fast-forwarded. +if test "$mb" = "$orig_head" +then + say "$(eval_gettext "Fast-forwarded \$branch_name to \$onto_name.")" + move_to_original_branch + exit 0 +fi + +if test -n "$rebase_root" +then + revisions="$onto..$orig_head" +else + revisions="$upstream..$orig_head" +fi + +run_specific_rebase diff --git a/git-rebase.sh b/git-rebase.sh deleted file mode 100755 index 38530e8..0000000 --- a/git-rebase.sh +++ /dev/null @@ -1,532 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2005 Junio C Hamano. -# - -SUBDIRECTORY_OK=Yes -OPTIONS_KEEPDASHDASH= -OPTIONS_SPEC="\ -git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] [<upstream>] [<branch>] -git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] --root [<branch>] -git-rebase --continue | --abort | --skip | --edit-todo --- - Available options are -v,verbose! display a diffstat of what changed upstream -q,quiet! be quiet. implies --no-stat -onto=! rebase onto given branch instead of upstream -p,preserve-merges! try to recreate merges instead of ignoring them -s,strategy=! use the given merge strategy -no-ff! cherry-pick all commits, even if unchanged -m,merge! use merging strategies to rebase -i,interactive! let the user edit the list of commits to rebase -x,exec=! add exec lines after each commit of the editable list -k,keep-empty preserve empty commits during rebase -f,force-rebase! force rebase even if branch is up to date -X,strategy-option=! pass the argument through to the merge strategy -stat! display a diffstat of what changed upstream -n,no-stat! do not show diffstat of what changed upstream -verify allow pre-rebase hook to run -rerere-autoupdate allow rerere to update index with resolved conflicts -root! rebase all reachable commits up to the root(s) -autosquash move commits that begin with squash!/fixup! under -i -committer-date-is-author-date! passed to 'git am' -ignore-date! passed to 'git am' -whitespace=! passed to 'git apply' -ignore-whitespace! passed to 'git apply' -C=! passed to 'git apply' - Actions: -continue! continue -abort! abort and check out the original branch -skip! skip current patch and continue -edit-todo! edit the todo list during an interactive rebase -" -. git-sh-setup -. git-sh-i18n -set_reflog_action rebase -require_work_tree_exists -cd_to_toplevel - -LF=' -' -ok_to_skip_pre_rebase= -unset onto -cmd= -strategy= -strategy_opts= -do_merge= -merge_dir="$GIT_DIR"/rebase-merge -apply_dir="$GIT_DIR"/rebase-apply -verbose= -diffstat= -test "$(git config --bool rebase.stat)" = true && diffstat=t -git_am_opt= -rebase_root= -force_rebase= -allow_rerere_autoupdate= -# Non-empty if a rebase was in progress when 'git rebase' was invoked -in_progress= -# One of {am, merge, interactive} -type= -# One of {"$GIT_DIR"/rebase-apply, "$GIT_DIR"/rebase-merge} -state_dir= -# One of {'', continue, skip, abort}, as parsed from command line -action= -preserve_merges= -autosquash= -keep_empty= -test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t - -read_basic_state () { - head_name=$(cat "$state_dir"/head-name) && - onto=$(cat "$state_dir"/onto) && - # 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. - if test -f "$state_dir"/orig-head - then - orig_head=$(cat "$state_dir"/orig-head) - else - orig_head=$(cat "$state_dir"/head) - fi && - GIT_QUIET=$(cat "$state_dir"/quiet) && - test -f "$state_dir"/verbose && verbose=t - test -f "$state_dir"/strategy && strategy="$(cat "$state_dir"/strategy)" - test -f "$state_dir"/strategy_opts && - strategy_opts="$(cat "$state_dir"/strategy_opts)" - test -f "$state_dir"/allow_rerere_autoupdate && - allow_rerere_autoupdate="$(cat "$state_dir"/allow_rerere_autoupdate)" -} - -output () { - case "$verbose" in - '') - output=$("$@" 2>&1 ) - status=$? - test $status != 0 && printf "%s\n" "$output" - return $status - ;; - *) - "$@" - ;; - esac -} - -move_to_original_branch () { - case "$head_name" in - refs/*) - message="rebase finished: $head_name onto $onto" - git update-ref -m "$message" \ - $head_name $(git rev-parse HEAD) $orig_head && - git symbolic-ref \ - -m "rebase finished: returning to $head_name" \ - HEAD $head_name || - die "$(gettext "Could not move back to $head_name")" - ;; - esac -} - -run_specific_rebase () { - if [ "$interactive_rebase" = implied ]; then - GIT_EDITOR=: - export GIT_EDITOR - autosquash= - fi - git_quiet=$GIT_QUIET - export GIT_PAGER - export action allow_rerere_autoupdate git_am_opt git_quiet head_name keep_empty - export onto orig_head rebase_root revisions - export state_dir verbose strategy strategy_opts - export autosquash cmd force_rebase preserve_merges squash_onto switch_to upstream - exec git-rebase--$type -} - -run_pre_rebase_hook () { - if test -z "$ok_to_skip_pre_rebase" && - test -x "$GIT_DIR/hooks/pre-rebase" - then - "$GIT_DIR/hooks/pre-rebase" ${1+"$@"} || - die "$(gettext "The pre-rebase hook refused to rebase.")" - fi -} - -test -f "$apply_dir"/applying && - die "$(gettext "It looks like git-am is in progress. Cannot rebase.")" - -if test -d "$apply_dir" -then - type=am - state_dir="$apply_dir" -elif test -d "$merge_dir" -then - if test -f "$merge_dir"/interactive - then - type=interactive - interactive_rebase=explicit - else - type=merge - fi - state_dir="$merge_dir" -fi -test -n "$type" && in_progress=t - -total_argc=$# -while test $# != 0 -do - case "$1" in - --no-verify) - ok_to_skip_pre_rebase=yes - ;; - --verify) - ok_to_skip_pre_rebase= - ;; - --continue|--skip|--abort|--edit-todo) - test $total_argc -eq 2 || usage - action=${1##--} - ;; - --onto) - test 2 -le "$#" || usage - onto="$2" - shift - ;; - -x) - test 2 -le "$#" || usage - cmd="${cmd}exec $2${LF}" - shift - ;; - -i) - interactive_rebase=explicit - ;; - -k) - keep_empty=yes - ;; - -p) - preserve_merges=t - test -z "$interactive_rebase" && interactive_rebase=implied - ;; - --autosquash) - autosquash=t - ;; - --no-autosquash) - autosquash= - ;; - -M|-m) - do_merge=t - ;; - -X) - shift - strategy_opts="$strategy_opts $(git rev-parse --sq-quote "--$1")" - do_merge=t - test -z "$strategy" && strategy=recursive - ;; - -s) - shift - strategy="$1" - do_merge=t - ;; - -n) - diffstat= - ;; - --stat) - diffstat=t - ;; - -v) - verbose=t - diffstat=t - GIT_QUIET= - ;; - -q) - GIT_QUIET=t - git_am_opt="$git_am_opt -q" - verbose= - diffstat= - ;; - --whitespace) - shift - git_am_opt="$git_am_opt --whitespace=$1" - case "$1" in - fix|strip) - force_rebase=t - ;; - esac - ;; - --ignore-whitespace) - git_am_opt="$git_am_opt $1" - ;; - --committer-date-is-author-date|--ignore-date) - git_am_opt="$git_am_opt $1" - force_rebase=t - ;; - -C) - shift - git_am_opt="$git_am_opt -C$1" - ;; - --root) - rebase_root=t - ;; - -f|--no-ff) - force_rebase=t - ;; - --rerere-autoupdate|--no-rerere-autoupdate) - allow_rerere_autoupdate="$1" - ;; - --) - shift - break - ;; - esac - shift -done -test $# -gt 2 && usage - -if test -n "$cmd" && - test "$interactive_rebase" != explicit -then - die "$(gettext "The --exec option must be used with the --interactive option")" -fi - -if test -n "$action" -then - test -z "$in_progress" && die "$(gettext "No rebase in progress?")" - # Only interactive rebase uses detailed reflog messages - if test "$type" = interactive && test "$GIT_REFLOG_ACTION" = rebase - then - GIT_REFLOG_ACTION="rebase -i ($action)" - export GIT_REFLOG_ACTION - fi -fi - -if test "$action" = "edit-todo" && test "$type" != "interactive" -then - die "$(gettext "The --edit-todo action can only be used during interactive rebase.")" -fi - -case "$action" in -continue) - # Sanity check - git rev-parse --verify HEAD >/dev/null || - die "$(gettext "Cannot read HEAD")" - git update-index --ignore-submodules --refresh && - git diff-files --quiet --ignore-submodules || { - echo "$(gettext "You must edit all merge conflicts and then -mark them as resolved using git add")" - exit 1 - } - read_basic_state - run_specific_rebase - ;; -skip) - output git reset --hard HEAD || exit $? - read_basic_state - run_specific_rebase - ;; -abort) - git rerere clear - read_basic_state - case "$head_name" in - refs/*) - git symbolic-ref -m "rebase: aborting" HEAD $head_name || - die "$(eval_gettext "Could not move back to \$head_name")" - ;; - esac - output git reset --hard $orig_head - rm -r "$state_dir" - exit - ;; -edit-todo) - run_specific_rebase - ;; -esac - -# Make sure no rebase is in progress -if test -n "$in_progress" -then - state_dir_base=${state_dir##*/} - cmd_live_rebase="git rebase (--continue | --abort | --skip)" - cmd_clear_stale_rebase="rm -fr \"$state_dir\"" - die " -$(eval_gettext 'It seems that there is already a $state_dir_base directory, and -I wonder if you are in the middle of another rebase. If that is the -case, please try - $cmd_live_rebase -If that is not the case, please - $cmd_clear_stale_rebase -and run me again. I am stopping in case you still have something -valuable there.')" -fi - -if test -n "$rebase_root" && test -z "$onto" -then - test -z "$interactive_rebase" && interactive_rebase=implied -fi - -if test -n "$interactive_rebase" -then - type=interactive - state_dir="$merge_dir" -elif test -n "$do_merge" -then - type=merge - state_dir="$merge_dir" -else - type=am - state_dir="$apply_dir" -fi - -if test -z "$rebase_root" -then - case "$#" in - 0) - if ! upstream_name=$(git rev-parse --symbolic-full-name \ - --verify -q @{upstream} 2>/dev/null) - then - . git-parse-remote - error_on_missing_default_upstream "rebase" "rebase" \ - "against" "git rebase <branch>" - fi - ;; - *) upstream_name="$1" - shift - ;; - esac - upstream=`git rev-parse --verify "${upstream_name}^0"` || - die "$(eval_gettext "invalid upstream \$upstream_name")" - upstream_arg="$upstream_name" -else - if test -z "$onto" - then - empty_tree=`git hash-object -t tree /dev/null` - onto=`git commit-tree $empty_tree </dev/null` - squash_onto="$onto" - fi - unset upstream_name - unset upstream - test $# -gt 1 && usage - upstream_arg=--root -fi - -# Make sure the branch to rebase onto is valid. -onto_name=${onto-"$upstream_name"} -case "$onto_name" in -*...*) - if left=${onto_name%...*} right=${onto_name#*...} && - onto=$(git merge-base --all ${left:-HEAD} ${right:-HEAD}) - then - case "$onto" in - ?*"$LF"?*) - die "$(eval_gettext "\$onto_name: there are more than one merge bases")" - ;; - '') - die "$(eval_gettext "\$onto_name: there is no merge base")" - ;; - esac - else - die "$(eval_gettext "\$onto_name: there is no merge base")" - fi - ;; -*) - onto=$(git rev-parse --verify "${onto_name}^0") || - die "$(eval_gettext "Does not point to a valid commit: \$onto_name")" - ;; -esac - -# If the branch to rebase is given, that is the branch we will rebase -# $branch_name -- branch being rebased, or HEAD (already detached) -# $orig_head -- commit object name of tip of the branch before rebasing -# $head_name -- refs/heads/<that-branch> or "detached HEAD" -switch_to= -case "$#" in -1) - # Is it "rebase other $branchname" or "rebase other $commit"? - branch_name="$1" - switch_to="$1" - - if git show-ref --verify --quiet -- "refs/heads/$1" && - orig_head=$(git rev-parse -q --verify "refs/heads/$1") - then - head_name="refs/heads/$1" - elif orig_head=$(git rev-parse -q --verify "$1") - then - head_name="detached HEAD" - else - die "$(eval_gettext "fatal: no such branch: \$branch_name")" - fi - ;; -0) - # Do not need to switch branches, we are already on it. - if branch_name=`git symbolic-ref -q HEAD` - then - head_name=$branch_name - branch_name=`expr "z$branch_name" : 'zrefs/heads/\(.*\)'` - else - head_name="detached HEAD" - branch_name=HEAD ;# detached - fi - orig_head=$(git rev-parse --verify "${branch_name}^0") || exit - ;; -*) - die "BUG: unexpected number of arguments left to parse" - ;; -esac - -require_clean_work_tree "rebase" "$(gettext "Please commit or stash them.")" - -# 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. -mb=$(git merge-base "$onto" "$orig_head") -if test "$type" != interactive && test "$upstream" = "$onto" && - test "$mb" = "$onto" && - # linear history? - ! (git rev-list --parents "$onto".."$orig_head" | sane_grep " .* ") > /dev/null -then - if test -z "$force_rebase" - then - # Lazily switch to the target branch if needed... - test -z "$switch_to" || git checkout "$switch_to" -- - say "$(eval_gettext "Current branch \$branch_name is up to date.")" - exit 0 - else - say "$(eval_gettext "Current branch \$branch_name is up to date, rebase forced.")" - fi -fi - -# If a hook exists, give it a chance to interrupt -run_pre_rebase_hook "$upstream_arg" "$@" - -if test -n "$diffstat" -then - if test -n "$verbose" - then - echo "$(eval_gettext "Changes from \$mb to \$onto:")" - fi - # We want color (if set), but no pager - GIT_PAGER='' git diff --stat --summary "$mb" "$onto" -fi - -test "$type" = interactive && run_specific_rebase - -# Detach HEAD and reset the tree -say "$(gettext "First, rewinding head to replay your work on top of it...")" -git checkout -q "$onto^0" || die "could not detach HEAD" -git update-ref ORIG_HEAD $orig_head - -# If the $onto is a proper descendant of the tip of the branch, then -# we just fast-forwarded. -if test "$mb" = "$orig_head" -then - say "$(eval_gettext "Fast-forwarded \$branch_name to \$onto_name.")" - move_to_original_branch - exit 0 -fi - -if test -n "$rebase_root" -then - revisions="$onto..$orig_head" -else - revisions="$upstream..$orig_head" -fi - -run_specific_rebase diff --git a/git.c b/git.c index ed66c66..a4cb4a8 100644 --- a/git.c +++ b/git.c @@ -379,6 +379,7 @@ static void handle_internal_command(int argc, const char **argv) { "prune-packed", cmd_prune_packed, RUN_SETUP }, { "push", cmd_push, RUN_SETUP }, { "read-tree", cmd_read_tree, RUN_SETUP }, + { "rebase", cmd_rebase, RUN_SETUP | NEED_WORK_TREE }, { "receive-pack", cmd_receive_pack }, { "reflog", cmd_reflog, RUN_SETUP }, { "remote", cmd_remote, RUN_SETUP }, -- 2.3.0.rc1.137.g477eb31 -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html