From 55071ea248ef8040e4b29575376273e4dd061683 Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 7 Aug 2018 01:16:09 +0545 Subject: [PATCH 1/3] rebase: start implementing it as a builtin This commit imitates the strategy that was used to convert the difftool to a builtin. We start by renaming the shell script `git-rebase.sh` to `git-legacy-rebase.sh` and introduce a `builtin/rebase.c` that simply executes the shell script version, unless the config setting `rebase.useBuiltin` is set to `true`. The motivation behind this is to rewrite all the functionality of the shell script version in the aforementioned `rebase.c`, one by one and be able to conveniently test new features by configuring `rebase.useBuiltin`. In the original difftool conversion, if sane_execvp() that attempts to run the legacy scripted version returned with non-negative status, the command silently exited without doing anything with success, but sane_execvp() should not return with non-negative status in the first place, so we use die() to notice such an abnormal case. We intentionally avoid reading the config directly to avoid messing up the GIT_* environment variables when we need to fall back to exec()ing the shell script. The test of builtin rebase can be done by `git -c rebase.useBuiltin=true rebase ...` Signed-off-by: Pratik Karki Signed-off-by: Junio C Hamano --- .gitignore | 1 + Makefile | 3 +- builtin.h | 1 + builtin/rebase.c | 58 +++++++++++++++++++++++++++ git-rebase.sh => git-legacy-rebase.sh | 0 git.c | 6 +++ 6 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 builtin/rebase.c rename git-rebase.sh => git-legacy-rebase.sh (100%) diff --git a/.gitignore b/.gitignore index 3284a1e9b1..ec23959014 100644 --- a/.gitignore +++ b/.gitignore @@ -78,6 +78,7 @@ /git-init-db /git-interpret-trailers /git-instaweb +/git-legacy-rebase /git-log /git-ls-files /git-ls-remote diff --git a/Makefile b/Makefile index bc4fc8eeab..c311532791 100644 --- a/Makefile +++ b/Makefile @@ -609,7 +609,7 @@ SCRIPT_SH += git-merge-one-file.sh SCRIPT_SH += git-merge-resolve.sh SCRIPT_SH += git-mergetool.sh SCRIPT_SH += git-quiltimport.sh -SCRIPT_SH += git-rebase.sh +SCRIPT_SH += git-legacy-rebase.sh SCRIPT_SH += git-remote-testgit.sh SCRIPT_SH += git-request-pull.sh SCRIPT_SH += git-stash.sh @@ -1063,6 +1063,7 @@ BUILTIN_OBJS += builtin/prune.o BUILTIN_OBJS += builtin/pull.o BUILTIN_OBJS += builtin/push.o BUILTIN_OBJS += builtin/read-tree.o +BUILTIN_OBJS += builtin/rebase.o BUILTIN_OBJS += builtin/rebase--helper.o BUILTIN_OBJS += builtin/receive-pack.o BUILTIN_OBJS += builtin/reflog.o diff --git a/builtin.h b/builtin.h index 0362f1ce25..44651a447f 100644 --- a/builtin.h +++ b/builtin.h @@ -202,6 +202,7 @@ extern int cmd_prune_packed(int argc, const char **argv, const char *prefix); extern int cmd_pull(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_rebase__helper(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); diff --git a/builtin/rebase.c b/builtin/rebase.c new file mode 100644 index 0000000000..c44addb2a4 --- /dev/null +++ b/builtin/rebase.c @@ -0,0 +1,58 @@ +/* + * "git rebase" builtin command + * + * Copyright (c) 2018 Pratik Karki + */ + +#include "builtin.h" +#include "run-command.h" +#include "exec-cmd.h" +#include "argv-array.h" +#include "dir.h" + +static int use_builtin_rebase(void) +{ + struct child_process cp = CHILD_PROCESS_INIT; + struct strbuf out = STRBUF_INIT; + int ret; + + argv_array_pushl(&cp.args, + "config", "--bool", "rebase.usebuiltin", NULL); + cp.git_cmd = 1; + if (capture_command(&cp, &out, 6)) { + strbuf_release(&out); + return 0; + } + + strbuf_trim(&out); + ret = !strcmp("true", out.buf); + strbuf_release(&out); + return ret; +} + +int cmd_rebase(int argc, const char **argv, const char *prefix) +{ + /* + * NEEDSWORK: Once the builtin rebase has been tested enough + * and git-legacy-rebase.sh is retired to contrib/, this preamble + * can be removed. + */ + + if (!use_builtin_rebase()) { + const char *path = mkpath("%s/git-legacy-rebase", + git_exec_path()); + + if (sane_execvp(path, (char **)argv) < 0) + die_errno(_("could not exec %s"), path); + else + BUG("sane_execvp() returned???"); + } + + if (argc != 2) + die(_("Usage: %s "), argv[0]); + prefix = setup_git_directory(); + trace_repo_setup(prefix); + setup_work_tree(); + + die("TODO"); +} diff --git a/git-rebase.sh b/git-legacy-rebase.sh similarity index 100% rename from git-rebase.sh rename to git-legacy-rebase.sh diff --git a/git.c b/git.c index fc7d15d549..2c6b188c77 100644 --- a/git.c +++ b/git.c @@ -521,6 +521,12 @@ static struct cmd_struct commands[] = { { "pull", cmd_pull, RUN_SETUP | NEED_WORK_TREE }, { "push", cmd_push, RUN_SETUP }, { "read-tree", cmd_read_tree, RUN_SETUP | SUPPORT_SUPER_PREFIX}, + /* + * NEEDSWORK: Until the rebase is independent and needs no redirection + * to rebase shell script this is kept as is, then should be changed to + * RUN_SETUP | NEED_WORK_TREE + */ + { "rebase", cmd_rebase }, { "rebase--helper", cmd_rebase__helper, RUN_SETUP | NEED_WORK_TREE }, { "receive-pack", cmd_receive_pack }, { "reflog", cmd_reflog, RUN_SETUP }, From c7b64aa0f3a83ed574d9d374bf2639c3c091120c Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 7 Aug 2018 01:16:10 +0545 Subject: [PATCH 2/3] rebase: refactor common shell functions into their own file The functions present in `git-legacy-rebase.sh` are used by the rebase backends as they are implemented as shell script functions in the `git-rebase--` files. To make the `builtin/rebase.c` work, we have to provide support via a Unix shell script snippet that uses these functions and so, we want to use the rebase backends *directly* from the builtin rebase without going through `git-legacy-rebase.sh`. This commit extracts the functions to a separate file, `git-rebase--common`, that will be read by `git-legacy-rebase.sh` and by the shell script snippets which will be used extensively in the following commits. Signed-off-by: Pratik Karki Signed-off-by: Junio C Hamano --- .gitignore | 1 + Makefile | 1 + git-legacy-rebase.sh | 69 ++----------------------------------------- git-rebase--common.sh | 68 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 67 deletions(-) create mode 100644 git-rebase--common.sh diff --git a/.gitignore b/.gitignore index ec23959014..824141cba1 100644 --- a/.gitignore +++ b/.gitignore @@ -117,6 +117,7 @@ /git-read-tree /git-rebase /git-rebase--am +/git-rebase--common /git-rebase--helper /git-rebase--interactive /git-rebase--merge diff --git a/Makefile b/Makefile index c311532791..c210681a1d 100644 --- a/Makefile +++ b/Makefile @@ -619,6 +619,7 @@ SCRIPT_SH += git-web--browse.sh SCRIPT_LIB += git-mergetool--lib SCRIPT_LIB += git-parse-remote SCRIPT_LIB += git-rebase--am +SCRIPT_LIB += git-rebase--common SCRIPT_LIB += git-rebase--interactive SCRIPT_LIB += git-rebase--preserve-merges SCRIPT_LIB += git-rebase--merge diff --git a/git-legacy-rebase.sh b/git-legacy-rebase.sh index 7973447645..af2cdfef03 100755 --- a/git-legacy-rebase.sh +++ b/git-legacy-rebase.sh @@ -57,12 +57,7 @@ cd_to_toplevel LF=' ' ok_to_skip_pre_rebase= -resolvemsg=" -$(gettext 'Resolve all conflicts manually, mark them as resolved with -"git add/rm ", then run "git rebase --continue". -You can instead skip this commit: run "git rebase --skip". -To abort and get back to the state before "git rebase", run "git rebase --abort".') -" + squash_onto= unset onto unset restrict_revision @@ -102,6 +97,7 @@ case "$(git config --bool commit.gpgsign)" in true) gpg_sign_opt=-S ;; *) gpg_sign_opt= ;; esac +. git-rebase--common read_basic_state () { test -f "$state_dir/head-name" && @@ -132,67 +128,6 @@ read_basic_state () { } } -write_basic_state () { - echo "$head_name" > "$state_dir"/head-name && - echo "$onto" > "$state_dir"/onto && - echo "$orig_head" > "$state_dir"/orig-head && - echo "$GIT_QUIET" > "$state_dir"/quiet && - test t = "$verbose" && : > "$state_dir"/verbose - test -n "$strategy" && echo "$strategy" > "$state_dir"/strategy - test -n "$strategy_opts" && echo "$strategy_opts" > \ - "$state_dir"/strategy_opts - test -n "$allow_rerere_autoupdate" && echo "$allow_rerere_autoupdate" > \ - "$state_dir"/allow_rerere_autoupdate - test -n "$gpg_sign_opt" && echo "$gpg_sign_opt" > "$state_dir"/gpg_sign_opt - test -n "$signoff" && echo "$signoff" >"$state_dir"/signoff -} - -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 "$(eval_gettext "Could not move back to \$head_name")" - ;; - esac -} - -apply_autostash () { - if test -f "$state_dir/autostash" - then - stash_sha1=$(cat "$state_dir/autostash") - if git stash apply $stash_sha1 >/dev/null 2>&1 - then - echo "$(gettext 'Applied autostash.')" >&2 - else - git stash store -m "autostash" -q $stash_sha1 || - die "$(eval_gettext "Cannot store \$stash_sha1")" - gettext 'Applying autostash resulted in conflicts. -Your changes are safe in the stash. -You can run "git stash pop" or "git stash drop" at any time. -' >&2 - fi - fi -} - finish_rebase () { rm -f "$(git rev-parse --git-path REBASE_HEAD)" apply_autostash && diff --git a/git-rebase--common.sh b/git-rebase--common.sh new file mode 100644 index 0000000000..7e39d22871 --- /dev/null +++ b/git-rebase--common.sh @@ -0,0 +1,68 @@ + +resolvemsg=" +$(gettext 'Resolve all conflicts manually, mark them as resolved with +"git add/rm ", then run "git rebase --continue". +You can instead skip this commit: run "git rebase --skip". +To abort and get back to the state before "git rebase", run "git rebase --abort".') +" + +write_basic_state () { + echo "$head_name" > "$state_dir"/head-name && + echo "$onto" > "$state_dir"/onto && + echo "$orig_head" > "$state_dir"/orig-head && + echo "$GIT_QUIET" > "$state_dir"/quiet && + test t = "$verbose" && : > "$state_dir"/verbose + test -n "$strategy" && echo "$strategy" > "$state_dir"/strategy + test -n "$strategy_opts" && echo "$strategy_opts" > \ + "$state_dir"/strategy_opts + test -n "$allow_rerere_autoupdate" && echo "$allow_rerere_autoupdate" > \ + "$state_dir"/allow_rerere_autoupdate + test -n "$gpg_sign_opt" && echo "$gpg_sign_opt" > "$state_dir"/gpg_sign_opt + test -n "$signoff" && echo "$signoff" >"$state_dir"/signoff +} + +apply_autostash () { + if test -f "$state_dir/autostash" + then + stash_sha1=$(cat "$state_dir/autostash") + if git stash apply $stash_sha1 >/dev/null 2>&1 + then + echo "$(gettext 'Applied autostash.')" >&2 + else + git stash store -m "autostash" -q $stash_sha1 || + die "$(eval_gettext "Cannot store \$stash_sha1")" + gettext 'Applying autostash resulted in conflicts. +Your changes are safe in the stash. +You can run "git stash pop" or "git stash drop" at any time. +' >&2 + fi + fi +} + +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 "$(eval_gettext "Could not move back to \$head_name")" + ;; + esac +} + +output () { + case "$verbose" in + '') + output=$("$@" 2>&1 ) + status=$? + test $status != 0 && printf "%s\n" "$output" + return $status + ;; + *) + "$@" + ;; + esac +} From ac7f467fef8b836084afdce5eded047c79a6858d Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 7 Aug 2018 01:16:11 +0545 Subject: [PATCH 3/3] builtin/rebase: support running "git rebase " This patch gives life to the skeleton added in the previous patches: With this change, we can perform a elementary rebase (without any options). It can be tested thusly by: git -c rebase.usebuiltin=true rebase HEAD~2 The rebase backends (i.e. the shell script functions defined in `git-rebase--`) are still at work here and the "builtin rebase"'s purpose is simply to parse the options and set everything up so that those rebase backends can do their work. Note: We take an alternative approach here which is *not* to set the environment variables via `run_command_v_opt_cd_env()` because those variables would then be visible by processes spawned from the rebase backends. Instead, we work hard to set them in the shell script snippet. On Windows, some of the tests fail merely due to core.fileMode not being heeded that's why the core.*config variables is parsed here. The `reset_head()` function is currently only used to detach the HEAD to onto by way of starting the rebase, but it offers additional functionality that subsequent patches will need like moving to the original branch (possibly updating it) and also to do the equivalent of `git reset --hard`. The next commits will handle and support all the wonderful rebase options. Signed-off-by: Pratik Karki Signed-off-by: Junio C Hamano --- builtin/rebase.c | 365 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 364 insertions(+), 1 deletion(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index c44addb2a4..e695d8a430 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -9,6 +9,24 @@ #include "exec-cmd.h" #include "argv-array.h" #include "dir.h" +#include "packfile.h" +#include "refs.h" +#include "quote.h" +#include "config.h" +#include "cache-tree.h" +#include "unpack-trees.h" +#include "lockfile.h" + +static GIT_PATH_FUNC(apply_dir, "rebase-apply") +static GIT_PATH_FUNC(merge_dir, "rebase-merge") + +enum rebase_type { + REBASE_UNSPECIFIED = -1, + REBASE_AM, + REBASE_MERGE, + REBASE_INTERACTIVE, + REBASE_PRESERVE_MERGES +}; static int use_builtin_rebase(void) { @@ -30,8 +48,260 @@ static int use_builtin_rebase(void) return ret; } +static int apply_autostash(void) +{ + warning("TODO"); + return 0; +} + +struct rebase_options { + enum rebase_type type; + const char *state_dir; + struct commit *upstream; + const char *upstream_name; + char *head_name; + struct object_id orig_head; + struct commit *onto; + const char *onto_name; + const char *revisions; + int root; + struct commit *restrict_revision; + int dont_finish_rebase; +}; + +/* Returns the filename prefixed by the state_dir */ +static const char *state_dir_path(const char *filename, struct rebase_options *opts) +{ + static struct strbuf path = STRBUF_INIT; + static size_t prefix_len; + + if (!prefix_len) { + strbuf_addf(&path, "%s/", opts->state_dir); + prefix_len = path.len; + } + + strbuf_setlen(&path, prefix_len); + strbuf_addstr(&path, filename); + return path.buf; +} + +static int finish_rebase(struct rebase_options *opts) +{ + struct strbuf dir = STRBUF_INIT; + const char *argv_gc_auto[] = { "gc", "--auto", NULL }; + + delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF); + apply_autostash(); + close_all_packs(the_repository->objects); + /* + * We ignore errors in 'gc --auto', since the + * user should see them. + */ + run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); + strbuf_addstr(&dir, opts->state_dir); + remove_dir_recursively(&dir, 0); + strbuf_release(&dir); + + return 0; +} + +static struct commit *peel_committish(const char *name) +{ + struct object *obj; + struct object_id oid; + + if (get_oid(name, &oid)) + return NULL; + obj = parse_object(the_repository, &oid); + return (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT); +} + +static void add_var(struct strbuf *buf, const char *name, const char *value) +{ + if (!value) + strbuf_addf(buf, "unset %s; ", name); + else { + strbuf_addf(buf, "%s=", name); + sq_quote_buf(buf, value); + strbuf_addstr(buf, "; "); + } +} + +static int run_specific_rebase(struct rebase_options *opts) +{ + const char *argv[] = { NULL, NULL }; + struct strbuf script_snippet = STRBUF_INIT; + int status; + const char *backend, *backend_func; + + add_var(&script_snippet, "GIT_DIR", absolute_path(get_git_dir())); + add_var(&script_snippet, "state_dir", opts->state_dir); + + add_var(&script_snippet, "upstream_name", opts->upstream_name); + add_var(&script_snippet, "upstream", + oid_to_hex(&opts->upstream->object.oid)); + add_var(&script_snippet, "head_name", opts->head_name); + 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_name", opts->onto_name); + add_var(&script_snippet, "revisions", opts->revisions); + add_var(&script_snippet, "restrict_revision", opts->restrict_revision ? + oid_to_hex(&opts->restrict_revision->object.oid) : NULL); + + switch (opts->type) { + case REBASE_AM: + backend = "git-rebase--am"; + backend_func = "git_rebase__am"; + break; + case REBASE_INTERACTIVE: + backend = "git-rebase--interactive"; + backend_func = "git_rebase__interactive"; + break; + case REBASE_MERGE: + backend = "git-rebase--merge"; + backend_func = "git_rebase__merge"; + break; + case REBASE_PRESERVE_MERGES: + backend = "git-rebase--preserve-merges"; + backend_func = "git_rebase__preserve_merges"; + break; + default: + BUG("Unhandled rebase type %d", opts->type); + break; + } + + strbuf_addf(&script_snippet, + ". git-sh-setup && . git-rebase--common &&" + " . %s && %s", backend, backend_func); + argv[0] = script_snippet.buf; + + status = run_command_v_opt(argv, RUN_USING_SHELL); + if (opts->dont_finish_rebase) + ; /* do nothing */ + else if (status == 0) { + if (!file_exists(state_dir_path("stopped-sha", opts))) + finish_rebase(opts); + } else if (status == 2) { + struct strbuf dir = STRBUF_INIT; + + apply_autostash(); + strbuf_addstr(&dir, opts->state_dir); + remove_dir_recursively(&dir, 0); + strbuf_release(&dir); + die("Nothing to do"); + } + + strbuf_release(&script_snippet); + + return status ? -1 : 0; +} + +#define GIT_REFLOG_ACTION_ENVIRONMENT "GIT_REFLOG_ACTION" + +static int reset_head(struct object_id *oid, const char *action, + const char *switch_to_branch, int detach_head) +{ + struct object_id head_oid; + struct tree_desc desc; + struct lock_file lock = LOCK_INIT; + struct unpack_trees_options unpack_tree_opts; + struct tree *tree; + const char *reflog_action; + struct strbuf msg = STRBUF_INIT; + size_t prefix_len; + struct object_id *orig = NULL, oid_orig, + *old_orig = NULL, oid_old_orig; + int ret = 0; + + if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) + return -1; + + if (!oid) { + if (get_oid("HEAD", &head_oid)) { + rollback_lock_file(&lock); + return error(_("could not determine HEAD revision")); + } + oid = &head_oid; + } + + memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts)); + setup_unpack_trees_porcelain(&unpack_tree_opts, action); + unpack_tree_opts.head_idx = 1; + unpack_tree_opts.src_index = the_repository->index; + unpack_tree_opts.dst_index = the_repository->index; + unpack_tree_opts.fn = oneway_merge; + unpack_tree_opts.update = 1; + unpack_tree_opts.merge = 1; + if (!detach_head) + unpack_tree_opts.reset = 1; + + if (read_index_unmerged(the_repository->index) < 0) { + rollback_lock_file(&lock); + return error(_("could not read index")); + } + + if (!fill_tree_descriptor(&desc, oid)) { + error(_("failed to find tree of %s"), oid_to_hex(oid)); + rollback_lock_file(&lock); + free((void *)desc.buffer); + return -1; + } + + if (unpack_trees(1, &desc, &unpack_tree_opts)) { + rollback_lock_file(&lock); + free((void *)desc.buffer); + return -1; + } + + tree = parse_tree_indirect(oid); + prime_cache_tree(the_repository->index, tree); + + if (write_locked_index(the_repository->index, &lock, COMMIT_LOCK) < 0) + ret = error(_("could not write index")); + free((void *)desc.buffer); + + if (ret) + return ret; + + reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT); + strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : "rebase"); + prefix_len = msg.len; + + if (!get_oid("ORIG_HEAD", &oid_old_orig)) + old_orig = &oid_old_orig; + if (!get_oid("HEAD", &oid_orig)) { + orig = &oid_orig; + strbuf_addstr(&msg, "updating ORIG_HEAD"); + update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, + UPDATE_REFS_MSG_ON_ERR); + } else if (old_orig) + delete_ref(NULL, "ORIG_HEAD", old_orig, 0); + strbuf_setlen(&msg, prefix_len); + strbuf_addstr(&msg, "updating HEAD"); + if (!switch_to_branch) + ret = update_ref(msg.buf, "HEAD", oid, orig, REF_NO_DEREF, + UPDATE_REFS_MSG_ON_ERR); + else { + ret = create_symref("HEAD", switch_to_branch, msg.buf); + if (!ret) + ret = update_ref(msg.buf, "HEAD", oid, NULL, 0, + UPDATE_REFS_MSG_ON_ERR); + } + + strbuf_release(&msg); + return ret; +} + int cmd_rebase(int argc, const char **argv, const char *prefix) { + struct rebase_options options = { + .type = REBASE_UNSPECIFIED, + }; + const char *branch_name; + int ret, flags; + struct strbuf msg = STRBUF_INIT; + struct strbuf revisions = STRBUF_INIT; + /* * NEEDSWORK: Once the builtin rebase has been tested enough * and git-legacy-rebase.sh is retired to contrib/, this preamble @@ -54,5 +324,98 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) trace_repo_setup(prefix); setup_work_tree(); - die("TODO"); + git_config(git_default_config, NULL); + + switch (options.type) { + case REBASE_MERGE: + case REBASE_INTERACTIVE: + case REBASE_PRESERVE_MERGES: + options.state_dir = merge_dir(); + break; + case REBASE_AM: + options.state_dir = apply_dir(); + break; + default: + /* the default rebase backend is `--am` */ + options.type = REBASE_AM; + options.state_dir = apply_dir(); + break; + } + + if (!options.root) { + if (argc < 2) + die("TODO: handle @{upstream}"); + else { + options.upstream_name = argv[1]; + argc--; + argv++; + if (!strcmp(options.upstream_name, "-")) + options.upstream_name = "@{-1}"; + } + options.upstream = peel_committish(options.upstream_name); + if (!options.upstream) + die(_("invalid upstream '%s'"), options.upstream_name); + } else + die("TODO: upstream for --root"); + + /* Make sure the branch to rebase onto is valid. */ + if (!options.onto_name) + options.onto_name = options.upstream_name; + if (strstr(options.onto_name, "...")) { + die("TODO"); + } else { + options.onto = peel_committish(options.onto_name); + if (!options.onto) + die(_("Does not point to a valid commit '%s'"), + options.onto_name); + } + + /* + * If the branch to rebase is given, that is the branch we will rebase + * branch_name -- branch/commit being rebased, or + * HEAD (already detached) + * orig_head -- commit object name of tip of the branch before rebasing + * head_name -- refs/heads/ or "detached HEAD" + */ + if (argc > 1) + die("TODO: handle switch_to"); + else { + /* Do not need to switch branches, we are already on it. */ + options.head_name = + xstrdup_or_null(resolve_ref_unsafe("HEAD", 0, NULL, + &flags)); + if (!options.head_name) + die(_("No such ref: %s"), "HEAD"); + if (flags & REF_ISSYMREF) { + if (!skip_prefix(options.head_name, + "refs/heads/", &branch_name)) + branch_name = options.head_name; + + } else { + options.head_name = xstrdup("detached HEAD"); + branch_name = "HEAD"; + } + if (get_oid("HEAD", &options.orig_head)) + die(_("Could not resolve HEAD to a revision")); + } + + strbuf_addf(&msg, "rebase: checkout %s", options.onto_name); + if (reset_head(&options.onto->object.oid, "checkout", NULL, 1)) + die(_("Could not detach HEAD")); + strbuf_release(&msg); + + strbuf_addf(&revisions, "%s..%s", + options.root ? oid_to_hex(&options.onto->object.oid) : + (options.restrict_revision ? + oid_to_hex(&options.restrict_revision->object.oid) : + oid_to_hex(&options.upstream->object.oid)), + oid_to_hex(&options.orig_head)); + + options.revisions = revisions.buf; + + ret = !!run_specific_rebase(&options); + + strbuf_release(&revisions); + free(options.head_name); + return ret; }