diff --git a/.gitignore b/.gitignore index d9773e1120585f..acfe057752b11b 100644 --- a/.gitignore +++ b/.gitignore @@ -78,9 +78,6 @@ /git-init-db /git-interpret-trailers /git-instaweb -/git-legacy-rebase -/git-legacy-rebase--interactive -/git-legacy-stash /git-log /git-ls-files /git-ls-remote @@ -121,7 +118,7 @@ /git-read-tree /git-rebase /git-rebase--am -/git-rebase--common +/git-rebase--helper /git-rebase--interactive /git-rebase--merge /git-rebase--preserve-merges diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index e31ea7d3037d55..7ef8c4791177b2 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git stash' list [] -'git stash' show [] [] +'git stash' show [] 'git stash' drop [-q|--quiet] [] 'git stash' ( pop | apply ) [--index] [-q|--quiet] [] 'git stash' branch [] @@ -106,7 +106,7 @@ stash@{1}: On master: 9cc0589... Add git-stash The command takes options applicable to the 'git log' command to control what is shown and how. See linkgit:git-log[1]. -show [] []:: +show []:: Show the changes recorded in the stash entry as a diff between the stashed contents and the commit back when the stash entry was first diff --git a/Makefile b/Makefile index 74319a98b88c05..3795d8379388a0 100644 --- a/Makefile +++ b/Makefile @@ -614,18 +614,17 @@ 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-legacy-rebase.sh -SCRIPT_SH += git-legacy-stash.sh +SCRIPT_SH += git-rebase.sh SCRIPT_SH += git-remote-testgit.sh SCRIPT_SH += git-request-pull.sh +SCRIPT_SH += git-stash.sh SCRIPT_SH += git-submodule.sh SCRIPT_SH += git-web--browse.sh -SCRIPT_LIB += git-legacy-rebase--interactive 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 SCRIPT_LIB += git-sh-setup @@ -941,7 +940,6 @@ LIB_OBJS += quote.o LIB_OBJS += range-diff.o LIB_OBJS += reachable.o LIB_OBJS += read-cache.o -LIB_OBJS += rebase-interactive.o LIB_OBJS += reflog-walk.o LIB_OBJS += refs.o LIB_OBJS += refs/files-backend.o @@ -1093,8 +1091,7 @@ BUILTIN_OBJS += builtin/pull.o BUILTIN_OBJS += builtin/push.o BUILTIN_OBJS += builtin/range-diff.o BUILTIN_OBJS += builtin/read-tree.o -BUILTIN_OBJS += builtin/rebase.o -BUILTIN_OBJS += builtin/rebase--interactive.o +BUILTIN_OBJS += builtin/rebase--helper.o BUILTIN_OBJS += builtin/receive-pack.o BUILTIN_OBJS += builtin/reflog.o BUILTIN_OBJS += builtin/remote.o @@ -1114,7 +1111,6 @@ BUILTIN_OBJS += builtin/shortlog.o BUILTIN_OBJS += builtin/show-branch.o BUILTIN_OBJS += builtin/show-index.o BUILTIN_OBJS += builtin/show-ref.o -BUILTIN_OBJS += builtin/stash.o BUILTIN_OBJS += builtin/stripspace.o BUILTIN_OBJS += builtin/submodule--helper.o BUILTIN_OBJS += builtin/symbolic-ref.o @@ -2434,6 +2430,7 @@ XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --language=Perl \ LOCALIZED_C = $(C_OBJ:o=c) $(LIB_H) $(GENERATED_H) LOCALIZED_SH = $(SCRIPT_SH) LOCALIZED_SH += git-parse-remote.sh +LOCALIZED_SH += git-rebase--interactive.sh LOCALIZED_SH += git-rebase--preserve-merges.sh LOCALIZED_SH += git-sh-setup.sh LOCALIZED_PERL = $(SCRIPT_PERL) diff --git a/builtin.h b/builtin.h index 1eb16d1ebbf558..620e3102a9803c 100644 --- a/builtin.h +++ b/builtin.h @@ -204,8 +204,7 @@ 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_range_diff(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__interactive(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); extern int cmd_remote(int argc, const char **argv, const char *prefix); @@ -225,7 +224,6 @@ extern int cmd_show(int argc, const char **argv, const char *prefix); extern int cmd_show_branch(int argc, const char **argv, const char *prefix); extern int cmd_show_index(int argc, const char **argv, const char *prefix); extern int cmd_status(int argc, const char **argv, const char *prefix); -extern int cmd_stash(int argc, const char **argv, const char *prefix); extern int cmd_stripspace(int argc, const char **argv, const char *prefix); extern int cmd_submodule__helper(int argc, const char **argv, const char *prefix); extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix); diff --git a/builtin/merge-base.c b/builtin/merge-base.c index 790ceaeed68fae..08d91b1f0c0172 100644 --- a/builtin/merge-base.c +++ b/builtin/merge-base.c @@ -110,12 +110,54 @@ 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: @@ -131,14 +173,41 @@ 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); - fork_point = get_fork_point(refname, derived); + if (!revs.nr && !get_oid(refname, &oid)) + add_one_commit(&oid, &revs); - if (!fork_point) - return 1; + for (i = 0; i < revs.nr; i++) + revs.commit[i]->object.flags &= ~TMP_MARK; - printf("%s\n", oid_to_hex(&fork_point->object.oid)); - return 0; + 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)); + +cleanup_return: + free_commit_list(bases); + return ret; } int cmd_merge_base(int argc, const char **argv, const char *prefix) diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c new file mode 100644 index 00000000000000..f7c2a5fdc815a8 --- /dev/null +++ b/builtin/rebase--helper.c @@ -0,0 +1,88 @@ +#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 []"), + 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 deleted file mode 100644 index 551faba558de3e..00000000000000 --- a/builtin/rebase--interactive.c +++ /dev/null @@ -1,286 +0,0 @@ -#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 []"), - 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, - MAKE_SCRIPT, SKIP_UNNECESSARY_PICKS, - } 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")), - OPT_STRING('S', "gpg-sign", &opts.gpg_sign, N_("gpg-sign"), - N_("GPG-sign commits")), - 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_CMDMODE(0, "make-script", &command, - N_("make rebase script"), MAKE_SCRIPT), - OPT_CMDMODE(0, "skip-unnecessary-picks", &command, - N_("skip unnecessary picks"), SKIP_UNNECESSARY_PICKS), - 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; - case MAKE_SCRIPT: - ret = sequencer_make_script(stdout, argc, argv, flags); - break; - case SKIP_UNNECESSARY_PICKS: { - struct object_id oid; - - ret = skip_unnecessary_picks(&oid); - if (!ret) - printf("%s\n", oid_to_hex(&oid)); - break; - } - default: - BUG("invalid command '%d'", command); - } - - return !!ret; -} diff --git a/builtin/rebase.c b/builtin/rebase.c deleted file mode 100644 index a28bfbd62f58d1..00000000000000 --- a/builtin/rebase.c +++ /dev/null @@ -1,1721 +0,0 @@ -/* - * "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 "rerere.h" - -static char const * const builtin_rebase_usage[] = { - N_("git rebase [-i] [options] [--exec ] [--onto ] " - "[] []"), - N_("git rebase [-i] [options] [--exec ] [--onto ] " - "--root []"), - 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 0; - } - - 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 write_basic_state(struct rebase_options *opts) -{ - write_file(state_dir_path("head-name", opts), "%s", - opts->head_name ? opts->head_name : "detached HEAD"); - write_file(state_dir_path("onto", opts), "%s", - opts->onto ? oid_to_hex(&opts->onto->object.oid) : ""); - write_file(state_dir_path("orig-head", opts), "%s", - oid_to_hex(&opts->orig_head)); - write_file(state_dir_path("quiet", opts), "%s", - opts->flags & REBASE_NO_QUIET ? "" : "t"); - if (opts->flags & REBASE_VERBOSE) - write_file(state_dir_path("verbose", opts), ""); - if (opts->strategy) - write_file(state_dir_path("strategy", opts), "%s", - opts->strategy); - if (opts->strategy_opts) - write_file(state_dir_path("strategy_opts", opts), "%s", - opts->strategy_opts); - if (opts->allow_rerere_autoupdate >= 0) - write_file(state_dir_path("allow_rerere_autoupdate", opts), - "-%s-rerere-autoupdate", - opts->allow_rerere_autoupdate ? "" : "-no"); - if (opts->gpg_sign_opt) - write_file(state_dir_path("gpg_sign_opt", opts), "%s", - opts->gpg_sign_opt); - if (opts->signoff) - write_file(state_dir_path("strategy", opts), "--signoff"); - - 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(state_dir_path("autostash", opts), &autostash)) - return error(_("Could not read '%s'"), path); - 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 \", 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 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); - -static int move_to_original_branch(struct rebase_options *opts) -{ - struct strbuf buf = STRBUF_INIT; - int ret; - - if (opts->head_name && opts->onto) - strbuf_addf(&buf, "rebase finished: %s onto %s", - opts->head_name, - oid_to_hex(&opts->onto->object.oid)); - ret = reset_head(NULL, "checkout", opts->head_name, 0, - "HEAD", buf.buf); - - strbuf_release(&buf); - return ret; -} - -static int run_am(struct rebase_options *opts) -{ - struct child_process am = CHILD_PROCESS_INIT; - struct child_process format_patch = CHILD_PROCESS_INIT; - struct strbuf revisions = STRBUF_INIT; - int status; - char *rebased_patches; - - am.git_cmd = 1; - argv_array_push(&am.args, "am"); - - if (opts->action && !strcmp("continue", opts->action)) { - argv_array_push(&am.args, "--resolved"); - argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg); - if (opts->gpg_sign_opt) - argv_array_push(&am.args, opts->gpg_sign_opt); - status = run_command(&am); - if (status) - return status; - - discard_cache(); - return move_to_original_branch(opts); - } - if (opts->action && !strcmp("skip", opts->action)) { - argv_array_push(&am.args, "--skip"); - argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg); - status = run_command(&am); - if (status) - return status; - - discard_cache(); - return move_to_original_branch(opts); - } - if (opts->action && !strcmp("show-current-patch", opts->action)) { - argv_array_push(&am.args, "--show-current-patch"); - return run_command(&am); - } - - strbuf_addf(&revisions, "%s...%s", - oid_to_hex(opts->root ? - /* this is now equivalent to ! -z "$upstream" */ - &opts->onto->object.oid : - &opts->upstream->object.oid), - oid_to_hex(&opts->orig_head)); - - rebased_patches = xstrdup(git_path("rebased-patches")); - format_patch.out = open(rebased_patches, O_WRONLY | O_CREAT, 0666); - if (format_patch.out < 0) { - status = error_errno(_("could not write '%s'"), - rebased_patches); - free(rebased_patches); - argv_array_clear(&am.args); - return status; - } - - format_patch.git_cmd = 1; - argv_array_pushl(&format_patch.args, "format-patch", "-k", "--stdout", - "--full-index", "--cherry-pick", "--right-only", - "--src-prefix=a/", "--dst-prefix=b/", "--no-renames", - "--no-cover-letter", "--pretty=mboxrd", NULL); - if (opts->git_format_patch_opt.len) - argv_array_split(&format_patch.args, - opts->git_format_patch_opt.buf); - argv_array_push(&format_patch.args, revisions.buf); - if (opts->restrict_revision) - argv_array_pushf(&format_patch.args, "^%s", - oid_to_hex(&opts->restrict_revision->object.oid)); - - status = run_command(&format_patch); - if (status) { - unlink(rebased_patches); - free(rebased_patches); - argv_array_clear(&am.args); - - reset_head(&opts->orig_head, "checkout", opts->head_name, 0, - "HEAD", NULL); - error(_("\ngit encountered an error while preparing the " - "patches to replay\n" - "these revisions:\n" - "\n %s\n\n" - "As a result, git cannot rebase them."), - opts->revisions); - - strbuf_release(&revisions); - return status; - } - strbuf_release(&revisions); - - am.in = open(rebased_patches, O_RDONLY); - if (am.in < 0) { - status = error_errno(_("could not read '%s'"), - rebased_patches); - free(rebased_patches); - argv_array_clear(&am.args); - return status; - } - - argv_array_split(&am.args, opts->git_am_opt.buf); - argv_array_push(&am.args, "--rebasing"); - argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg); - argv_array_push(&am.args, "--patch-format=mboxrd"); - if (opts->allow_rerere_autoupdate > 0) - argv_array_push(&am.args, "--rerere-autoupdate"); - else if (opts->allow_rerere_autoupdate == 0) - argv_array_push(&am.args, "--no-rerere-autoupdate"); - if (opts->gpg_sign_opt) - argv_array_push(&am.args, opts->gpg_sign_opt); - status = run_command(&am); - unlink(rebased_patches); - free(rebased_patches); - - if (!status) { - discard_cache(); - return move_to_original_branch(opts); - } - - if (is_directory(opts->state_dir)) - write_basic_state(opts); - - return status; -} - -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; - } - - if (opts->type == REBASE_AM) { - status = run_am(opts); - 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_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); -finished_rebase: - 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(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 = !oidcmp(merge_base, &onto->object.oid); - } else { - oidcpy(merge_base, &null_oid); - res = 0; - } - free_commit_list(merge_bases); - return res && is_linear_history(onto, head); -} - -/* -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 ''\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 = _(""); - - printf(_("If you wish to set tracking information for this " - "branch you can do so with:\n" - "\n" - " git branch --set-upstream-to=%s/ %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")), - OPT_STRING('S', "gpg-sign", &gpg_sign, - N_("gpg-sign?"), N_("GPG-sign commits")), - OPT_STRING_LIST(0, "whitespace", &whitespace, - N_("whitespace"), N_("passed to 'git apply'")), - OPT_SET_INT('C', 0, &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/ 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(0) || has_uncommitted_changes(0)) { - 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", buf.buf); - 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/stash.c b/builtin/stash.c deleted file mode 100644 index ed669dae0fb5ad..00000000000000 --- a/builtin/stash.c +++ /dev/null @@ -1,1598 +0,0 @@ -#include "builtin.h" -#include "config.h" -#include "parse-options.h" -#include "refs.h" -#include "lockfile.h" -#include "cache-tree.h" -#include "unpack-trees.h" -#include "merge-recursive.h" -#include "argv-array.h" -#include "run-command.h" -#include "dir.h" -#include "rerere.h" -#include "revision.h" -#include "log-tree.h" -#include "diffcore.h" -#include "exec-cmd.h" - -static const char * const git_stash_usage[] = { - N_("git stash list []"), - N_("git stash show [] []"), - N_("git stash drop [-q|--quiet] []"), - N_("git stash ( pop | apply ) [--index] [-q|--quiet] []"), - N_("git stash branch []"), - N_("git stash clear"), - N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" - " [-u|--include-untracked] [-a|--all] [-m|--message ]\n" - " [--] [...]]"), - N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" - " [-u|--include-untracked] [-a|--all] []"), - NULL -}; - -static const char * const git_stash_list_usage[] = { - N_("git stash list []"), - NULL -}; - -static const char * const git_stash_show_usage[] = { - N_("git stash show [] []"), - NULL -}; - -static const char * const git_stash_drop_usage[] = { - N_("git stash drop [-q|--quiet] []"), - NULL -}; - -static const char * const git_stash_pop_usage[] = { - N_("git stash pop [--index] [-q|--quiet] []"), - NULL -}; - -static const char * const git_stash_apply_usage[] = { - N_("git stash apply [--index] [-q|--quiet] []"), - NULL -}; - -static const char * const git_stash_branch_usage[] = { - N_("git stash branch []"), - NULL -}; - -static const char * const git_stash_clear_usage[] = { - N_("git stash clear"), - NULL -}; - -static const char * const git_stash_store_usage[] = { - N_("git stash store [-m|--message ] [-q|--quiet] "), - NULL -}; - -static const char * const git_stash_create_usage[] = { - N_("git stash create []"), - NULL -}; - -static const char * const git_stash_push_usage[] = { - N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" - " [-u|--include-untracked] [-a|--all] [-m|--message ]\n" - " [--] [...]]"), - NULL -}; - -static const char * const git_stash_save_usage[] = { - N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" - " [-u|--include-untracked] [-a|--all] []"), - NULL -}; - -static const char *ref_stash = "refs/stash"; -static int quiet; -static struct strbuf stash_index_path = STRBUF_INIT; - -/* - * w_commit is set to the commit containing the working tree - * b_commit is set to the base commit - * i_commit is set to the commit containing the index tree - * u_commit is set to the commit containing the untracked files tree - * w_tree is set to the working tree - * b_tree is set to the base tree - * i_tree is set to the index tree - * u_tree is set to the untracked files tree - */ - -struct stash_info { - struct object_id w_commit; - struct object_id b_commit; - struct object_id i_commit; - struct object_id u_commit; - struct object_id w_tree; - struct object_id b_tree; - struct object_id i_tree; - struct object_id u_tree; - struct strbuf revision; - int is_stash_ref; - int has_u; -}; - -static void free_stash_info(struct stash_info *info) -{ - strbuf_release(&info->revision); -} - -static void assert_stash_like(struct stash_info *info, const char *revision) -{ - if (get_oidf(&info->b_commit, "%s^1", revision) || - get_oidf(&info->w_tree, "%s:", revision) || - get_oidf(&info->b_tree, "%s^1:", revision) || - get_oidf(&info->i_tree, "%s^2:", revision)) { - free_stash_info(info); - error(_("'%s' is not a stash-like commit"), revision); - exit(128); - } -} - -static int get_stash_info(struct stash_info *info, int argc, const char **argv) -{ - struct strbuf symbolic = STRBUF_INIT; - int ret; - const char *revision; - const char *commit = NULL; - char *end_of_rev; - char *expanded_ref; - struct object_id dummy; - - if (argc > 1) { - int i; - struct strbuf refs_msg = STRBUF_INIT; - for (i = 0; i < argc; ++i) - strbuf_addf(&refs_msg, " '%s'", argv[i]); - - fprintf_ln(stderr, _("Too many revisions specified:%s"), - refs_msg.buf); - strbuf_release(&refs_msg); - - return -1; - } - - if (argc == 1) - commit = argv[0]; - - strbuf_init(&info->revision, 0); - if (!commit) { - if (!ref_exists(ref_stash)) { - free_stash_info(info); - fprintf_ln(stderr, _("No stash entries found.")); - return -1; - } - - strbuf_addf(&info->revision, "%s@{0}", ref_stash); - } else if (strspn(commit, "0123456789") == strlen(commit)) { - strbuf_addf(&info->revision, "%s@{%s}", ref_stash, commit); - } else { - strbuf_addstr(&info->revision, commit); - } - - revision = info->revision.buf; - - if (get_oid(revision, &info->w_commit)) { - error(_("%s is not a valid reference"), revision); - free_stash_info(info); - return -1; - } - - assert_stash_like(info, revision); - - info->has_u = !get_oidf(&info->u_tree, "%s^3:", revision); - - end_of_rev = strchrnul(revision, '@'); - strbuf_add(&symbolic, revision, end_of_rev - revision); - - ret = dwim_ref(symbolic.buf, symbolic.len, &dummy, &expanded_ref); - strbuf_release(&symbolic); - switch (ret) { - case 0: /* Not found, but valid ref */ - info->is_stash_ref = 0; - break; - case 1: - info->is_stash_ref = !strcmp(expanded_ref, ref_stash); - break; - default: /* Invalid or ambiguous */ - free_stash_info(info); - } - - free(expanded_ref); - return !(ret == 0 || ret == 1); -} - -static int do_clear_stash(void) -{ - struct object_id obj; - if (get_oid(ref_stash, &obj)) - return 0; - - return delete_ref(NULL, ref_stash, &obj, 0); -} - -static int clear_stash(int argc, const char **argv, const char *prefix) -{ - struct option options[] = { - OPT_END() - }; - - argc = parse_options(argc, argv, prefix, options, - git_stash_clear_usage, - PARSE_OPT_STOP_AT_NON_OPTION); - - if (argc) - return error(_("git stash clear with parameters is unimplemented")); - - return do_clear_stash(); -} - -static int reset_tree(struct object_id *i_tree, int update, int reset) -{ - struct unpack_trees_options opts; - int nr_trees = 1; - struct tree_desc t[MAX_UNPACK_TREES]; - struct tree *tree; - struct lock_file lock_file = LOCK_INIT; - - read_cache_preload(NULL); - if (refresh_cache(REFRESH_QUIET)) - return -1; - - hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); - - memset(&opts, 0, sizeof(opts)); - - tree = parse_tree_indirect(i_tree); - if (parse_tree(tree)) - return -1; - - init_tree_desc(t, tree->buffer, tree->size); - - opts.head_idx = 1; - opts.src_index = &the_index; - opts.dst_index = &the_index; - opts.merge = 1; - opts.reset = reset; - opts.update = update; - opts.fn = oneway_merge; - - if (unpack_trees(nr_trees, t, &opts)) - return -1; - - if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) - return error(_("unable to write new index file")); - - return 0; -} - -static int diff_tree_binary(struct strbuf *out, struct object_id *w_commit) -{ - struct child_process cp = CHILD_PROCESS_INIT; - const char *w_commit_hex = oid_to_hex(w_commit); - - /* - * Diff-tree would not be very hard to replace with a native function, - * however it should be done together with apply_cached. - */ - cp.git_cmd = 1; - argv_array_pushl(&cp.args, "diff-tree", "--binary", NULL); - argv_array_pushf(&cp.args, "%s^2^..%s^2", w_commit_hex, w_commit_hex); - - return pipe_command(&cp, NULL, 0, out, 0, NULL, 0); -} - -static int apply_cached(struct strbuf *out) -{ - struct child_process cp = CHILD_PROCESS_INIT; - - /* - * Apply currently only reads either from stdin or a file, thus - * apply_all_patches would have to be updated to optionally take a - * buffer. - */ - cp.git_cmd = 1; - argv_array_pushl(&cp.args, "apply", "--cached", NULL); - return pipe_command(&cp, out->buf, out->len, NULL, 0, NULL, 0); -} - -static int reset_head(void) -{ - struct child_process cp = CHILD_PROCESS_INIT; - - /* - * Reset is overall quite simple, however there is no current public - * API for resetting. - */ - cp.git_cmd = 1; - argv_array_push(&cp.args, "reset"); - - return run_command(&cp); -} - -static void add_diff_to_buf(struct diff_queue_struct *q, - struct diff_options *options, - void *data) -{ - int i; - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - strbuf_addstr(data, p->one->path); - strbuf_addch(data, 0); - } -} - -static int get_newly_staged(struct strbuf *out, struct object_id *c_tree) -{ - struct child_process cp = CHILD_PROCESS_INIT; - const char *c_tree_hex = oid_to_hex(c_tree); - - /* - * diff-index is very similar to diff-tree above, and should be - * converted together with update_index. - */ - cp.git_cmd = 1; - argv_array_pushl(&cp.args, "diff-index", "--cached", "--name-only", - "--diff-filter=A", NULL); - argv_array_push(&cp.args, c_tree_hex); - return pipe_command(&cp, NULL, 0, out, 0, NULL, 0); -} - -static int update_index(struct strbuf *out) -{ - struct child_process cp = CHILD_PROCESS_INIT; - - /* - * Update-index is very complicated and may need to have a public - * function exposed in order to remove this forking. - */ - cp.git_cmd = 1; - argv_array_pushl(&cp.args, "update-index", "--add", "--stdin", NULL); - return pipe_command(&cp, out->buf, out->len, NULL, 0, NULL, 0); -} - -static int restore_untracked(struct object_id *u_tree) -{ - struct child_process cp = CHILD_PROCESS_INIT; - int res; - - /* - * We need to run restore files from a given index, but without - * affecting the current index, so we use GIT_INDEX_FILE with - * run_command to fork processes that will not interfere. - */ - cp.git_cmd = 1; - argv_array_push(&cp.args, "read-tree"); - argv_array_push(&cp.args, oid_to_hex(u_tree)); - argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", - stash_index_path.buf); - if (run_command(&cp)) { - remove_path(stash_index_path.buf); - return -1; - } - - child_process_init(&cp); - cp.git_cmd = 1; - argv_array_pushl(&cp.args, "checkout-index", "--all", NULL); - argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", - stash_index_path.buf); - - res = run_command(&cp); - remove_path(stash_index_path.buf); - return res; -} - -static int do_apply_stash(const char *prefix, struct stash_info *info, - int index) -{ - struct merge_options o; - struct object_id c_tree; - struct object_id index_tree; - const struct object_id *bases[1]; - struct commit *result; - int ret; - int has_index = index; - - read_cache_preload(NULL); - if (refresh_cache(REFRESH_QUIET)) - return -1; - - if (write_cache_as_tree(&c_tree, 0, NULL) || reset_tree(&c_tree, 0, 0)) - return error(_("Cannot apply a stash in the middle of a merge")); - - if (index) { - if (!oidcmp(&info->b_tree, &info->i_tree) || !oidcmp(&c_tree, - &info->i_tree)) { - has_index = 0; - } else { - struct strbuf out = STRBUF_INIT; - - if (diff_tree_binary(&out, &info->w_commit)) { - strbuf_release(&out); - return -1; - } - - ret = apply_cached(&out); - strbuf_release(&out); - if (ret) - return -1; - - discard_cache(); - read_cache(); - if (write_cache_as_tree(&index_tree, 0, NULL)) - return -1; - - reset_head(); - } - } - - if (info->has_u && restore_untracked(&info->u_tree)) - return error(_("Could not restore untracked files from stash")); - - init_merge_options(&o); - - o.branch1 = "Updated upstream"; - o.branch2 = "Stashed changes"; - - if (!oidcmp(&info->b_tree, &c_tree)) - o.branch1 = "Version stash was based on"; - - if (quiet) - o.verbosity = 0; - - if (o.verbosity >= 3) - printf_ln(_("Merging %s with %s"), o.branch1, o.branch2); - - bases[0] = &info->b_tree; - - ret = merge_recursive_generic(&o, &c_tree, &info->w_tree, 1, bases, - &result); - if (ret) { - rerere(0); - - if (index) - fprintf_ln(stderr, _("Index was not unstashed.")); - - return ret; - } - - if (has_index) { - if (reset_tree(&index_tree, 0, 0)) - return -1; - } else { - struct strbuf out = STRBUF_INIT; - - if (get_newly_staged(&out, &c_tree)) { - strbuf_release(&out); - return -1; - } - - if (reset_tree(&c_tree, 0, 1)) { - strbuf_release(&out); - return -1; - } - - ret = update_index(&out); - strbuf_release(&out); - if (ret) - return -1; - - discard_cache(); - } - - if (quiet) { - if (refresh_cache(REFRESH_QUIET)) - warning("could not refresh index"); - } else { - struct child_process cp = CHILD_PROCESS_INIT; - - /* - * Status is quite simple and could be replaced with calls to - * wt_status in the future, but it adds complexities which may - * require more tests. - */ - cp.git_cmd = 1; - cp.dir = prefix; - argv_array_push(&cp.args, "status"); - run_command(&cp); - } - - return 0; -} - -static int apply_stash(int argc, const char **argv, const char *prefix) -{ - int index = 0; - struct stash_info info; - int ret; - struct option options[] = { - OPT__QUIET(&quiet, N_("be quiet, only report errors")), - OPT_BOOL(0, "index", &index, - N_("attempt to recreate the index")), - OPT_END() - }; - - argc = parse_options(argc, argv, prefix, options, - git_stash_apply_usage, 0); - - if (get_stash_info(&info, argc, argv)) - return -1; - - ret = do_apply_stash(prefix, &info, index); - free_stash_info(&info); - return ret; -} - -static int do_drop_stash(const char *prefix, struct stash_info *info) -{ - struct child_process cp_reflog = CHILD_PROCESS_INIT; - struct child_process cp = CHILD_PROCESS_INIT; - int ret; - - /* - * reflog does not provide a simple function for deleting refs. One will - * need to be added to avoid implementing too much reflog code here - */ - - cp_reflog.git_cmd = 1; - argv_array_pushl(&cp_reflog.args, "reflog", "delete", "--updateref", - "--rewrite", NULL); - argv_array_push(&cp_reflog.args, info->revision.buf); - ret = run_command(&cp_reflog); - if (!ret) { - if (!quiet) - printf_ln(_("Dropped %s (%s)"), info->revision.buf, - oid_to_hex(&info->w_commit)); - } else { - return error(_("%s: Could not drop stash entry"), - info->revision.buf); - } - - /* - * This could easily be replaced by get_oid, but currently it will throw - * a fatal error when a reflog is empty, which we can not recover from. - */ - cp.git_cmd = 1; - /* Even though --quiet is specified, rev-parse still outputs the hash */ - cp.no_stdout = 1; - argv_array_pushl(&cp.args, "rev-parse", "--verify", "--quiet", NULL); - argv_array_pushf(&cp.args, "%s@{0}", ref_stash); - ret = run_command(&cp); - - /* do_clear_stash if we just dropped the last stash entry */ - if (ret) - do_clear_stash(); - - return 0; -} - -static void assert_stash_ref(struct stash_info *info) -{ - if (!info->is_stash_ref) { - free_stash_info(info); - error(_("'%s' is not a stash reference"), info->revision.buf); - exit(128); - } -} - -static int drop_stash(int argc, const char **argv, const char *prefix) -{ - struct stash_info info; - int ret; - struct option options[] = { - OPT__QUIET(&quiet, N_("be quiet, only report errors")), - OPT_END() - }; - - argc = parse_options(argc, argv, prefix, options, - git_stash_drop_usage, 0); - - if (get_stash_info(&info, argc, argv)) - return -1; - - assert_stash_ref(&info); - - ret = do_drop_stash(prefix, &info); - free_stash_info(&info); - return ret; -} - -static int pop_stash(int argc, const char **argv, const char *prefix) -{ - int index = 0, ret; - struct stash_info info; - struct option options[] = { - OPT__QUIET(&quiet, N_("be quiet, only report errors")), - OPT_BOOL(0, "index", &index, - N_("attempt to recreate the index")), - OPT_END() - }; - - argc = parse_options(argc, argv, prefix, options, - git_stash_pop_usage, 0); - - if (get_stash_info(&info, argc, argv)) - return -1; - - assert_stash_ref(&info); - if ((ret = do_apply_stash(prefix, &info, index))) - printf_ln(_("The stash entry is kept in case you need it again.")); - else - ret = do_drop_stash(prefix, &info); - - free_stash_info(&info); - return ret; -} - -static int branch_stash(int argc, const char **argv, const char *prefix) -{ - const char *branch = NULL; - int ret; - struct child_process cp = CHILD_PROCESS_INIT; - struct stash_info info; - struct option options[] = { - OPT_END() - }; - - argc = parse_options(argc, argv, prefix, options, - git_stash_branch_usage, 0); - - if (!argc) - return error(_("No branch name specified")); - - branch = argv[0]; - - if (get_stash_info(&info, argc - 1, argv + 1)) - return -1; - - cp.git_cmd = 1; - argv_array_pushl(&cp.args, "checkout", "-b", NULL); - argv_array_push(&cp.args, branch); - argv_array_push(&cp.args, oid_to_hex(&info.b_commit)); - ret = run_command(&cp); - if (!ret) - ret = do_apply_stash(prefix, &info, 1); - if (!ret && info.is_stash_ref) - ret = do_drop_stash(prefix, &info); - - free_stash_info(&info); - - return ret; -} - -static int list_stash(int argc, const char **argv, const char *prefix) -{ - struct child_process cp = CHILD_PROCESS_INIT; - struct option options[] = { - OPT_END() - }; - - argc = parse_options(argc, argv, prefix, options, - git_stash_list_usage, - PARSE_OPT_KEEP_UNKNOWN); - - if (!ref_exists(ref_stash)) - return 0; - - cp.git_cmd = 1; - argv_array_pushl(&cp.args, "log", "--format=%gd: %gs", "-g", - "--first-parent", "-m", NULL); - argv_array_pushv(&cp.args, argv); - argv_array_push(&cp.args, ref_stash); - argv_array_push(&cp.args, "--"); - return run_command(&cp); -} - -static int show_stat = 1; -static int show_patch; - -static int git_stash_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, "stash.showstat")) { - show_stat = git_config_bool(var, value); - return 0; - } - if (!strcmp(var, "stash.showpatch")) { - show_patch = git_config_bool(var, value); - return 0; - } - return git_default_config(var, value, cb); -} - -static int show_stash(int argc, const char **argv, const char *prefix) -{ - int i; - int opts = 0; - int ret = 0; - struct stash_info info; - struct rev_info rev; - struct argv_array stash_args = ARGV_ARRAY_INIT; - struct option options[] = { - OPT_END() - }; - - init_diff_ui_defaults(); - git_config(git_diff_ui_config, NULL); - init_revisions(&rev, prefix); - - for (i = 1; i < argc; ++i) { - if (argv[i][0] != '-') - argv_array_push(&stash_args, argv[i]); - else - opts++; - } - - ret = get_stash_info(&info, stash_args.argc, stash_args.argv); - argv_array_clear(&stash_args); - if (ret) - return -1; - - /* - * The config settings are applied only if there are not passed - * any options. - */ - if (!opts) { - git_config(git_stash_config, NULL); - if (show_stat) - rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT; - - if (show_patch) - rev.diffopt.output_format |= DIFF_FORMAT_PATCH; - - if (!show_stat && !show_patch) { - free_stash_info(&info); - return 0; - } - } - - argc = setup_revisions(argc, argv, &rev, NULL); - if (argc > 1) { - free_stash_info(&info); - usage_with_options(git_stash_show_usage, options); - } - - rev.diffopt.flags.recursive = 1; - setup_diff_pager(&rev.diffopt); - diff_tree_oid(&info.b_commit, &info.w_commit, "", &rev.diffopt); - log_tree_diff_flush(&rev); - - free_stash_info(&info); - return diff_result_code(&rev.diffopt, 0); -} - -static int do_store_stash(const char *w_commit, const char *stash_msg, - int quiet) -{ - int ret = 0; - int need_to_free = 0; - struct object_id obj; - - if (!stash_msg) { - need_to_free = 1; - stash_msg = xstrdup("Created via \"git stash store\"."); - } - - ret = get_oid(w_commit, &obj); - if (!ret) { - ret = update_ref(stash_msg, ref_stash, &obj, NULL, - REF_FORCE_CREATE_REFLOG, - quiet ? UPDATE_REFS_QUIET_ON_ERR : - UPDATE_REFS_MSG_ON_ERR); - } - if (ret && !quiet) - fprintf_ln(stderr, _("Cannot update %s with %s"), - ref_stash, w_commit); - if (need_to_free) - free((char *) stash_msg); - return ret; -} - -static int store_stash(int argc, const char **argv, const char *prefix) -{ - const char *stash_msg = NULL; - struct option options[] = { - OPT__QUIET(&quiet, N_("be quiet, only report errors")), - OPT_STRING('m', "message", &stash_msg, "message", N_("stash message")), - OPT_END() - }; - - argc = parse_options(argc, argv, prefix, options, - git_stash_store_usage, - PARSE_OPT_KEEP_UNKNOWN); - - if (argc != 1) { - fprintf_ln(stderr, _("\"git stash store\" requires one argument")); - return -1; - } - - return do_store_stash(argv[0], stash_msg, quiet); -} - -/* - * `untracked_files` will be filled with the names of untracked files. - * The return value is: - * - * = 0 if there are not any untracked files - * > 0 if there are untracked files - */ -static struct strbuf untracked_files = STRBUF_INIT; - -static int get_untracked_files(struct pathspec ps, int include_untracked) -{ - int max_len; - int i; - char *seen; - struct dir_struct dir; - - memset(&dir, 0, sizeof(dir)); - if (include_untracked != 2) - setup_standard_excludes(&dir); - - seen = xcalloc(ps.nr, 1); - - max_len = fill_directory(&dir, the_repository->index, &ps); - for (i = 0; i < dir.nr; i++) { - struct dir_entry *ent = dir.entries[i]; - if (!dir_path_match(&the_index, ent, &ps, max_len, seen)) { - free(ent); - continue; - } - strbuf_addf(&untracked_files, "%s%c", ent->name, '\0'); - free(ent); - } - - free(dir.entries); - free(dir.ignored); - clear_directory(&dir); - free(seen); - return untracked_files.len; -} - -/* - * The return value of `check_changes_tracked_files()` can be: - * - * < 0 if there was an error - * = 0 if there are no changes. - * > 0 if there are changes. - */ - -static int check_changes_tracked_files(struct pathspec ps) -{ - int result; - struct rev_info rev; - struct object_id dummy; - - init_revisions(&rev, NULL); - rev.prune_data = ps; - - rev.diffopt.flags.quick = 1; - rev.diffopt.flags.ignore_submodules = 1; - rev.abbrev = 0; - - /* No initial commit. */ - if (get_oid("HEAD", &dummy)) - return -1; - - add_head_to_pending(&rev); - diff_setup_done(&rev.diffopt); - - if (read_cache() < 0) - return 1; - result = run_diff_index(&rev, 1); - if (diff_result_code(&rev.diffopt, result)) - return 1; - - object_array_clear(&rev.pending); - result = run_diff_files(&rev, 0); - if (diff_result_code(&rev.diffopt, result)) - return 1; - - return 0; -} - -static int check_changes(struct pathspec ps, int include_untracked) -{ - int ret = 0; - if (check_changes_tracked_files(ps)) - ret = 1; - - if (include_untracked && get_untracked_files(ps, include_untracked)) - ret = 1; - - return ret; -} - -static int save_untracked_files(struct stash_info *info, struct strbuf *msg) -{ - int ret = 0; - struct strbuf untracked_msg = STRBUF_INIT; - struct child_process cp_upd_index = CHILD_PROCESS_INIT; - struct index_state istate = { NULL }; - - cp_upd_index.git_cmd = 1; - argv_array_pushl(&cp_upd_index.args, "update-index", "-z", "--add", - "--remove", "--stdin", NULL); - argv_array_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s", - stash_index_path.buf); - - strbuf_addf(&untracked_msg, "untracked files on %s\n", msg->buf); - if (pipe_command(&cp_upd_index, untracked_files.buf, untracked_files.len, - NULL, 0, NULL, 0)) { - ret = -1; - goto done; - } - - if (write_index_as_tree(&info->u_tree, &istate, stash_index_path.buf, 0, - NULL)) { - ret = -1; - goto done; - } - - if (commit_tree(untracked_msg.buf, untracked_msg.len, - &info->u_tree, NULL, &info->u_commit, NULL, NULL)) { - ret = -1; - goto done; - } - -done: - discard_index(&istate); - strbuf_release(&untracked_msg); - remove_path(stash_index_path.buf); - return ret; -} - -static struct strbuf patch = STRBUF_INIT; - -static int stash_patch(struct stash_info *info, struct pathspec ps, int quiet) -{ - int i; - int ret = 0; - struct child_process cp_read_tree = CHILD_PROCESS_INIT; - struct child_process cp_add_i = CHILD_PROCESS_INIT; - struct child_process cp_diff_tree = CHILD_PROCESS_INIT; - struct index_state istate = { NULL }; - - remove_path(stash_index_path.buf); - - cp_read_tree.git_cmd = 1; - argv_array_pushl(&cp_read_tree.args, "read-tree", "HEAD", NULL); - argv_array_pushf(&cp_read_tree.env_array, "GIT_INDEX_FILE=%s", - stash_index_path.buf); - if (run_command(&cp_read_tree)) { - ret = -1; - goto done; - } - - cp_add_i.git_cmd = 1; - argv_array_pushl(&cp_add_i.args, "add--interactive", "--patch=stash", - "--", NULL); - for (i = 0; i < ps.nr; ++i) - argv_array_push(&cp_add_i.args, ps.items[i].match); - argv_array_pushf(&cp_add_i.env_array, "GIT_INDEX_FILE=%s", - stash_index_path.buf); - if (run_command(&cp_add_i)) { - ret = -1; - goto done; - } - - if (write_index_as_tree(&info->w_tree, &istate, stash_index_path.buf, 0, - NULL)) { - ret = -1; - goto done; - } - - cp_diff_tree.git_cmd = 1; - argv_array_pushl(&cp_diff_tree.args, "diff-tree", "-p", "HEAD", - oid_to_hex(&info->w_tree), "--", NULL); - if (pipe_command(&cp_diff_tree, NULL, 0, &patch, 0, NULL, 0)) { - ret = -1; - goto done; - } - - if (!patch.len) { - if (!quiet) - fprintf_ln(stderr, _("No changes selected")); - ret = 1; - } - -done: - discard_index(&istate); - remove_path(stash_index_path.buf); - return ret; -} - -static int stash_working_tree(struct stash_info *info, struct pathspec ps) -{ - int ret = 0; - struct child_process cp_upd_index = CHILD_PROCESS_INIT; - struct strbuf diff_output = STRBUF_INIT; - struct rev_info rev; - struct index_state istate = { NULL }; - - set_alternate_index_output(stash_index_path.buf); - if (reset_tree(&info->i_tree, 0, 0)) { - ret = -1; - goto done; - } - set_alternate_index_output(NULL); - - git_config(git_diff_basic_config, NULL); - init_revisions(&rev, NULL); - rev.prune_data = ps; - rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; - rev.diffopt.format_callback = add_diff_to_buf; - rev.diffopt.format_callback_data = &diff_output; - - if (read_cache_preload(&rev.diffopt.pathspec) < 0) { - ret = -1; - goto done; - } - - add_pending_object(&rev, parse_object(the_repository, &info->b_commit), ""); - if (run_diff_index(&rev, 0)) { - ret = -1; - goto done; - } - - cp_upd_index.git_cmd = 1; - argv_array_pushl(&cp_upd_index.args, "update-index", "-z", "--add", - "--remove", "--stdin", NULL); - argv_array_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s", - stash_index_path.buf); - - if (pipe_command(&cp_upd_index, diff_output.buf, diff_output.len, - NULL, 0, NULL, 0)) { - ret = -1; - goto done; - } - - if (write_index_as_tree(&info->w_tree, &istate, stash_index_path.buf, 0, - NULL)) { - ret = -1; - goto done; - } - -done: - discard_index(&istate); - UNLEAK(rev); - object_array_clear(&rev.pending); - strbuf_release(&diff_output); - remove_path(stash_index_path.buf); - return ret; -} - -static int do_create_stash(struct pathspec ps, const char **stash_msg, - int include_untracked, int patch_mode, - struct stash_info *info, int quiet) -{ - int untracked_commit_option = 0; - int ret = 0; - int flags; - const char *head_short_sha1 = NULL; - const char *branch_ref = NULL; - const char *branch_name = "(no branch)"; - struct commit *head_commit = NULL; - struct commit_list *parents = NULL; - struct strbuf msg = STRBUF_INIT; - struct strbuf commit_tree_label = STRBUF_INIT; - struct strbuf stash_msg_buf = STRBUF_INIT; - - read_cache_preload(NULL); - refresh_cache(REFRESH_QUIET); - - if (get_oid("HEAD", &info->b_commit)) { - if (!quiet) - fprintf_ln(stderr, _("You do not have the initial commit yet")); - ret = -1; - *stash_msg = NULL; - goto done; - } else { - head_commit = lookup_commit(the_repository, &info->b_commit); - } - - branch_ref = resolve_ref_unsafe("HEAD", 0, NULL, &flags); - if (flags & REF_ISSYMREF) - branch_name = strrchr(branch_ref, '/') + 1; - head_short_sha1 = find_unique_abbrev(&head_commit->object.oid, - DEFAULT_ABBREV); - strbuf_addf(&msg, "%s: %s ", branch_name, head_short_sha1); - pp_commit_easy(CMIT_FMT_ONELINE, head_commit, &msg); - - strbuf_addf(&commit_tree_label, "index on %s\n", msg.buf); - commit_list_insert(head_commit, &parents); - if (write_cache_as_tree(&info->i_tree, 0, NULL) || - commit_tree(commit_tree_label.buf, commit_tree_label.len, - &info->i_tree, parents, &info->i_commit, NULL, NULL)) { - if (!quiet) - fprintf_ln(stderr, _("Cannot save the current index state")); - ret = -1; - *stash_msg = NULL; - goto done; - } - - if (include_untracked) { - if (save_untracked_files(info, &msg)) { - if (!quiet) - fprintf_ln(stderr, _("Cannot save the untracked files")); - ret = -1; - *stash_msg = NULL; - goto done; - } - untracked_commit_option = 1; - } - if (patch_mode) { - ret = stash_patch(info, ps, quiet); - *stash_msg = NULL; - if (ret < 0) { - if (!quiet) - fprintf_ln(stderr, _("Cannot save the current worktree state")); - goto done; - } else if (ret > 0) { - goto done; - } - } else { - if (stash_working_tree(info, ps)) { - if (!quiet) - fprintf_ln(stderr, _("Cannot save the current worktree state")); - ret = -1; - *stash_msg = NULL; - goto done; - } - } - - if (!*stash_msg || !strlen(*stash_msg)) - strbuf_addf(&stash_msg_buf, "WIP on %s", msg.buf); - else - strbuf_addf(&stash_msg_buf, "On %s: %s", branch_name, - *stash_msg); - *stash_msg = strbuf_detach(&stash_msg_buf, NULL); - - /* - * `parents` will be empty after calling `commit_tree()`, so there is - * no need to call `free_commit_list()` - */ - parents = NULL; - if (untracked_commit_option) - commit_list_insert(lookup_commit(the_repository, &info->u_commit), &parents); - commit_list_insert(lookup_commit(the_repository, &info->i_commit), &parents); - commit_list_insert(head_commit, &parents); - - if (commit_tree(*stash_msg, strlen(*stash_msg), &info->w_tree, - parents, &info->w_commit, NULL, NULL)) { - if (!quiet) - fprintf_ln(stderr, _("Cannot record working tree state")); - ret = -1; - goto done; - } - -done: - strbuf_release(&commit_tree_label); - strbuf_release(&msg); - strbuf_release(&stash_msg_buf); - return ret; -} - -static int create_stash(int argc, const char **argv, const char *prefix) -{ - int i; - int ret = 0; - char *to_free = NULL; - const char *stash_msg = NULL; - struct stash_info info; - struct pathspec ps; - struct strbuf stash_msg_buf = STRBUF_INIT; - struct option options[] = { - OPT_END() - }; - - argc = parse_options(argc, argv, prefix, options, - git_stash_create_usage, - 0); - - memset(&ps, 0, sizeof(ps)); - if (!check_changes_tracked_files(ps)) - return 0; - - for (i = 0; i < argc; ++i) - strbuf_addf(&stash_msg_buf, "%s ", argv[i]); - stash_msg = strbuf_detach(&stash_msg_buf, NULL); - to_free = (char *) stash_msg; - - if (!(ret = do_create_stash(ps, &stash_msg, 0, 0, &info, 0))) - printf_ln("%s", oid_to_hex(&info.w_commit)); - - free(to_free); - free((char *) stash_msg); - return ret; -} - -static void add_ps_items_to_argv_array(struct argv_array *args, - struct pathspec ps) { - int i; - for (i = 0; i < ps.nr; ++i) - argv_array_push(args, ps.items[i].match); -} - -static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet, - int keep_index, int patch_mode, int include_untracked) -{ - int ret = 0; - struct stash_info info; - if (patch_mode && keep_index == -1) - keep_index = 1; - - if (patch_mode && include_untracked) { - fprintf_ln(stderr, _("Can't use --patch and --include-untracked or --all at the same time")); - return -1; - } - - read_cache_preload(NULL); - if (!include_untracked && ps.nr) { - int i; - char *ps_matched = xcalloc(ps.nr, 1); - - for (i = 0; i < active_nr; ++i) { - const struct cache_entry *ce = active_cache[i]; - ce_path_match(&the_index, ce, &ps, ps_matched); - } - - if (report_path_error(ps_matched, &ps, NULL)) { - fprintf_ln(stderr, _("Did you forget to 'git add'?")); - return -1; - } - free(ps_matched); - } - - if (refresh_cache(REFRESH_QUIET)) - return -1; - - if (!check_changes(ps, include_untracked)) { - if (!quiet) - printf_ln(_("No local changes to save")); - return 0; - } - - if (!reflog_exists(ref_stash) && do_clear_stash()) { - if (!quiet) - fprintf_ln(stderr, _("Cannot initialize stash")); - return -1; - } - - if (do_create_stash(ps, &stash_msg, include_untracked, patch_mode, - &info, quiet)) { - ret = -1; - goto done; - } - - if (do_store_stash(oid_to_hex(&info.w_commit), stash_msg, 1)) { - if (!quiet) - fprintf_ln(stderr, _("Cannot save the current status")); - ret = -1; - goto done; - } - - if (!quiet) - printf_ln(_("Saved working directory and index state %s"), - stash_msg); - - if (!patch_mode) { - if (include_untracked && !ps.nr) { - struct child_process cp = CHILD_PROCESS_INIT; - - cp.git_cmd = 1; - argv_array_pushl(&cp.args, "clean", "--force", - "--quiet", "-d", NULL); - if (include_untracked == 2) - argv_array_push(&cp.args, "-x"); - if (run_command(&cp)) { - ret = -1; - goto done; - } - } - if (ps.nr) { - struct child_process cp1 = CHILD_PROCESS_INIT; - struct child_process cp2 = CHILD_PROCESS_INIT; - struct child_process cp3 = CHILD_PROCESS_INIT; - struct strbuf out = STRBUF_INIT; - - cp1.git_cmd = 1; - argv_array_push(&cp1.args, "add"); - if (!include_untracked) - argv_array_push(&cp1.args, "-u"); - if (include_untracked == 2) - argv_array_push(&cp1.args, "--force"); - argv_array_push(&cp1.args, "--"); - add_ps_items_to_argv_array(&cp1.args, ps); - if (run_command(&cp1)) { - ret = -1; - goto done; - } - - cp2.git_cmd = 1; - argv_array_pushl(&cp2.args, "diff-index", "-p", - "--cached", "--binary", "HEAD", "--", - NULL); - add_ps_items_to_argv_array(&cp2.args, ps); - if (pipe_command(&cp2, NULL, 0, &out, 0, NULL, 0)) { - ret = -1; - goto done; - } - - cp3.git_cmd = 1; - argv_array_pushl(&cp3.args, "apply", "--index", "-R", - NULL); - if (pipe_command(&cp3, out.buf, out.len, NULL, 0, NULL, - 0)) { - ret = -1; - goto done; - } - } else { - struct child_process cp = CHILD_PROCESS_INIT; - cp.git_cmd = 1; - argv_array_pushl(&cp.args, "reset", "--hard", "-q", - NULL); - if (run_command(&cp)) { - ret = -1; - goto done; - } - } - - if (keep_index == 1 && !is_null_oid(&info.i_tree)) { - struct child_process cp1 = CHILD_PROCESS_INIT; - struct child_process cp2 = CHILD_PROCESS_INIT; - struct strbuf out = STRBUF_INIT; - - if (reset_tree(&info.i_tree, 0, 1)) { - ret = -1; - goto done; - } - - cp1.git_cmd = 1; - argv_array_pushl(&cp1.args, "ls-files", "-z", - "--modified", "--", NULL); - add_ps_items_to_argv_array(&cp1.args, ps); - if (pipe_command(&cp1, NULL, 0, &out, 0, NULL, 0)) { - ret = -1; - goto done; - } - - cp2.git_cmd = 1; - argv_array_pushl(&cp2.args, "checkout-index", "-z", - "--force", "--stdin", NULL); - if (pipe_command(&cp2, out.buf, out.len, NULL, 0, NULL, - 0)) { - ret = -1; - goto done; - } - } - } else { - struct child_process cp = CHILD_PROCESS_INIT; - - cp.git_cmd = 1; - argv_array_pushl(&cp.args, "apply", "-R", NULL); - - if (pipe_command(&cp, patch.buf, patch.len, NULL, 0, NULL, 0)) { - if (!quiet) - fprintf_ln(stderr, _("Cannot remove worktree changes")); - ret = -1; - goto done; - } - - if (keep_index < 1) { - int i; - struct child_process cp = CHILD_PROCESS_INIT; - - cp.git_cmd = 1; - argv_array_pushl(&cp.args, "reset", "-q", "--", NULL); - for (i = 0; i < ps.nr; ++i) - argv_array_push(&cp.args, ps.items[i].match); - if (run_command(&cp)) { - ret = -1; - goto done; - } - } - } -done: - free((char *) stash_msg); - return ret; -} - -static int push_stash(int argc, const char **argv, const char *prefix) -{ - int keep_index = -1; - int patch_mode = 0; - int include_untracked = 0; - int quiet = 0; - const char *stash_msg = NULL; - struct pathspec ps; - struct option options[] = { - OPT_SET_INT('k', "keep-index", &keep_index, - N_("keep index"), 1), - OPT_BOOL('p', "patch", &patch_mode, - N_("stash in patch mode")), - OPT__QUIET(&quiet, N_("quiet mode")), - OPT_BOOL('u', "include-untracked", &include_untracked, - N_("include untracked files in stash")), - OPT_SET_INT('a', "all", &include_untracked, - N_("include ignore files"), 2), - OPT_STRING('m', "message", &stash_msg, N_("message"), - N_("stash message")), - OPT_END() - }; - - if (argc) - argc = parse_options(argc, argv, prefix, options, - git_stash_push_usage, - 0); - - parse_pathspec(&ps, 0, PATHSPEC_PREFER_FULL, prefix, argv); - return do_push_stash(ps, stash_msg, quiet, keep_index, patch_mode, - include_untracked); -} - -static int save_stash(int argc, const char **argv, const char *prefix) -{ - int i; - int keep_index = -1; - int patch_mode = 0; - int include_untracked = 0; - int quiet = 0; - int ret = 0; - const char *stash_msg = NULL; - char *to_free = NULL; - struct strbuf stash_msg_buf = STRBUF_INIT; - struct pathspec ps; - struct option options[] = { - OPT_SET_INT('k', "keep-index", &keep_index, - N_("keep index"), 1), - OPT_BOOL('p', "patch", &patch_mode, - N_("stash in patch mode")), - OPT__QUIET(&quiet, N_("quiet mode")), - OPT_BOOL('u', "include-untracked", &include_untracked, - N_("include untracked files in stash")), - OPT_SET_INT('a', "all", &include_untracked, - N_("include ignore files"), 2), - OPT_END() - }; - - argc = parse_options(argc, argv, prefix, options, - git_stash_save_usage, - 0); - - for (i = 0; i < argc; ++i) - strbuf_addf(&stash_msg_buf, "%s ", argv[i]); - stash_msg = strbuf_detach(&stash_msg_buf, NULL); - to_free = (char *) stash_msg; - - memset(&ps, 0, sizeof(ps)); - ret = do_push_stash(ps, stash_msg, quiet, keep_index, patch_mode, - include_untracked); - - free(to_free); - return ret; -} - -static int use_builtin_stash(void) -{ - struct child_process cp = CHILD_PROCESS_INIT; - struct strbuf out = STRBUF_INIT; - int ret; - - argv_array_pushl(&cp.args, - "config", "--bool", "stash.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_stash(int argc, const char **argv, const char *prefix) -{ - int i = -1; - pid_t pid = getpid(); - const char *index_file; - struct argv_array args = ARGV_ARRAY_INIT; - - struct option options[] = { - OPT_END() - }; - - if (!use_builtin_stash()) { - const char *path = mkpath("%s/git-legacy-stash", - git_exec_path()); - - if (sane_execvp(path, (char **)argv) < 0) - die_errno(_("could not exec %s"), path); - else - BUG("sane_execvp() returned???"); - } - - prefix = setup_git_directory(); - trace_repo_setup(prefix); - setup_work_tree(); - - git_config(git_default_config, NULL); - - argc = parse_options(argc, argv, prefix, options, git_stash_usage, - PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH); - - index_file = get_index_file(); - strbuf_addf(&stash_index_path, "%s.stash.%" PRIuMAX, index_file, - (uintmax_t)pid); - - if (argc == 0) - return !!push_stash(0, NULL, prefix); - else if (!strcmp(argv[0], "apply")) - return !!apply_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "clear")) - return !!clear_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "drop")) - return !!drop_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "pop")) - return !!pop_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "branch")) - return !!branch_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "list")) - return !!list_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "show")) - return !!show_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "store")) - return !!store_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "create")) - return !!create_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "push")) - return !!push_stash(argc, argv, prefix); - else if (!strcmp(argv[0], "save")) - return !!save_stash(argc, argv, prefix); - else if (*argv[0] != '-') - usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]), - git_stash_usage, options); - - if (strcmp(argv[0], "-p")) { - while (++i < argc && strcmp(argv[i], "--")) { - /* - * `akpqu` is a string which contains all short options, - * except `-m` which is verified separately. - */ - if ((strlen(argv[i]) == 2) && *argv[i] == '-' && - strchr("akpqu", argv[i][1])) - continue; - - if (!strcmp(argv[i], "--all") || - !strcmp(argv[i], "--keep-index") || - !strcmp(argv[i], "--no-keep-index") || - !strcmp(argv[i], "--patch") || - !strcmp(argv[i], "--quiet") || - !strcmp(argv[i], "--include-untracked")) - continue; - - /* - * `-m` and `--message=` are verified separately because - * they need to be immediately followed by a string - * (i.e.`-m"foobar"` or `--message="foobar"`). - */ - if ((strlen(argv[i]) > 2 && - !strncmp(argv[i], "-m", 2)) || - (strlen(argv[i]) > 10 && - !strncmp(argv[i], "--message=", 10))) - continue; - - usage_with_options(git_stash_usage, options); - } - } - - argv_array_push(&args, "push"); - argv_array_pushv(&args, argv); - return !!push_stash(args.argc, args.argv, prefix); -} diff --git a/cache.h b/cache.h index 1912a8ea8d1c5f..93cb2b739dc6a9 100644 --- a/cache.h +++ b/cache.h @@ -1318,7 +1318,6 @@ struct object_context { GET_OID_BLOB) extern int get_oid(const char *str, struct object_id *oid); -extern int get_oidf(struct object_id *oid, const char *fmt, ...); extern int get_oid_commit(const char *str, struct object_id *oid); extern int get_oid_committish(const char *str, struct object_id *oid); extern int get_oid_tree(const char *str, struct object_id *oid); @@ -1472,7 +1471,6 @@ extern const char *fmt_name(const char *name, const char *email); extern const char *ident_default_name(void); extern const char *ident_default_email(void); extern const char *git_editor(void); -extern const char *git_sequence_editor(void); extern const char *git_pager(int stdout_is_tty); extern int is_terminal_dumb(void); extern int git_ident_config(const char *, const char *, void *); diff --git a/commit.c b/commit.c index 487421fbb6d4df..8a6616028ee2f9 100644 --- a/commit.c +++ b/commit.c @@ -17,7 +17,6 @@ #include "sha1-lookup.h" #include "wt-status.h" #include "advice.h" -#include "refs.h" static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **); @@ -965,86 +964,6 @@ static struct commit_list *merge_bases_many(struct commit *one, int n, struct co return result; } -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; -} - -struct commit *get_fork_point(const char *refname, struct commit *commit) -{ - struct object_id oid; - struct rev_collect revs; - struct commit_list *bases; - int i; - struct commit *ret = NULL; - - 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); - - for (i = 0; i < revs.nr; i++) - revs.commit[i]->object.flags &= ~TMP_MARK; - - bases = get_merge_bases_many(commit, 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) - 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) - goto cleanup_return; - - ret = bases->item; - -cleanup_return: - free_commit_list(bases); - return ret; -} - struct commit_list *get_octopus_merge_bases(struct commit_list *in) { struct commit_list *i, *j, *k, *ret = NULL; diff --git a/commit.h b/commit.h index 98482e0a60ed3f..b5b1dcc8ef8bae 100644 --- a/commit.h +++ b/commit.h @@ -215,8 +215,6 @@ extern struct commit_list *get_octopus_merge_bases(struct commit_list *in); /* To be used only when object flags after this call no longer matter */ extern struct commit_list *get_merge_bases_many_dirty(struct commit *one, int n, struct commit **twos); -struct commit *get_fork_point(const char *refname, struct commit *commit); - /* largest positive number a signed 32-bit integer can contain */ #define INFINITE_DEPTH 0x7fffffff diff --git a/editor.c b/editor.c index 71547674ab4e88..29707de1984eb4 100644 --- a/editor.c +++ b/editor.c @@ -1,5 +1,4 @@ #include "cache.h" -#include "config.h" #include "strbuf.h" #include "run-command.h" #include "sigchain.h" @@ -35,21 +34,10 @@ const char *git_editor(void) return editor; } -const char *git_sequence_editor(void) +int launch_editor(const char *path, struct strbuf *buffer, const char *const *env) { - const char *editor = getenv("GIT_SEQUENCE_EDITOR"); - - if (!editor) - git_config_get_string_const("sequence.editor", &editor); - if (!editor) - editor = git_editor(); + const char *editor = git_editor(); - return editor; -} - -static int launch_specified_editor(const char *editor, const char *path, - struct strbuf *buffer, const char *const *env) -{ if (!editor) return error("Terminal is dumb, but EDITOR unset"); @@ -108,14 +96,3 @@ static int launch_specified_editor(const char *editor, const char *path, return error_errno("could not read file '%s'", path); return 0; } - -int launch_editor(const char *path, struct strbuf *buffer, const char *const *env) -{ - return launch_specified_editor(git_editor(), path, buffer, env); -} - -int launch_sequence_editor(const char *path, struct strbuf *buffer, - const char *const *env) -{ - return launch_specified_editor(git_sequence_editor(), path, buffer, env); -} diff --git a/git-rebase--common.sh b/git-rebase--common.sh deleted file mode 100644 index 7e39d228717899..00000000000000 --- a/git-rebase--common.sh +++ /dev/null @@ -1,68 +0,0 @@ - -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 -} diff --git a/git-legacy-rebase--interactive.sh b/git-rebase--interactive.sh similarity index 91% rename from git-legacy-rebase--interactive.sh rename to git-rebase--interactive.sh index 9740875ad50bf7..299ded21375ed3 100644 --- a/git-legacy-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -95,11 +95,11 @@ git_sequence_editor () { } expand_todo_ids() { - git rebase--interactive --expand-ids + git rebase--helper --expand-ids } collapse_todo_ids() { - git rebase--interactive --shorten-ids + git rebase--helper --shorten-ids } # Switch to the branch in $into and notify it in the reflog @@ -131,12 +131,12 @@ get_missing_commit_check_level () { initiate_action () { case "$1" in continue) - exec git rebase--interactive ${force_rebase:+--no-ff} $allow_empty_message \ + exec git rebase--helper ${force_rebase:+--no-ff} $allow_empty_message \ --continue ;; skip) git rerere clear - exec git rebase--interactive ${force_rebase:+--no-ff} $allow_empty_message \ + exec git rebase--helper ${force_rebase:+--no-ff} $allow_empty_message \ --continue ;; edit-todo) @@ -207,8 +207,8 @@ init_revisions_and_shortrevisions () { complete_action() { test -s "$todo" || echo noop >> "$todo" - test -z "$autosquash" || git rebase--interactive --rearrange-squash || exit - test -n "$cmd" && git rebase--interactive --add-exec-commands --cmd "$cmd" + test -z "$autosquash" || git rebase--helper --rearrange-squash || exit + test -n "$cmd" && git rebase--helper --add-exec-commands "$cmd" todocount=$(git stripspace --strip-comments <"$todo" | wc -l) todocount=${todocount##* } @@ -243,7 +243,7 @@ EOF has_action "$todo" || return 2 - git rebase--interactive --check-todo-list || { + git rebase--helper --check-todo-list || { ret=$? checkout_onto exit $ret @@ -252,12 +252,12 @@ EOF expand_todo_ids test -n "$force_rebase" || - onto="$(git rebase--interactive --skip-unnecessary-picks)" || + onto="$(git rebase--helper --skip-unnecessary-picks)" || die "Could not skip unnecessary pick commands" checkout_onto require_clean_work_tree "rebase" - exec git rebase--interactive ${force_rebase:+--no-ff} $allow_empty_message \ + exec git rebase--helper ${force_rebase:+--no-ff} $allow_empty_message \ --continue } @@ -273,7 +273,7 @@ git_rebase__interactive () { init_revisions_and_shortrevisions - git rebase--interactive --make-script ${keep_empty:+--keep-empty} \ + git rebase--helper --make-script ${keep_empty:+--keep-empty} \ ${rebase_merges:+--rebase-merges} \ ${rebase_cousins:+--rebase-cousins} \ $revisions ${restrict_revision+^$restrict_revision} >"$todo" || diff --git a/git-rebase--preserve-merges.sh b/git-rebase--preserve-merges.sh index afbb65765d4610..c214c5e4d6ce21 100644 --- a/git-rebase--preserve-merges.sh +++ b/git-rebase--preserve-merges.sh @@ -711,11 +711,11 @@ do_rest () { } expand_todo_ids() { - git rebase--interactive --expand-ids + git rebase--helper --expand-ids } collapse_todo_ids() { - git rebase--interactive --shorten-ids + git rebase--helper --shorten-ids } # Switch to the branch in $into and notify it in the reflog @@ -876,8 +876,8 @@ init_revisions_and_shortrevisions () { complete_action() { test -s "$todo" || echo noop >> "$todo" - test -z "$autosquash" || git rebase--interactive --rearrange-squash || exit - test -n "$cmd" && git rebase--interactive --add-exec-commands --cmd "$cmd" + test -z "$autosquash" || git rebase--helper --rearrange-squash || exit + test -n "$cmd" && git rebase--helper --add-exec-commands "$cmd" todocount=$(git stripspace --strip-comments <"$todo" | wc -l) todocount=${todocount##* } @@ -912,7 +912,7 @@ However, if you remove everything, the rebase will be aborted. has_action "$todo" || return 2 - git rebase--interactive --check-todo-list || { + git rebase--helper --check-todo-list || { ret=$? checkout_onto exit $ret diff --git a/git-legacy-rebase.sh b/git-rebase.sh similarity index 89% rename from git-legacy-rebase.sh rename to git-rebase.sh index 4312e69c6b92ac..797344764510e0 100755 --- a/git-legacy-rebase.sh +++ b/git-rebase.sh @@ -57,7 +57,12 @@ 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 @@ -97,7 +102,6 @@ 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" && @@ -128,6 +132,67 @@ 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 && @@ -141,37 +206,24 @@ run_specific_rebase () { export GIT_EDITOR autosquash= fi + . git-rebase--$type - if test -n "$interactive_rebase" -a -z "$preserve_merges" + if test -z "$preserve_merges" then - . git-legacy-rebase--$type - git_rebase__$type else - . git-rebase--$type - - if test -z "$preserve_merges" - then - git_rebase__$type - else - git_rebase__preserve_merges - fi + git_rebase__preserve_merges fi ret=$? if test $ret -eq 0 then finish_rebase - elif test $ret -eq 2 # special exit status for rebase -p + elif test $ret -eq 2 # special exit status for rebase -i then apply_autostash && rm -rf "$state_dir" && - if test -n "$interactive_rebase" -a -z "$preserve_merges" - then - die "error: nothing to do" - else - die "Nothing to do" - fi + die "Nothing to do" fi exit $ret } diff --git a/git-sh-setup.sh b/git-sh-setup.sh index 6a19a3bfc4ed8f..219c687f34c832 100644 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -101,7 +101,6 @@ $LONG_USAGE")" case "$1" in -h) echo "$LONG_USAGE" - case "$0" in *git-legacy-stash) exit 129;; esac exit esac fi diff --git a/git-legacy-stash.sh b/git-stash.sh similarity index 97% rename from git-legacy-stash.sh rename to git-stash.sh index eed7da488456a5..94793c1a913abf 100755 --- a/git-legacy-stash.sh +++ b/git-stash.sh @@ -66,28 +66,6 @@ clear_stash () { fi } -maybe_quiet () { - case "$1" in - --keep-stdout) - shift - if test -n "$GIT_QUIET" - then - eval "$@" 2>/dev/null - else - eval "$@" - fi - ;; - *) - if test -n "$GIT_QUIET" - then - eval "$@" >/dev/null 2>&1 - else - eval "$@" - fi - ;; - esac -} - create_stash () { stash_msg= untracked= @@ -117,18 +95,15 @@ create_stash () { done git update-index -q --refresh - if maybe_quiet no_changes "$@" + if no_changes "$@" then exit 0 fi # state of the base commit - if b_commit=$(maybe_quiet --keep-stdout git rev-parse --verify HEAD) + if b_commit=$(git rev-parse --verify HEAD) then head=$(git rev-list --oneline -n 1 HEAD --) - elif test -n "$GIT_QUIET" - then - exit 1 else die "$(gettext "You do not have the initial commit yet")" fi @@ -323,7 +298,7 @@ push_stash () { test -n "$untracked" || git ls-files --error-unmatch -- "$@" >/dev/null || exit 1 git update-index -q --refresh - if maybe_quiet no_changes "$@" + if no_changes "$@" then say "$(gettext "No local changes to save")" exit 0 @@ -378,9 +353,6 @@ save_stash () { while test $# != 0 do case "$1" in - -q|--quiet) - GIT_QUIET=t - ;; --) shift break diff --git a/git.c b/git.c index 2924710db52e79..9420cd37b00cbf 100644 --- a/git.c +++ b/git.c @@ -602,13 +602,7 @@ static struct cmd_struct commands[] = { { "push", cmd_push, RUN_SETUP }, { "range-diff", cmd_range_diff, RUN_SETUP | USE_PAGER }, { "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--interactive", cmd_rebase__interactive, RUN_SETUP | NEED_WORK_TREE }, + { "rebase--helper", cmd_rebase__helper, RUN_SETUP | NEED_WORK_TREE }, { "receive-pack", cmd_receive_pack }, { "reflog", cmd_reflog, RUN_SETUP }, { "remote", cmd_remote, RUN_SETUP }, @@ -630,12 +624,6 @@ static struct cmd_struct commands[] = { { "show-index", cmd_show_index }, { "show-ref", cmd_show_ref, RUN_SETUP }, { "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE }, - /* - * NEEDSWORK: Until the builtin stash is thoroughly robust and no - * longer needs redirection to the stash shell script this is kept as - * is, then should be changed to RUN_SETUP | NEED_WORK_TREE - */ - { "stash", cmd_stash }, { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE }, { "stripspace", cmd_stripspace }, { "submodule--helper", cmd_submodule__helper, RUN_SETUP | SUPPORT_SUPER_PREFIX | NO_PARSEOPT }, diff --git a/rebase-interactive.c b/rebase-interactive.c deleted file mode 100644 index 0f4119cbae1bb6..00000000000000 --- a/rebase-interactive.c +++ /dev/null @@ -1,90 +0,0 @@ -#include "cache.h" -#include "commit.h" -#include "rebase-interactive.h" -#include "sequencer.h" -#include "strbuf.h" - -void append_todo_help(unsigned edit_todo, unsigned keep_empty, - struct strbuf *buf) -{ - const char *msg = _("\nCommands:\n" -"p, pick = use commit\n" -"r, reword = use commit, but edit the commit message\n" -"e, edit = use commit, but stop for amending\n" -"s, squash = use commit, but meld into previous commit\n" -"f, fixup = like \"squash\", but discard this commit's log message\n" -"x, exec = run command (the rest of the line) using shell\n" -"d, drop = remove commit\n" -"l, label