diff options
Diffstat (limited to 'builtin')
-rw-r--r-- | builtin/branch.c | 8 | ||||
-rw-r--r-- | builtin/fetch-pack.c | 13 | ||||
-rw-r--r-- | builtin/help.c | 34 | ||||
-rw-r--r-- | builtin/ls-remote.c | 2 | ||||
-rw-r--r-- | builtin/merge-base.c | 81 | ||||
-rw-r--r-- | builtin/mktree.c | 2 | ||||
-rw-r--r-- | builtin/pack-objects.c | 1 | ||||
-rw-r--r-- | builtin/rebase--helper.c | 88 | ||||
-rw-r--r-- | builtin/rebase--interactive.c | 271 | ||||
-rw-r--r-- | builtin/rebase.c | 1545 | ||||
-rw-r--r-- | builtin/receive-pack.c | 12 | ||||
-rw-r--r-- | builtin/repack.c | 13 | ||||
-rw-r--r-- | builtin/rev-list.c | 11 | ||||
-rw-r--r-- | builtin/submodule--helper.c | 2 |
14 files changed, 1886 insertions, 197 deletions
diff --git a/builtin/branch.c b/builtin/branch.c index c396c41533..0c55f7f065 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -716,8 +716,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) print_columns(&output, colopts, NULL); string_list_clear(&output, 0); return 0; - } - else if (edit_description) { + } else if (edit_description) { const char *branch_name; struct strbuf branch_ref = STRBUF_INIT; @@ -809,11 +808,6 @@ int cmd_branch(int argc, const char **argv, const char *prefix) git_config_set_multivar(buf.buf, NULL, NULL, 1); strbuf_release(&buf); } else if (argc > 0 && argc <= 2) { - struct branch *branch = branch_get(argv[0]); - - if (!branch) - die(_("no such branch '%s'"), argv[0]); - if (filter.kind != FILTER_REFS_BRANCHES) die(_("-a and -r options to 'git branch' do not make sense with a branch name")); diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index 1a1bc63566..63e69a5801 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -16,13 +16,14 @@ static void add_sought_entry(struct ref ***sought, int *nr, int *alloc, { struct ref *ref; struct object_id oid; + const char *p; - if (!get_oid_hex(name, &oid)) { - if (name[GIT_SHA1_HEXSZ] == ' ') { - /* <sha1> <ref>, find refname */ - name += GIT_SHA1_HEXSZ + 1; - } else if (name[GIT_SHA1_HEXSZ] == '\0') { - ; /* <sha1>, leave sha1 as name */ + if (!parse_oid_hex(name, &oid, &p)) { + if (*p == ' ') { + /* <oid> <ref>, find refname */ + name = p + 1; + } else if (*p == '\0') { + ; /* <oid>, leave oid as name */ } else { /* <ref>, clear cruft from oid */ oidclr(&oid); diff --git a/builtin/help.c b/builtin/help.c index d83dac2839..7739a5c155 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -415,9 +415,37 @@ static const char *check_git_cmd(const char* cmd) alias = alias_lookup(cmd); if (alias) { - printf_ln(_("'%s' is aliased to '%s'"), cmd, alias); - free(alias); - exit(0); + const char **argv; + int count; + + /* + * handle_builtin() in git.c rewrites "git cmd --help" + * to "git help --exclude-guides cmd", so we can use + * exclude_guides to distinguish "git cmd --help" from + * "git help cmd". In the latter case, or if cmd is an + * alias for a shell command, just print the alias + * definition. + */ + if (!exclude_guides || alias[0] == '!') { + printf_ln(_("'%s' is aliased to '%s'"), cmd, alias); + free(alias); + exit(0); + } + /* + * Otherwise, we pretend that the command was "git + * word0 --help". We use split_cmdline() to get the + * first word of the alias, to ensure that we use the + * same rules as when the alias is actually + * used. split_cmdline() modifies alias in-place. + */ + fprintf_ln(stderr, _("'%s' is aliased to '%s'"), cmd, alias); + count = split_cmdline(alias, &argv); + if (count < 0) + die(_("bad alias.%s string: %s"), cmd, + split_cmdline_strerror(count)); + free(argv); + UNLEAK(alias); + return alias; } if (exclude_guides) diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index 1a25df7ee1..6a0cdec30d 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -151,6 +151,6 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) } UNLEAK(sorting); - UNLEAK(ref_array); + ref_array_clear(&ref_array); return status; } diff --git a/builtin/merge-base.c b/builtin/merge-base.c index 1c92099070..e3f8da13b6 100644 --- a/builtin/merge-base.c +++ b/builtin/merge-base.c @@ -111,54 +111,12 @@ static int handle_is_ancestor(int argc, const char **argv) return 1; } -struct rev_collect { - struct commit **commit; - int nr; - int alloc; - unsigned int initial : 1; -}; - -static void add_one_commit(struct object_id *oid, struct rev_collect *revs) -{ - struct commit *commit; - - if (is_null_oid(oid)) - return; - - commit = lookup_commit(the_repository, oid); - if (!commit || - (commit->object.flags & TMP_MARK) || - parse_commit(commit)) - return; - - ALLOC_GROW(revs->commit, revs->nr + 1, revs->alloc); - revs->commit[revs->nr++] = commit; - commit->object.flags |= TMP_MARK; -} - -static int collect_one_reflog_ent(struct object_id *ooid, struct object_id *noid, - const char *ident, timestamp_t timestamp, - int tz, const char *message, void *cbdata) -{ - struct rev_collect *revs = cbdata; - - if (revs->initial) { - revs->initial = 0; - add_one_commit(ooid, revs); - } - add_one_commit(noid, revs); - return 0; -} - static int handle_fork_point(int argc, const char **argv) { struct object_id oid; char *refname; + struct commit *derived, *fork_point; const char *commitname; - struct rev_collect revs; - struct commit *derived; - struct commit_list *bases; - int i, ret = 0; switch (dwim_ref(argv[0], strlen(argv[0]), &oid, &refname)) { case 0: @@ -174,41 +132,14 @@ static int handle_fork_point(int argc, const char **argv) die("Not a valid object name: '%s'", commitname); derived = lookup_commit_reference(the_repository, &oid); - memset(&revs, 0, sizeof(revs)); - revs.initial = 1; - for_each_reflog_ent(refname, collect_one_reflog_ent, &revs); - if (!revs.nr && !get_oid(refname, &oid)) - add_one_commit(&oid, &revs); + fork_point = get_fork_point(refname, derived); - for (i = 0; i < revs.nr; i++) - revs.commit[i]->object.flags &= ~TMP_MARK; - - bases = get_merge_bases_many_dirty(derived, revs.nr, revs.commit); - - /* - * There should be one and only one merge base, when we found - * a common ancestor among reflog entries. - */ - if (!bases || bases->next) { - ret = 1; - goto cleanup_return; - } - - /* And the found one must be one of the reflog entries */ - for (i = 0; i < revs.nr; i++) - if (&bases->item->object == &revs.commit[i]->object) - break; /* found */ - if (revs.nr <= i) { - ret = 1; /* not found */ - goto cleanup_return; - } - - printf("%s\n", oid_to_hex(&bases->item->object.oid)); + if (!fork_point) + return 1; -cleanup_return: - free_commit_list(bases); - return ret; + printf("%s\n", oid_to_hex(&fork_point->object.oid)); + return 0; } int cmd_merge_base(int argc, const char **argv, const char *prefix) diff --git a/builtin/mktree.c b/builtin/mktree.c index 2dc4ad6ba8..94e82b8504 100644 --- a/builtin/mktree.c +++ b/builtin/mktree.c @@ -98,7 +98,7 @@ static void mktree_line(char *buf, size_t len, int nul_term_line, int allow_miss *ntr++ = 0; /* now at the beginning of SHA1 */ - path = ntr + 41; /* at the beginning of name */ + path = (char *)p + 1; /* at the beginning of name */ if (!nul_term_line && path[0] == '"') { struct strbuf p_uq = STRBUF_INIT; if (unquote_c_style(&p_uq, path, NULL)) diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index b059b86aee..e50c6cd1ff 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -2399,7 +2399,6 @@ static void init_threaded_search(void) pthread_mutex_init(&cache_mutex, NULL); pthread_mutex_init(&progress_mutex, NULL); pthread_cond_init(&progress_cond, NULL); - pthread_mutex_init(&to_pack.lock, NULL); old_try_to_free_routine = set_try_to_free_routine(try_to_free_from_threads); } diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c deleted file mode 100644 index f7c2a5fdc8..0000000000 --- a/builtin/rebase--helper.c +++ /dev/null @@ -1,88 +0,0 @@ -#include "builtin.h" -#include "cache.h" -#include "config.h" -#include "parse-options.h" -#include "sequencer.h" - -static const char * const builtin_rebase_helper_usage[] = { - N_("git rebase--helper [<options>]"), - NULL -}; - -int cmd_rebase__helper(int argc, const char **argv, const char *prefix) -{ - struct replay_opts opts = REPLAY_OPTS_INIT; - unsigned flags = 0, keep_empty = 0, rebase_merges = 0; - int abbreviate_commands = 0, rebase_cousins = -1; - enum { - CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS, - CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH, - ADD_EXEC - } command = 0; - struct option options[] = { - OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")), - OPT_BOOL(0, "keep-empty", &keep_empty, N_("keep empty commits")), - OPT_BOOL(0, "allow-empty-message", &opts.allow_empty_message, - N_("allow commits with empty messages")), - OPT_BOOL(0, "rebase-merges", &rebase_merges, N_("rebase merge commits")), - OPT_BOOL(0, "rebase-cousins", &rebase_cousins, - N_("keep original branch points of cousins")), - OPT_CMDMODE(0, "continue", &command, N_("continue rebase"), - CONTINUE), - OPT_CMDMODE(0, "abort", &command, N_("abort rebase"), - ABORT), - OPT_CMDMODE(0, "make-script", &command, - N_("make rebase script"), MAKE_SCRIPT), - OPT_CMDMODE(0, "shorten-ids", &command, - N_("shorten commit ids in the todo list"), SHORTEN_OIDS), - OPT_CMDMODE(0, "expand-ids", &command, - N_("expand commit ids in the todo list"), EXPAND_OIDS), - OPT_CMDMODE(0, "check-todo-list", &command, - N_("check the todo list"), CHECK_TODO_LIST), - OPT_CMDMODE(0, "skip-unnecessary-picks", &command, - N_("skip unnecessary picks"), SKIP_UNNECESSARY_PICKS), - OPT_CMDMODE(0, "rearrange-squash", &command, - N_("rearrange fixup/squash lines"), REARRANGE_SQUASH), - OPT_CMDMODE(0, "add-exec-commands", &command, - N_("insert exec commands in todo list"), ADD_EXEC), - OPT_END() - }; - - sequencer_init_config(&opts); - git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands); - - opts.action = REPLAY_INTERACTIVE_REBASE; - opts.allow_ff = 1; - opts.allow_empty = 1; - - argc = parse_options(argc, argv, NULL, options, - builtin_rebase_helper_usage, PARSE_OPT_KEEP_ARGV0); - - flags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0; - flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0; - flags |= rebase_merges ? TODO_LIST_REBASE_MERGES : 0; - flags |= rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0; - flags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0; - - if (rebase_cousins >= 0 && !rebase_merges) - warning(_("--[no-]rebase-cousins has no effect without " - "--rebase-merges")); - - if (command == CONTINUE && argc == 1) - return !!sequencer_continue(&opts); - if (command == ABORT && argc == 1) - return !!sequencer_remove_state(&opts); - if (command == MAKE_SCRIPT && argc > 1) - return !!sequencer_make_script(stdout, argc, argv, flags); - if ((command == SHORTEN_OIDS || command == EXPAND_OIDS) && argc == 1) - return !!transform_todos(flags); - if (command == CHECK_TODO_LIST && argc == 1) - return !!check_todo_list(); - if (command == SKIP_UNNECESSARY_PICKS && argc == 1) - return !!skip_unnecessary_picks(); - if (command == REARRANGE_SQUASH && argc == 1) - return !!rearrange_squash(); - if (command == ADD_EXEC && argc == 2) - return !!sequencer_add_exec_commands(argv[1]); - usage_with_options(builtin_rebase_helper_usage, options); -} diff --git a/builtin/rebase--interactive.c b/builtin/rebase--interactive.c new file mode 100644 index 0000000000..a2ab68ed06 --- /dev/null +++ b/builtin/rebase--interactive.c @@ -0,0 +1,271 @@ +#include "builtin.h" +#include "cache.h" +#include "config.h" +#include "parse-options.h" +#include "sequencer.h" +#include "rebase-interactive.h" +#include "argv-array.h" +#include "refs.h" +#include "rerere.h" +#include "run-command.h" + +static GIT_PATH_FUNC(path_state_dir, "rebase-merge/") +static GIT_PATH_FUNC(path_squash_onto, "rebase-merge/squash-onto") +static GIT_PATH_FUNC(path_interactive, "rebase-merge/interactive") + +static int get_revision_ranges(const char *upstream, const char *onto, + const char **head_hash, + char **revisions, char **shortrevisions) +{ + const char *base_rev = upstream ? upstream : onto, *shorthead; + struct object_id orig_head; + + if (get_oid("HEAD", &orig_head)) + return error(_("no HEAD?")); + + *head_hash = find_unique_abbrev(&orig_head, GIT_MAX_HEXSZ); + *revisions = xstrfmt("%s...%s", base_rev, *head_hash); + + shorthead = find_unique_abbrev(&orig_head, DEFAULT_ABBREV); + + if (upstream) { + const char *shortrev; + struct object_id rev_oid; + + get_oid(base_rev, &rev_oid); + shortrev = find_unique_abbrev(&rev_oid, DEFAULT_ABBREV); + + *shortrevisions = xstrfmt("%s..%s", shortrev, shorthead); + } else + *shortrevisions = xstrdup(shorthead); + + return 0; +} + +static int init_basic_state(struct replay_opts *opts, const char *head_name, + const char *onto, const char *orig_head) +{ + FILE *interactive; + + if (!is_directory(path_state_dir()) && mkdir_in_gitdir(path_state_dir())) + return error_errno(_("could not create temporary %s"), path_state_dir()); + + delete_reflog("REBASE_HEAD"); + + interactive = fopen(path_interactive(), "w"); + if (!interactive) + return error_errno(_("could not mark as interactive")); + fclose(interactive); + + return write_basic_state(opts, head_name, onto, orig_head); +} + +static int do_interactive_rebase(struct replay_opts *opts, unsigned flags, + const char *switch_to, const char *upstream, + const char *onto, const char *onto_name, + const char *squash_onto, const char *head_name, + const char *restrict_revision, char *raw_strategies, + const char *cmd, unsigned autosquash) +{ + int ret; + const char *head_hash = NULL; + char *revisions = NULL, *shortrevisions = NULL; + struct argv_array make_script_args = ARGV_ARRAY_INIT; + FILE *todo_list; + + if (prepare_branch_to_be_rebased(opts, switch_to)) + return -1; + + if (get_revision_ranges(upstream, onto, &head_hash, + &revisions, &shortrevisions)) + return -1; + + if (raw_strategies) + parse_strategy_opts(opts, raw_strategies); + + if (init_basic_state(opts, head_name, onto, head_hash)) { + free(revisions); + free(shortrevisions); + + return -1; + } + + if (!upstream && squash_onto) + write_file(path_squash_onto(), "%s\n", squash_onto); + + todo_list = fopen(rebase_path_todo(), "w"); + if (!todo_list) { + free(revisions); + free(shortrevisions); + + return error_errno(_("could not open %s"), rebase_path_todo()); + } + + argv_array_pushl(&make_script_args, "", revisions, NULL); + if (restrict_revision) + argv_array_push(&make_script_args, restrict_revision); + + ret = sequencer_make_script(todo_list, + make_script_args.argc, make_script_args.argv, + flags); + fclose(todo_list); + + if (ret) + error(_("could not generate todo list")); + else { + discard_cache(); + ret = complete_action(opts, flags, shortrevisions, onto_name, onto, + head_hash, cmd, autosquash); + } + + free(revisions); + free(shortrevisions); + argv_array_clear(&make_script_args); + + return ret; +} + +static const char * const builtin_rebase_interactive_usage[] = { + N_("git rebase--interactive [<options>]"), + NULL +}; + +int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) +{ + struct replay_opts opts = REPLAY_OPTS_INIT; + unsigned flags = 0, keep_empty = 0, rebase_merges = 0, autosquash = 0; + int abbreviate_commands = 0, rebase_cousins = -1, ret = 0; + const char *onto = NULL, *onto_name = NULL, *restrict_revision = NULL, + *squash_onto = NULL, *upstream = NULL, *head_name = NULL, + *switch_to = NULL, *cmd = NULL; + char *raw_strategies = NULL; + enum { + NONE = 0, CONTINUE, SKIP, EDIT_TODO, SHOW_CURRENT_PATCH, + SHORTEN_OIDS, EXPAND_OIDS, CHECK_TODO_LIST, REARRANGE_SQUASH, ADD_EXEC + } command = 0; + struct option options[] = { + OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")), + OPT_BOOL(0, "keep-empty", &keep_empty, N_("keep empty commits")), + OPT_BOOL(0, "allow-empty-message", &opts.allow_empty_message, + N_("allow commits with empty messages")), + OPT_BOOL(0, "rebase-merges", &rebase_merges, N_("rebase merge commits")), + OPT_BOOL(0, "rebase-cousins", &rebase_cousins, + N_("keep original branch points of cousins")), + OPT_BOOL(0, "autosquash", &autosquash, + N_("move commits that begin with squash!/fixup!")), + OPT_BOOL(0, "signoff", &opts.signoff, N_("sign commits")), + OPT__VERBOSE(&opts.verbose, N_("be verbose")), + OPT_CMDMODE(0, "continue", &command, N_("continue rebase"), + CONTINUE), + OPT_CMDMODE(0, "skip", &command, N_("skip commit"), SKIP), + OPT_CMDMODE(0, "edit-todo", &command, N_("edit the todo list"), + EDIT_TODO), + OPT_CMDMODE(0, "show-current-patch", &command, N_("show the current patch"), + SHOW_CURRENT_PATCH), + OPT_CMDMODE(0, "shorten-ids", &command, + N_("shorten commit ids in the todo list"), SHORTEN_OIDS), + OPT_CMDMODE(0, "expand-ids", &command, + N_("expand commit ids in the todo list"), EXPAND_OIDS), + OPT_CMDMODE(0, "check-todo-list", &command, + N_("check the todo list"), CHECK_TODO_LIST), + OPT_CMDMODE(0, "rearrange-squash", &command, + N_("rearrange fixup/squash lines"), REARRANGE_SQUASH), + OPT_CMDMODE(0, "add-exec-commands", &command, + N_("insert exec commands in todo list"), ADD_EXEC), + OPT_STRING(0, "onto", &onto, N_("onto"), N_("onto")), + OPT_STRING(0, "restrict-revision", &restrict_revision, + N_("restrict-revision"), N_("restrict revision")), + OPT_STRING(0, "squash-onto", &squash_onto, N_("squash-onto"), + N_("squash onto")), + OPT_STRING(0, "upstream", &upstream, N_("upstream"), + N_("the upstream commit")), + OPT_STRING(0, "head-name", &head_name, N_("head-name"), N_("head name")), + { OPTION_STRING, 'S', "gpg-sign", &opts.gpg_sign, N_("key-id"), + N_("GPG-sign commits"), + PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, + OPT_STRING(0, "strategy", &opts.strategy, N_("strategy"), + N_("rebase strategy")), + OPT_STRING(0, "strategy-opts", &raw_strategies, N_("strategy-opts"), + N_("strategy options")), + OPT_STRING(0, "switch-to", &switch_to, N_("switch-to"), + N_("the branch or commit to checkout")), + OPT_STRING(0, "onto-name", &onto_name, N_("onto-name"), N_("onto name")), + OPT_STRING(0, "cmd", &cmd, N_("cmd"), N_("the command to run")), + OPT_RERERE_AUTOUPDATE(&opts.allow_rerere_auto), + OPT_END() + }; + + sequencer_init_config(&opts); + git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands); + + opts.action = REPLAY_INTERACTIVE_REBASE; + opts.allow_ff = 1; + opts.allow_empty = 1; + + if (argc == 1) + usage_with_options(builtin_rebase_interactive_usage, options); + + argc = parse_options(argc, argv, NULL, options, + builtin_rebase_interactive_usage, PARSE_OPT_KEEP_ARGV0); + + opts.gpg_sign = xstrdup_or_null(opts.gpg_sign); + + flags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0; + flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0; + flags |= rebase_merges ? TODO_LIST_REBASE_MERGES : 0; + flags |= rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0; + flags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0; + + if (rebase_cousins >= 0 && !rebase_merges) + warning(_("--[no-]rebase-cousins has no effect without " + "--rebase-merges")); + + switch (command) { + case NONE: + if (!onto && !upstream) + die(_("a base commit must be provided with --upstream or --onto")); + + ret = do_interactive_rebase(&opts, flags, switch_to, upstream, onto, + onto_name, squash_onto, head_name, restrict_revision, + raw_strategies, cmd, autosquash); + break; + case SKIP: { + struct string_list merge_rr = STRING_LIST_INIT_DUP; + + rerere_clear(&merge_rr); + /* fallthrough */ + case CONTINUE: + ret = sequencer_continue(&opts); + break; + } + case EDIT_TODO: + ret = edit_todo_list(flags); + break; + case SHOW_CURRENT_PATCH: { + struct child_process cmd = CHILD_PROCESS_INIT; + + cmd.git_cmd = 1; + argv_array_pushl(&cmd.args, "show", "REBASE_HEAD", "--", NULL); + ret = run_command(&cmd); + + break; + } + case SHORTEN_OIDS: + case EXPAND_OIDS: + ret = transform_todos(flags); + break; + case CHECK_TODO_LIST: + ret = check_todo_list(); + break; + case REARRANGE_SQUASH: + ret = rearrange_squash(); + break; + case ADD_EXEC: + ret = sequencer_add_exec_commands(cmd); + break; + default: + BUG("invalid command '%d'", command); + } + + return !!ret; +} diff --git a/builtin/rebase.c b/builtin/rebase.c new file mode 100644 index 0000000000..0ee06aa363 --- /dev/null +++ b/builtin/rebase.c @@ -0,0 +1,1545 @@ +/* + * "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" +#include "packfile.h" +#include "refs.h" +#include "quote.h" +#include "config.h" +#include "cache-tree.h" +#include "unpack-trees.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" +#include "rerere.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(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) +{ + 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 1; + } + + strbuf_trim(&out); + ret = !strcmp("true", out.buf); + strbuf_release(&out); + return ret; +} + +struct rebase_options { + enum rebase_type type; + const char *state_dir; + struct commit *upstream; + const char *upstream_name; + const char *upstream_arg; + char *head_name; + struct object_id orig_head; + struct commit *onto; + const char *onto_name; + const char *revisions; + const char *switch_to; + int root; + struct object_id *squash_onto; + struct commit *restrict_revision; + 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; + const char *action; + int signoff; + int allow_rerere_autoupdate; + int keep_empty; + int autosquash; + char *gpg_sign_opt; + int autostash; + char *cmd; + int allow_empty_message; + int rebase_merges, rebase_cousins; + char *strategy, *strategy_opts; + struct strbuf git_format_patch_opt; +}; + +static int is_interactive(struct rebase_options *opts) +{ + return opts->type == REBASE_INTERACTIVE || + opts->type == REBASE_PRESERVE_MERGES; +} + +static void imply_interactive(struct rebase_options *opts, const char *option) +{ + switch (opts->type) { + case REBASE_AM: + die(_("%s requires an interactive rebase"), option); + break; + case REBASE_INTERACTIVE: + case REBASE_PRESERVE_MERGES: + break; + case REBASE_MERGE: + /* we silently *upgrade* --merge to --interactive if needed */ + default: + opts->type = REBASE_INTERACTIVE; /* implied */ + break; + } +} + +/* 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; +} + +/* Read one file, then strip line endings */ +static int read_one(const char *path, struct strbuf *buf) +{ + if (strbuf_read_file(buf, path, 0) < 0) + return error_errno(_("could not read '%s'"), path); + strbuf_trim_trailing_newline(buf); + return 0; +} + +/* Initialize the rebase options from the state directory. */ +static int read_basic_state(struct rebase_options *opts) +{ + struct strbuf head_name = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT; + struct object_id oid; + + if (read_one(state_dir_path("head-name", opts), &head_name) || + read_one(state_dir_path("onto", opts), &buf)) + return -1; + opts->head_name = starts_with(head_name.buf, "refs/") ? + xstrdup(head_name.buf) : NULL; + strbuf_release(&head_name); + if (get_oid(buf.buf, &oid)) + return error(_("could not get 'onto': '%s'"), buf.buf); + opts->onto = lookup_commit_or_die(&oid, buf.buf); + + /* + * We always write to orig-head, but interactive rebase used to write to + * head. Fall back to reading from head to cover for the case that the + * user upgraded git with an ongoing interactive rebase. + */ + strbuf_reset(&buf); + if (file_exists(state_dir_path("orig-head", opts))) { + if (read_one(state_dir_path("orig-head", opts), &buf)) + return -1; + } else if (read_one(state_dir_path("head", opts), &buf)) + return -1; + if (get_oid(buf.buf, &opts->orig_head)) + return error(_("invalid orig-head: '%s'"), buf.buf); + + strbuf_reset(&buf); + if (read_one(state_dir_path("quiet", opts), &buf)) + return -1; + if (buf.len) + opts->flags &= ~REBASE_NO_QUIET; + else + opts->flags |= REBASE_NO_QUIET; + + if (file_exists(state_dir_path("verbose", opts))) + opts->flags |= REBASE_VERBOSE; + + if (file_exists(state_dir_path("signoff", opts))) { + opts->signoff = 1; + opts->flags |= REBASE_FORCE; + } + + if (file_exists(state_dir_path("allow_rerere_autoupdate", opts))) { + strbuf_reset(&buf); + if (read_one(state_dir_path("allow_rerere_autoupdate", opts), + &buf)) + return -1; + if (!strcmp(buf.buf, "--rerere-autoupdate")) + opts->allow_rerere_autoupdate = 1; + else if (!strcmp(buf.buf, "--no-rerere-autoupdate")) + opts->allow_rerere_autoupdate = 0; + else + warning(_("ignoring invalid allow_rerere_autoupdate: " + "'%s'"), buf.buf); + } else + opts->allow_rerere_autoupdate = -1; + + if (file_exists(state_dir_path("gpg_sign_opt", opts))) { + strbuf_reset(&buf); + if (read_one(state_dir_path("gpg_sign_opt", opts), + &buf)) + return -1; + free(opts->gpg_sign_opt); + opts->gpg_sign_opt = xstrdup(buf.buf); + } + + if (file_exists(state_dir_path("strategy", opts))) { + strbuf_reset(&buf); + if (read_one(state_dir_path("strategy", opts), &buf)) + return -1; + free(opts->strategy); + opts->strategy = xstrdup(buf.buf); + } + + if (file_exists(state_dir_path("strategy_opts", opts))) { + strbuf_reset(&buf); + if (read_one(state_dir_path("strategy_opts", opts), &buf)) + return -1; + free(opts->strategy_opts); + opts->strategy_opts = xstrdup(buf.buf); + } + + strbuf_release(&buf); + + return 0; +} + +static int apply_autostash(struct rebase_options *opts) +{ + const char *path = state_dir_path("autostash", opts); + struct strbuf autostash = STRBUF_INIT; + struct child_process stash_apply = CHILD_PROCESS_INIT; + + if (!file_exists(path)) + return 0; + + if (read_one(path, &autostash)) + return error(_("Could not read '%s'"), path); + /* Ensure that the hash is not mistaken for a number */ + strbuf_addstr(&autostash, "^0"); + argv_array_pushl(&stash_apply.args, + "stash", "apply", autostash.buf, NULL); + stash_apply.git_cmd = 1; + stash_apply.no_stderr = stash_apply.no_stdout = + stash_apply.no_stdin = 1; + if (!run_command(&stash_apply)) + printf(_("Applied autostash.\n")); + else { + struct argv_array args = ARGV_ARRAY_INIT; + int res = 0; + + argv_array_pushl(&args, + "stash", "store", "-m", "autostash", "-q", + autostash.buf, NULL); + if (run_command_v_opt(args.argv, RUN_GIT_CMD)) + res = error(_("Cannot store %s"), autostash.buf); + argv_array_clear(&args); + strbuf_release(&autostash); + if (res) + return res; + + fprintf(stderr, + _("Applying autostash resulted in conflicts.\n" + "Your changes are safe in the stash.\n" + "You can run \"git stash pop\" or \"git stash drop\" " + "at any time.\n")); + } + + strbuf_release(&autostash); + return 0; +} + +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(opts); + 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 const char *resolvemsg = +N_("Resolve all conflicts manually, mark them as resolved with\n" +"\"git add/rm <conflicted_files>\", then run \"git rebase --continue\".\n" +"You can instead skip this commit: run \"git rebase --skip\".\n" +"To abort and get back to the state before \"git rebase\", run " +"\"git rebase --abort\"."); + +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; + + if (opts->type == REBASE_INTERACTIVE) { + /* Run builtin interactive rebase */ + struct child_process child = CHILD_PROCESS_INIT; + + argv_array_pushf(&child.env_array, "GIT_CHERRY_PICK_HELP=%s", + resolvemsg); + if (!(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) { + argv_array_push(&child.env_array, "GIT_EDITOR=:"); + opts->autosquash = 0; + } + + child.git_cmd = 1; + argv_array_push(&child.args, "rebase--interactive"); + + if (opts->action) + argv_array_pushf(&child.args, "--%s", opts->action); + if (opts->keep_empty) + argv_array_push(&child.args, "--keep-empty"); + if (opts->rebase_merges) + argv_array_push(&child.args, "--rebase-merges"); + if (opts->rebase_cousins) + argv_array_push(&child.args, "--rebase-cousins"); + if (opts->autosquash) + argv_array_push(&child.args, "--autosquash"); + if (opts->flags & REBASE_VERBOSE) + argv_array_push(&child.args, "--verbose"); + if (opts->flags & REBASE_FORCE) + argv_array_push(&child.args, "--no-ff"); + if (opts->restrict_revision) + argv_array_pushf(&child.args, + "--restrict-revision=^%s", + oid_to_hex(&opts->restrict_revision->object.oid)); + if (opts->upstream) + argv_array_pushf(&child.args, "--upstream=%s", + oid_to_hex(&opts->upstream->object.oid)); + if (opts->onto) + argv_array_pushf(&child.args, "--onto=%s", + oid_to_hex(&opts->onto->object.oid)); + if (opts->squash_onto) + argv_array_pushf(&child.args, "--squash-onto=%s", + oid_to_hex(opts->squash_onto)); + if (opts->onto_name) + argv_array_pushf(&child.args, "--onto-name=%s", + opts->onto_name); + argv_array_pushf(&child.args, "--head-name=%s", + opts->head_name ? + opts->head_name : "detached HEAD"); + if (opts->strategy) + argv_array_pushf(&child.args, "--strategy=%s", + opts->strategy); + if (opts->strategy_opts) + argv_array_pushf(&child.args, "--strategy-opts=%s", + opts->strategy_opts); + if (opts->switch_to) + argv_array_pushf(&child.args, "--switch-to=%s", + opts->switch_to); + if (opts->cmd) + argv_array_pushf(&child.args, "--cmd=%s", opts->cmd); + if (opts->allow_empty_message) + argv_array_push(&child.args, "--allow-empty-message"); + if (opts->allow_rerere_autoupdate > 0) + argv_array_push(&child.args, "--rerere-autoupdate"); + else if (opts->allow_rerere_autoupdate == 0) + argv_array_push(&child.args, "--no-rerere-autoupdate"); + if (opts->gpg_sign_opt) + argv_array_push(&child.args, opts->gpg_sign_opt); + if (opts->signoff) + argv_array_push(&child.args, "--signoff"); + + status = run_command(&child); + goto finished_rebase; + } + + 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", opts->upstream ? + oid_to_hex(&opts->upstream->object.oid) : NULL); + 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, "onto", opts->onto ? + oid_to_hex(&opts->onto->object.oid) : NULL); + 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); + 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); + add_var(&script_snippet, "action", opts->action ? opts->action : ""); + add_var(&script_snippet, "signoff", opts->signoff ? "--signoff" : ""); + add_var(&script_snippet, "allow_rerere_autoupdate", + opts->allow_rerere_autoupdate < 0 ? "" : + opts->allow_rerere_autoupdate ? + "--rerere-autoupdate" : "--no-rerere-autoupdate"); + add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : ""); + add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : ""); + add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt); + add_var(&script_snippet, "cmd", opts->cmd); + add_var(&script_snippet, "allow_empty_message", + opts->allow_empty_message ? "--allow-empty-message" : ""); + add_var(&script_snippet, "rebase_merges", + opts->rebase_merges ? "t" : ""); + add_var(&script_snippet, "rebase_cousins", + opts->rebase_cousins ? "t" : ""); + add_var(&script_snippet, "strategy", opts->strategy); + add_var(&script_snippet, "strategy_opts", opts->strategy_opts); + add_var(&script_snippet, "rebase_root", opts->root ? "t" : ""); + add_var(&script_snippet, "squash_onto", + opts->squash_onto ? oid_to_hex(opts->squash_onto) : ""); + add_var(&script_snippet, "git_format_patch_opt", + opts->git_format_patch_opt.buf); + + if (is_interactive(opts) && + !(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) { + strbuf_addstr(&script_snippet, + "GIT_EDITOR=:; export GIT_EDITOR; "); + opts->autosquash = 0; + } + + switch (opts->type) { + case REBASE_AM: + backend = "git-rebase--am"; + backend_func = "git_rebase__am"; + 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); +finished_rebase: + if (opts->dont_finish_rebase) + ; /* do nothing */ + else if (opts->type == REBASE_INTERACTIVE) + ; /* interactive rebase cleans up after itself */ + 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(opts); + 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, + const char *reflog_orig_head, const char *reflog_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 (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) + 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; + if (!reflog_orig_head) { + strbuf_addstr(&msg, "updating ORIG_HEAD"); + reflog_orig_head = msg.buf; + } + update_ref(reflog_orig_head, "ORIG_HEAD", orig, old_orig, 0, + UPDATE_REFS_MSG_ON_ERR); + } else if (old_orig) + delete_ref(NULL, "ORIG_HEAD", old_orig, 0); + if (!reflog_head) { + strbuf_setlen(&msg, prefix_len); + strbuf_addstr(&msg, "updating HEAD"); + reflog_head = msg.buf; + } + if (!switch_to_branch) + ret = update_ref(reflog_head, "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(reflog_head, "HEAD", oid, NULL, 0, + UPDATE_REFS_MSG_ON_ERR); + } + + strbuf_release(&msg); + 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; + } + + if (!strcmp(var, "rebase.autosquash")) { + opts->autosquash = git_config_bool(var, value); + return 0; + } + + if (!strcmp(var, "commit.gpgsign")) { + free(opts->gpg_sign_opt); + opts->gpg_sign_opt = git_config_bool(var, value) ? + xstrdup("-S") : NULL; + return 0; + } + + if (!strcmp(var, "rebase.autostash")) { + opts->autostash = git_config_bool(var, value); + 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 = oideq(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); +} + +/* -i followed by -m is still -i */ +static int parse_opt_merge(const struct option *opt, const char *arg, int unset) +{ + struct rebase_options *opts = opt->value; + + if (!is_interactive(opts)) + opts->type = REBASE_MERGE; + + return 0; +} + +/* -i followed by -p is still explicitly interactive, but -p alone is not */ +static int parse_opt_interactive(const struct option *opt, const char *arg, + int unset) +{ + struct rebase_options *opts = opt->value; + + opts->type = REBASE_INTERACTIVE; + opts->flags |= REBASE_INTERACTIVE_EXPLICIT; + + return 0; +} + +static void NORETURN error_on_missing_default_upstream(void) +{ + struct branch *current_branch = branch_get(NULL); + + printf(_("%s\n" + "Please specify which branch you want to rebase against.\n" + "See git-rebase(1) for details.\n" + "\n" + " git rebase '<branch>'\n" + "\n"), + current_branch ? _("There is no tracking information for " + "the current branch.") : + _("You are not currently on a branch.")); + + if (current_branch) { + const char *remote = current_branch->remote_name; + + if (!remote) + remote = _("<remote>"); + + printf(_("If you wish to set tracking information for this " + "branch you can do so with:\n" + "\n" + " git branch --set-upstream-to=%s/<branch> %s\n" + "\n"), + remote, current_branch->name); + } + exit(1); +} + +int cmd_rebase(int argc, const char **argv, const char *prefix) +{ + struct rebase_options options = { + .type = REBASE_UNSPECIFIED, + .flags = REBASE_NO_QUIET, + .git_am_opt = STRBUF_INIT, + .allow_rerere_autoupdate = -1, + .allow_empty_message = 1, + .git_format_patch_opt = STRBUF_INIT, + }; + const char *branch_name; + int ret, flags, total_argc, in_progress = 0; + int ok_to_skip_pre_rebase = 0; + struct strbuf msg = STRBUF_INIT; + struct strbuf revisions = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT; + struct object_id merge_base; + enum { + NO_ACTION, + ACTION_CONTINUE, + ACTION_SKIP, + ACTION_ABORT, + ACTION_QUIT, + ACTION_EDIT_TODO, + ACTION_SHOW_CURRENT_PATCH, + } action = NO_ACTION; + int committer_date_is_author_date = 0; + int ignore_date = 0; + int ignore_whitespace = 0; + const char *gpg_sign = NULL; + int opt_c = -1; + struct string_list whitespace = STRING_LIST_INIT_NODUP; + struct string_list exec = STRING_LIST_INIT_NODUP; + const char *rebase_merges = NULL; + int fork_point = -1; + struct string_list strategy_options = STRING_LIST_INIT_NODUP; + struct object_id squash_onto; + char *squash_onto_name = NULL; + 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_BOOL(0, "ignore-whitespace", &ignore_whitespace, + N_("passed to 'git apply'")), + OPT_BOOL(0, "signoff", &options.signoff, + N_("add a Signed-off-by: line to each commit")), + OPT_BOOL(0, "committer-date-is-author-date", + &committer_date_is_author_date, + N_("passed to 'git am'")), + OPT_BOOL(0, "ignore-date", &ignore_date, + N_("passed to 'git am'")), + 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_CMDMODE(0, "continue", &action, N_("continue"), + ACTION_CONTINUE), + OPT_CMDMODE(0, "skip", &action, + N_("skip current patch and continue"), ACTION_SKIP), + OPT_CMDMODE(0, "abort", &action, + N_("abort and check out the original branch"), + ACTION_ABORT), + OPT_CMDMODE(0, "quit", &action, + N_("abort but keep HEAD where it is"), ACTION_QUIT), + OPT_CMDMODE(0, "edit-todo", &action, N_("edit the todo list " + "during an interactive rebase"), ACTION_EDIT_TODO), + OPT_CMDMODE(0, "show-current-patch", &action, + N_("show the patch file being applied or merged"), + ACTION_SHOW_CURRENT_PATCH), + { OPTION_CALLBACK, 'm', "merge", &options, NULL, + N_("use merging strategies to rebase"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, + parse_opt_merge }, + { OPTION_CALLBACK, 'i', "interactive", &options, NULL, + N_("let the user edit the list of commits to rebase"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, + parse_opt_interactive }, + OPT_SET_INT('p', "preserve-merges", &options.type, + N_("try to recreate merges instead of ignoring " + "them"), REBASE_PRESERVE_MERGES), + OPT_BOOL(0, "rerere-autoupdate", + &options.allow_rerere_autoupdate, + N_("allow rerere to update index with resolved " + "conflict")), + OPT_BOOL('k', "keep-empty", &options.keep_empty, + N_("preserve empty commits during rebase")), + OPT_BOOL(0, "autosquash", &options.autosquash, + N_("move commits that begin with " + "squash!/fixup! under -i")), + { OPTION_STRING, 'S', "gpg-sign", &gpg_sign, N_("key-id"), + N_("GPG-sign commits"), + PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, + OPT_STRING_LIST(0, "whitespace", &whitespace, + N_("whitespace"), N_("passed to 'git apply'")), + OPT_SET_INT('C', NULL, &opt_c, N_("passed to 'git apply'"), + REBASE_AM), + OPT_BOOL(0, "autostash", &options.autostash, + N_("automatically stash/stash pop before and after")), + OPT_STRING_LIST('x', "exec", &exec, N_("exec"), + N_("add exec lines after each commit of the " + "editable list")), + OPT_BOOL(0, "allow-empty-message", + &options.allow_empty_message, + N_("allow rebasing commits with empty messages")), + {OPTION_STRING, 'r', "rebase-merges", &rebase_merges, + N_("mode"), + N_("try to rebase merges instead of skipping them"), + PARSE_OPT_OPTARG, NULL, (intptr_t)""}, + OPT_BOOL(0, "fork-point", &fork_point, + N_("use 'merge-base --fork-point' to refine upstream")), + OPT_STRING('s', "strategy", &options.strategy, + N_("strategy"), N_("use the given merge strategy")), + OPT_STRING_LIST('X', "strategy-option", &strategy_options, + N_("option"), + N_("pass the argument through to the merge " + "strategy")), + OPT_BOOL(0, "root", &options.root, + N_("rebase all reachable commits up to the root(s)")), + OPT_END(), + }; + + /* + * 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 && !strcmp(argv[1], "-h")) + usage_with_options(builtin_rebase_usage, + builtin_rebase_options); + + prefix = setup_git_directory(); + trace_repo_setup(prefix); + setup_work_tree(); + + git_config(rebase_config, &options); + + strbuf_reset(&buf); + strbuf_addf(&buf, "%s/applying", apply_dir()); + if(file_exists(buf.buf)) + die(_("It looks like 'git am' is in progress. Cannot rebase.")); + + if (is_directory(apply_dir())) { + 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; + + total_argc = argc; + argc = parse_options(argc, argv, prefix, + builtin_rebase_options, + builtin_rebase_usage, 0); + + if (action != NO_ACTION && total_argc != 2) { + usage_with_options(builtin_rebase_usage, + builtin_rebase_options); + } + + if (argc > 2) + usage_with_options(builtin_rebase_usage, + builtin_rebase_options); + + if (action != NO_ACTION && !in_progress) + die(_("No rebase in progress?")); + + if (action == ACTION_EDIT_TODO && !is_interactive(&options)) + die(_("The --edit-todo action can only be used during " + "interactive rebase.")); + + switch (action) { + case ACTION_CONTINUE: { + struct object_id head; + struct lock_file lock_file = LOCK_INIT; + int fd; + + options.action = "continue"; + + /* Sanity check */ + if (get_oid("HEAD", &head)) + die(_("Cannot read HEAD")); + + fd = hold_locked_index(&lock_file, 0); + if (read_index(the_repository->index) < 0) + die(_("could not read index")); + refresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, + NULL); + if (0 <= fd) + update_index_if_able(the_repository->index, + &lock_file); + rollback_lock_file(&lock_file); + + if (has_unstaged_changes(1)) { + puts(_("You must edit all merge conflicts and then\n" + "mark them as resolved using git add")); + exit(1); + } + if (read_basic_state(&options)) + exit(1); + goto run_rebase; + } + case ACTION_SKIP: { + struct string_list merge_rr = STRING_LIST_INIT_DUP; + + options.action = "skip"; + + rerere_clear(&merge_rr); + string_list_clear(&merge_rr, 1); + + if (reset_head(NULL, "reset", NULL, 0, NULL, NULL) < 0) + die(_("could not discard worktree changes")); + if (read_basic_state(&options)) + exit(1); + goto run_rebase; + } + case ACTION_ABORT: { + struct string_list merge_rr = STRING_LIST_INIT_DUP; + options.action = "abort"; + + rerere_clear(&merge_rr); + string_list_clear(&merge_rr, 1); + + if (read_basic_state(&options)) + exit(1); + if (reset_head(&options.orig_head, "reset", + options.head_name, 0, NULL, NULL) < 0) + die(_("could not move back to %s"), + oid_to_hex(&options.orig_head)); + ret = finish_rebase(&options); + goto cleanup; + } + case ACTION_QUIT: { + strbuf_reset(&buf); + strbuf_addstr(&buf, options.state_dir); + ret = !!remove_dir_recursively(&buf, 0); + if (ret) + die(_("could not remove '%s'"), options.state_dir); + goto cleanup; + } + case ACTION_EDIT_TODO: + options.action = "edit-todo"; + options.dont_finish_rebase = 1; + goto run_rebase; + case ACTION_SHOW_CURRENT_PATCH: + options.action = "show-current-patch"; + options.dont_finish_rebase = 1; + goto run_rebase; + case NO_ACTION: + break; + default: + BUG("action: %d", action); + } + + /* Make sure no rebase is in progress */ + 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"); + + if (committer_date_is_author_date) { + strbuf_addstr(&options.git_am_opt, + " --committer-date-is-author-date"); + options.flags |= REBASE_FORCE; + } + + if (ignore_whitespace) + strbuf_addstr(&options.git_am_opt, " --ignore-whitespace"); + + if (ignore_date) { + strbuf_addstr(&options.git_am_opt, " --ignore-date"); + options.flags |= REBASE_FORCE; + } + + if (options.keep_empty) + imply_interactive(&options, "--keep-empty"); + + if (gpg_sign) { + free(options.gpg_sign_opt); + options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign); + } + + if (opt_c >= 0) + strbuf_addf(&options.git_am_opt, " -C%d", opt_c); + + if (whitespace.nr) { + int i; + + for (i = 0; i < whitespace.nr; i++) { + const char *item = whitespace.items[i].string; + + strbuf_addf(&options.git_am_opt, " --whitespace=%s", + item); + + if ((!strcmp(item, "fix")) || (!strcmp(item, "strip"))) + options.flags |= REBASE_FORCE; + } + } + + if (exec.nr) { + int i; + + imply_interactive(&options, "--exec"); + + strbuf_reset(&buf); + for (i = 0; i < exec.nr; i++) + strbuf_addf(&buf, "exec %s\n", exec.items[i].string); + options.cmd = xstrdup(buf.buf); + } + + if (rebase_merges) { + if (!*rebase_merges) + ; /* default mode; do nothing */ + else if (!strcmp("rebase-cousins", rebase_merges)) + options.rebase_cousins = 1; + else if (strcmp("no-rebase-cousins", rebase_merges)) + die(_("Unknown mode: %s"), rebase_merges); + options.rebase_merges = 1; + imply_interactive(&options, "--rebase-merges"); + } + + if (strategy_options.nr) { + int i; + + if (!options.strategy) + options.strategy = "recursive"; + + strbuf_reset(&buf); + for (i = 0; i < strategy_options.nr; i++) + strbuf_addf(&buf, " --%s", + strategy_options.items[i].string); + options.strategy_opts = xstrdup(buf.buf); + } + + if (options.strategy) { + options.strategy = xstrdup(options.strategy); + switch (options.type) { + case REBASE_AM: + die(_("--strategy requires --merge or --interactive")); + case REBASE_MERGE: + case REBASE_INTERACTIVE: + case REBASE_PRESERVE_MERGES: + /* compatible */ + break; + case REBASE_UNSPECIFIED: + options.type = REBASE_MERGE; + break; + default: + BUG("unhandled rebase type (%d)", options.type); + } + } + + if (options.root && !options.onto_name) + imply_interactive(&options, "--root without --onto"); + + if (isatty(2) && options.flags & REBASE_NO_QUIET) + strbuf_addstr(&options.git_format_patch_opt, " --progress"); + + 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.git_am_opt.len) { + const char *p; + + /* all am options except -q are compatible only with --am */ + strbuf_reset(&buf); + strbuf_addbuf(&buf, &options.git_am_opt); + strbuf_addch(&buf, ' '); + while ((p = strstr(buf.buf, " -q "))) + strbuf_splice(&buf, p - buf.buf, 4, " ", 1); + strbuf_trim(&buf); + + if (is_interactive(&options) && buf.len) + die(_("error: cannot combine interactive options " + "(--interactive, --exec, --rebase-merges, " + "--preserve-merges, --keep-empty, --root + " + "--onto) with am options (%s)"), buf.buf); + if (options.type == REBASE_MERGE && buf.len) + die(_("error: cannot combine merge options (--merge, " + "--strategy, --strategy-option) with am options " + "(%s)"), buf.buf); + } + + if (options.signoff) { + if (options.type == REBASE_PRESERVE_MERGES) + die("cannot combine '--signoff' with " + "'--preserve-merges'"); + strbuf_addstr(&options.git_am_opt, " --signoff"); + options.flags |= REBASE_FORCE; + } + + if (options.type == REBASE_PRESERVE_MERGES) + /* + * Note: incompatibility with --signoff handled in signoff block above + * Note: incompatibility with --interactive is just a strong warning; + * git-rebase.txt caveats with "unless you know what you are doing" + */ + if (options.rebase_merges) + die(_("error: cannot combine '--preserve-merges' with " + "'--rebase-merges'")); + + if (options.rebase_merges) { + if (strategy_options.nr) + die(_("error: cannot combine '--rebase-merges' with " + "'--strategy-option'")); + if (options.strategy) + die(_("error: cannot combine '--rebase-merges' with " + "'--strategy'")); + } + + if (!options.root) { + if (argc < 1) { + struct branch *branch; + + branch = branch_get(NULL); + options.upstream_name = branch_get_upstream(branch, + NULL); + if (!options.upstream_name) + error_on_missing_default_upstream(); + if (fork_point < 0) + fork_point = 1; + } else { + options.upstream_name = argv[0]; + 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); + options.upstream_arg = options.upstream_name; + } else { + if (!options.onto_name) { + if (commit_tree("", 0, the_hash_algo->empty_tree, NULL, + &squash_onto, NULL, NULL) < 0) + die(_("Could not create new root commit")); + options.squash_onto = &squash_onto; + options.onto_name = squash_onto_name = + xstrdup(oid_to_hex(&squash_onto)); + } + options.upstream_name = NULL; + options.upstream = NULL; + if (argc > 1) + usage_with_options(builtin_rebase_usage, + builtin_rebase_options); + options.upstream_arg = "--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, "...")) { + 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 { + 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/<that-branch> or NULL (detached HEAD) + */ + if (argc == 1) { + /* Is it "rebase other branchname" or "rebase other commit"? */ + 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. */ + 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 { + free(options.head_name); + options.head_name = NULL; + branch_name = "HEAD"; + } + if (get_oid("HEAD", &options.orig_head)) + die(_("Could not resolve HEAD to a revision")); + } else + BUG("unexpected number of arguments left to parse"); + + if (fork_point > 0) { + struct commit *head = + lookup_commit_reference(the_repository, + &options.orig_head); + options.restrict_revision = + get_fork_point(options.upstream_name, head); + } + + if (read_index(the_repository->index) < 0) + die(_("could not read index")); + + if (options.autostash) { + struct lock_file lock_file = LOCK_INIT; + int fd; + + fd = hold_locked_index(&lock_file, 0); + refresh_cache(REFRESH_QUIET); + if (0 <= fd) + update_index_if_able(&the_index, &lock_file); + rollback_lock_file(&lock_file); + + if (has_unstaged_changes(1) || has_uncommitted_changes(1)) { + const char *autostash = + state_dir_path("autostash", &options); + struct child_process stash = CHILD_PROCESS_INIT; + struct object_id oid; + struct commit *head = + lookup_commit_reference(the_repository, + &options.orig_head); + + argv_array_pushl(&stash.args, + "stash", "create", "autostash", NULL); + stash.git_cmd = 1; + stash.no_stdin = 1; + strbuf_reset(&buf); + if (capture_command(&stash, &buf, GIT_MAX_HEXSZ)) + die(_("Cannot autostash")); + strbuf_trim_trailing_newline(&buf); + if (get_oid(buf.buf, &oid)) + die(_("Unexpected stash response: '%s'"), + buf.buf); + strbuf_reset(&buf); + strbuf_add_unique_abbrev(&buf, &oid, DEFAULT_ABBREV); + + if (safe_create_leading_directories_const(autostash)) + die(_("Could not create directory for '%s'"), + options.state_dir); + write_file(autostash, "%s", oid_to_hex(&oid)); + printf(_("Created autostash: %s\n"), buf.buf); + if (reset_head(&head->object.oid, "reset --hard", + NULL, 0, NULL, NULL) < 0) + die(_("could not reset --hard")); + printf(_("HEAD is now at %s"), + find_unique_abbrev(&head->object.oid, + DEFAULT_ABBREV)); + strbuf_reset(&buf); + pp_commit_easy(CMIT_FMT_ONELINE, head, &buf); + if (buf.len > 0) + printf(" %s", buf.buf); + putchar('\n'); + + if (discard_index(the_repository->index) < 0 || + 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 && + options.upstream && + !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, + NULL, NULL) < 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); + } + + if (is_interactive(&options)) + goto run_rebase; + + /* 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); + if (reset_head(&options.onto->object.oid, "checkout", NULL, 1, + NULL, msg.buf)) + die(_("Could not detach HEAD")); + strbuf_release(&msg); + + /* + * If the onto is a proper descendant of the tip of the branch, then + * we just fast-forwarded. + */ + strbuf_reset(&msg); + if (!oidcmp(&merge_base, &options.orig_head)) { + printf(_("Fast-forwarded %s to %s. \n"), + branch_name, options.onto_name); + strbuf_addf(&msg, "rebase finished: %s onto %s", + options.head_name ? options.head_name : "detached HEAD", + oid_to_hex(&options.onto->object.oid)); + reset_head(NULL, "Fast-forwarded", options.head_name, 0, + "HEAD", msg.buf); + strbuf_release(&msg); + ret = !!finish_rebase(&options); + goto cleanup; + } + + 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; + +run_rebase: + ret = !!run_specific_rebase(&options); + +cleanup: + strbuf_release(&revisions); + free(options.head_name); + free(options.gpg_sign_opt); + free(options.cmd); + free(squash_onto_name); + return ret; +} diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 7f089be11e..33187bd8e9 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -1025,6 +1025,7 @@ static const char *update(struct command *cmd, struct shallow_info *si) const char *ret; struct object_id *old_oid = &cmd->old_oid; struct object_id *new_oid = &cmd->new_oid; + int do_update_worktree = 0; /* only refs/... are allowed */ if (!starts_with(name, "refs/") || check_refname_format(name + 5, 0)) { @@ -1050,9 +1051,8 @@ static const char *update(struct command *cmd, struct shallow_info *si) refuse_unconfigured_deny(); return "branch is currently checked out"; case DENY_UPDATE_INSTEAD: - ret = update_worktree(new_oid->hash); - if (ret) - return ret; + /* pass -- let other checks intervene first */ + do_update_worktree = 1; break; } } @@ -1117,6 +1117,12 @@ static const char *update(struct command *cmd, struct shallow_info *si) return "hook declined"; } + if (do_update_worktree) { + ret = update_worktree(new_oid->hash); + if (ret) + return ret; + } + if (is_null_oid(new_oid)) { struct strbuf err = STRBUF_INIT; if (!parse_object(the_repository, old_oid)) { diff --git a/builtin/repack.c b/builtin/repack.c index c6a7943d5c..0223f2880c 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -235,8 +235,8 @@ static void repack_promisor_objects(const struct pack_objects_args *args, while (strbuf_getline_lf(&line, out) != EOF) { char *promisor_name; int fd; - if (line.len != 40) - die("repack: Expecting 40 character sha1 lines only from pack-objects."); + if (line.len != the_hash_algo->hexsz) + die("repack: Expecting full hex object ID lines only from pack-objects."); string_list_append(names, line.buf); /* @@ -407,8 +407,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) out = xfdopen(cmd.out, "r"); while (strbuf_getline_lf(&line, out) != EOF) { - if (line.len != 40) - die("repack: Expecting 40 character sha1 lines only from pack-objects."); + if (line.len != the_hash_algo->hexsz) + die("repack: Expecting full hex object ID lines only from pack-objects."); string_list_append(&names, line.buf); } fclose(out); @@ -535,14 +535,15 @@ int cmd_repack(int argc, const char **argv, const char *prefix) reprepare_packed_git(the_repository); if (delete_redundant) { + const int hexsz = the_hash_algo->hexsz; int opts = 0; string_list_sort(&names); for_each_string_list_item(item, &existing_packs) { char *sha1; size_t len = strlen(item->string); - if (len < 40) + if (len < hexsz) continue; - sha1 = item->string + len - 40; + sha1 = item->string + len - hexsz; if (!string_list_has_string(&names, sha1)) remove_redundant_pack(packdir, item->string); } diff --git a/builtin/rev-list.c b/builtin/rev-list.c index cc1b70522f..5064d08e1b 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -6,6 +6,7 @@ #include "list-objects.h" #include "list-objects-filter.h" #include "list-objects-filter-options.h" +#include "object.h" #include "object-store.h" #include "pack.h" #include "pack-bitmap.h" @@ -209,7 +210,8 @@ static inline void finish_object__ma(struct object *obj) */ switch (arg_missing_action) { case MA_ERROR: - die("missing blob object '%s'", oid_to_hex(&obj->oid)); + die("missing %s object '%s'", + type_name(obj->type), oid_to_hex(&obj->oid)); return; case MA_ALLOW_ANY: @@ -222,8 +224,8 @@ static inline void finish_object__ma(struct object *obj) case MA_ALLOW_PROMISOR: if (is_promisor_object(&obj->oid)) return; - die("unexpected missing blob object '%s'", - oid_to_hex(&obj->oid)); + die("unexpected missing %s object '%s'", + type_name(obj->type), oid_to_hex(&obj->oid)); return; default: @@ -235,7 +237,7 @@ static inline void finish_object__ma(struct object *obj) static int finish_object(struct object *obj, const char *name, void *cb_data) { struct rev_list_info *info = cb_data; - if (obj->type == OBJ_BLOB && !has_object_file(&obj->oid)) { + if (!has_object_file(&obj->oid)) { finish_object__ma(obj); return 1; } @@ -373,6 +375,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) repo_init_revisions(the_repository, &revs, prefix); revs.abbrev = DEFAULT_ABBREV; revs.commit_format = CMIT_FMT_UNSPECIFIED; + revs.do_not_die_on_missing_tree = 1; /* * Scan the argument list before invoking setup_revisions(), so that we diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 80474c3ff5..8e1db55e13 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -1457,7 +1457,6 @@ static void determine_submodule_update_strategy(struct repository *r, key = xstrfmt("submodule.%s.update", sub->name); if (update) { - trace_printf("parsing update"); if (parse_submodule_update_strategy(update, out) < 0) die(_("Invalid update mode '%s' for submodule path '%s'"), update, path); @@ -1466,7 +1465,6 @@ static void determine_submodule_update_strategy(struct repository *r, die(_("Invalid update mode '%s' configured for submodule path '%s'"), val, path); } else if (sub->update_strategy.type != SM_UPDATE_UNSPECIFIED) { - trace_printf("loaded thing"); out->type = sub->update_strategy.type; out->command = sub->update_strategy.command; } else |