From bda7d60fd6f27e5cc89c0de549d4237b8cd603c7 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 10 Jan 2017 12:44:48 +0100 Subject: [PATCH] Revert "Merge branch 'interactive-rebase'" Prepare for the current interactive-rebase patch thicket, backported to `maint` (actually, our latest start of a merging rebase). This reverts commit b358f8dfe0cb92bdf539ae3ba67e632c8c6886d9, reversing changes made to 9e699f5eb6c41e1f5ffc8712a0b4c1e07a0b5102. Signed-off-by: Johannes Schindelin --- .gitignore | 1 - Makefile | 1 - builtin.h | 1 - builtin/rebase--helper.c | 67 -- git-rebase--interactive.sh | 373 ++++++-- git.c | 1 - run-command.c | 23 - run-command.h | 1 - sequencer.c | 1516 +-------------------------------- sequencer.h | 12 +- t/t3404-rebase-interactive.sh | 24 +- t/t3415-rebase-autosquash.sh | 16 +- 12 files changed, 383 insertions(+), 1653 deletions(-) delete mode 100644 builtin/rebase--helper.c diff --git a/.gitignore b/.gitignore index 006bf00df1..dd037dab6a 100644 --- a/.gitignore +++ b/.gitignore @@ -115,7 +115,6 @@ /git-read-tree /git-rebase /git-rebase--am -/git-rebase--helper /git-rebase--interactive /git-rebase--merge /git-receive-pack diff --git a/Makefile b/Makefile index 2053cb0458..fc164369a1 100644 --- a/Makefile +++ b/Makefile @@ -931,7 +931,6 @@ BUILTIN_OBJS += builtin/prune.o BUILTIN_OBJS += builtin/pull.o BUILTIN_OBJS += builtin/push.o BUILTIN_OBJS += builtin/read-tree.o -BUILTIN_OBJS += builtin/rebase--helper.o BUILTIN_OBJS += builtin/receive-pack.o BUILTIN_OBJS += builtin/reflog.o BUILTIN_OBJS += builtin/remote.o diff --git a/builtin.h b/builtin.h index 9e4a89816d..67f80519da 100644 --- a/builtin.h +++ b/builtin.h @@ -103,7 +103,6 @@ extern int cmd_prune_packed(int argc, const char **argv, const char *prefix); extern int cmd_pull(int argc, const char **argv, const char *prefix); extern int cmd_push(int argc, const char **argv, const char *prefix); extern int cmd_read_tree(int argc, const char **argv, const char *prefix); -extern int cmd_rebase__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); diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c deleted file mode 100644 index e6591f0111..0000000000 --- a/builtin/rebase--helper.c +++ /dev/null @@ -1,67 +0,0 @@ -#include "builtin.h" -#include "cache.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; - int keep_empty = 0; - enum { - CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_SHA1S, EXPAND_SHA1S, - CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH - } 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_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-sha1s", &command, - N_("shorten SHA-1s in the todo list"), SHORTEN_SHA1S), - OPT_CMDMODE(0, "expand-sha1s", &command, - N_("expand SHA-1s in the todo list"), EXPAND_SHA1S), - 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_END() - }; - - git_config(git_default_config, NULL); - - 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); - - 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(keep_empty, stdout, argc, argv); - if (command == SHORTEN_SHA1S && argc == 1) - return !!transform_todo_ids(1); - if (command == EXPAND_SHA1S && argc == 1) - return !!transform_todo_ids(0); - 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(); - usage_with_options(builtin_rebase_helper_usage, options); -} diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 8790d8ed8b..41fd374c72 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -155,13 +155,13 @@ reschedule_last_action () { append_todo_help () { gettext " Commands: -p, pick = use commit -r, reword = use commit, but edit the commit message -e, edit = use commit, but stop for amending -s, squash = use commit, but meld into previous commit -f, fixup = like \"squash\", but discard this commit's log message -x, exec = run command (the rest of the line) using shell -d, drop = remove commit + p, pick = use commit + r, reword = use commit, but edit the commit message + e, edit = use commit, but stop for amending + s, squash = use commit, but meld into previous commit + f, fixup = like \"squash\", but discard this commit's log message + x, exec = run command (the rest of the line) using shell + d, drop = remove commit These lines can be re-ordered; they are executed from top to bottom. " | git stripspace --comment-lines >>"$todo" @@ -712,6 +712,43 @@ do_rest () { done } +# skip picking commits whose parents are unchanged +skip_unnecessary_picks () { + fd=3 + while read -r command rest + do + # fd=3 means we skip the command + case "$fd,$command" in + 3,pick|3,p) + # pick a commit whose parent is current $onto -> skip + sha1=${rest%% *} + case "$(git rev-parse --verify --quiet "$sha1"^)" in + "$onto"*) + onto=$sha1 + ;; + *) + fd=1 + ;; + esac + ;; + 3,"$comment_char"*|3,) + # copy comments + ;; + *) + fd=1 + ;; + esac + printf '%s\n' "$command${rest:+ }$rest" >&$fd + done <"$todo" >"$todo.new" 3>>"$done" && + mv -f "$todo".new "$todo" && + case "$(peek_next_command)" in + squash|s|fixup|f) + record_in_rewritten "$onto" + ;; + esac || + die "$(gettext "Could not skip unnecessary pick commands")" +} + transform_todo_ids () { while read -r command rest do @@ -722,12 +759,7 @@ transform_todo_ids () { ;; *) sha1=$(git rev-parse --verify --quiet "$@" ${rest%%[ ]*}) && - if test "a$rest" = "a${rest#*[ ]}" - then - rest=$sha1 - else - rest="$sha1 ${rest#*[ ]}" - fi + rest="$sha1 ${rest#*[ ]}" ;; esac printf '%s\n' "$command${rest:+ }$rest" @@ -736,11 +768,98 @@ transform_todo_ids () { } expand_todo_ids() { - git rebase--helper --expand-sha1s + transform_todo_ids } collapse_todo_ids() { - git rebase--helper --shorten-sha1s + transform_todo_ids --short +} + +# Rearrange the todo list that has both "pick sha1 msg" and +# "pick sha1 fixup!/squash! msg" appears in it so that the latter +# comes immediately after the former, and change "pick" to +# "fixup"/"squash". +# +# Note that if the config has specified a custom instruction format +# each log message will be re-retrieved in order to normalize the +# autosquash arrangement +rearrange_squash () { + # extract fixup!/squash! lines and resolve any referenced sha1's + while read -r pick sha1 message + do + test -z "${format}" || message=$(git log -n 1 --format="%s" ${sha1}) + case "$message" in + "squash! "*|"fixup! "*) + action="${message%%!*}" + rest=$message + prefix= + # skip all squash! or fixup! (but save for later) + while : + do + case "$rest" in + "squash! "*|"fixup! "*) + prefix="$prefix${rest%%!*}," + rest="${rest#*! }" + ;; + *) + break + ;; + esac + done + printf '%s %s %s %s\n' "$sha1" "$action" "$prefix" "$rest" + # if it's a single word, try to resolve to a full sha1 and + # emit a second copy. This allows us to match on both message + # and on sha1 prefix + if test "${rest#* }" = "$rest"; then + fullsha="$(git rev-parse -q --verify "$rest" 2>/dev/null)" + if test -n "$fullsha"; then + # prefix the action to uniquely identify this line as + # intended for full sha1 match + echo "$sha1 +$action $prefix $fullsha" + fi + fi + esac + done >"$1.sq" <"$1" + test -s "$1.sq" || return + + used= + while read -r pick sha1 message + do + case " $used" in + *" $sha1 "*) continue ;; + esac + printf '%s\n' "$pick $sha1 $message" + test -z "${format}" || message=$(git log -n 1 --format="%s" ${sha1}) + used="$used$sha1 " + while read -r squash action msg_prefix msg_content + do + case " $used" in + *" $squash "*) continue ;; + esac + emit=0 + case "$action" in + +*) + action="${action#+}" + # full sha1 prefix test + case "$msg_content" in "$sha1"*) emit=1;; esac ;; + *) + # message prefix test + case "$message" in "$msg_content"*) emit=1;; esac ;; + esac + if test $emit = 1; then + if test -n "${format}" + then + msg_content=$(git log -n 1 --format="${format}" ${squash}) + else + msg_content="$(echo "$msg_prefix" | sed "s/,/! /g")$msg_content" + fi + printf '%s\n' "$action $squash $msg_content" + used="$used$squash " + fi + done <"$1.sq" + done >"$1.rearranged" <"$1" + cat "$1.rearranged" >"$1" + rm -f "$1.sq" "$1.rearranged" } # Add commands after a pick or after a squash/fixup serie @@ -764,6 +883,96 @@ add_exec_commands () { mv "$1.new" "$1" } +# Check if the SHA-1 passed as an argument is a +# correct one, if not then print $2 in "$todo".badsha +# $1: the SHA-1 to test +# $2: the line number of the input +# $3: the input filename +check_commit_sha () { + badsha=0 + if test -z "$1" + then + badsha=1 + else + sha1_verif="$(git rev-parse --verify --quiet $1^{commit})" + if test -z "$sha1_verif" + then + badsha=1 + fi + fi + + if test $badsha -ne 0 + then + line="$(sed -n -e "${2}p" "$3")" + warn "$(eval_gettext "\ +Warning: the SHA-1 is missing or isn't a commit in the following line: + - \$line")" + warn + fi + + return $badsha +} + +# prints the bad commits and bad commands +# from the todolist in stdin +check_bad_cmd_and_sha () { + retval=0 + lineno=0 + while read -r command rest + do + lineno=$(( $lineno + 1 )) + case $command in + "$comment_char"*|''|noop|x|exec) + # Doesn't expect a SHA-1 + ;; + "$cr") + # Work around CR left by "read" (e.g. with Git for + # Windows' Bash). + ;; + pick|p|drop|d|reword|r|edit|e|squash|s|fixup|f) + if ! check_commit_sha "${rest%%[ ]*}" "$lineno" "$1" + then + retval=1 + fi + ;; + *) + line="$(sed -n -e "${lineno}p" "$1")" + warn "$(eval_gettext "\ +Warning: the command isn't recognized in the following line: + - \$line")" + warn + retval=1 + ;; + esac + done <"$1" + return $retval +} + +# Print the list of the SHA-1 of the commits +# from stdin to stdout +todo_list_to_sha_list () { + git stripspace --strip-comments | + while read -r command sha1 rest + do + case $command in + "$comment_char"*|''|noop|x|"exec") + ;; + *) + long_sha=$(git rev-list --no-walk "$sha1" 2>/dev/null) + printf "%s\n" "$long_sha" + ;; + esac + done +} + +# Use warn for each line in stdin +warn_lines () { + while read -r line + do + warn " - $line" + done +} + # Switch to the branch in $into and notify it in the reflog checkout_onto () { GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name" @@ -778,6 +987,74 @@ get_missing_commit_check_level () { printf '%s' "$check_level" | tr 'A-Z' 'a-z' } +# Check if the user dropped some commits by mistake +# Behaviour determined by rebase.missingCommitsCheck. +# Check if there is an unrecognized command or a +# bad SHA-1 in a command. +check_todo_list () { + raise_error=f + + check_level=$(get_missing_commit_check_level) + + case "$check_level" in + warn|error) + # Get the SHA-1 of the commits + todo_list_to_sha_list <"$todo".backup >"$todo".oldsha1 + todo_list_to_sha_list <"$todo" >"$todo".newsha1 + + # Sort the SHA-1 and compare them + sort -u "$todo".oldsha1 >"$todo".oldsha1+ + mv "$todo".oldsha1+ "$todo".oldsha1 + sort -u "$todo".newsha1 >"$todo".newsha1+ + mv "$todo".newsha1+ "$todo".newsha1 + comm -2 -3 "$todo".oldsha1 "$todo".newsha1 >"$todo".miss + + # Warn about missing commits + if test -s "$todo".miss + then + test "$check_level" = error && raise_error=t + + warn "$(gettext "\ +Warning: some commits may have been dropped accidentally. +Dropped commits (newer to older):")" + + # Make the list user-friendly and display + opt="--no-walk=sorted --format=oneline --abbrev-commit --stdin" + git rev-list $opt <"$todo".miss | warn_lines + + warn "$(gettext "\ +To avoid this message, use \"drop\" to explicitly remove a commit. + +Use 'git config rebase.missingCommitsCheck' to change the level of warnings. +The possible behaviours are: ignore, warn, error.")" + warn + fi + ;; + ignore) + ;; + *) + warn "$(eval_gettext "Unrecognized setting \$check_level for option rebase.missingCommitsCheck. Ignoring.")" + ;; + esac + + if ! check_bad_cmd_and_sha "$todo" + then + raise_error=t + fi + + if test $raise_error = t + then + # Checkout before the first commit of the + # rebase: this way git rebase --continue + # will work correctly as it expects HEAD to be + # placed before the commit of the next action + checkout_onto + + warn "$(gettext "You can fix this with 'git rebase --edit-todo' and then run 'git rebase --continue'.")" + die "$(gettext "Or you can abort the rebase with 'git rebase --abort'.")" + fi +} + # The whole contents of this file is run by dot-sourcing it from # inside a shell function. It used to be that "return"s we see # below were not inside any function, and expected to return @@ -791,10 +1068,6 @@ git_rebase__interactive () { case "$action" in continue) - if test ! -d "$rewritten" - then - exec git rebase--helper ${force_rebase:+--no-ff} --continue - fi # do we have anything to commit? if git diff-index --cached --quiet HEAD -- then @@ -854,10 +1127,6 @@ first and then run 'git rebase --continue' again.")" skip) git rerere clear - if test ! -d "$rewritten" - then - exec git rebase--helper ${force_rebase:+--no-ff} --continue - fi do_rest return 0 ;; @@ -932,27 +1201,26 @@ else revisions=$onto...$orig_head shortrevisions=$shorthead fi -if test t != "$preserve_merges" -then - git rebase--helper --make-script ${keep_empty:+--keep-empty} \ - $revisions ${restrict_revision+^$restrict_revision} >"$todo" -else - format=$(git config --get rebase.instructionFormat) - # the 'rev-list .. | sed' requires %m to parse; the instruction requires %H to parse - git rev-list $merges_option --format="%m%H ${format:-%s}" \ - --reverse --left-right --topo-order \ - $revisions ${restrict_revision+^$restrict_revision} | \ - sed -n "s/^>//p" | - while read -r sha1 rest - do +format=$(git config --get rebase.instructionFormat) +# the 'rev-list .. | sed' requires %m to parse; the instruction requires %H to parse +git rev-list $merges_option --format="%m%H ${format:-%s}" \ + --reverse --left-right --topo-order \ + $revisions ${restrict_revision+^$restrict_revision} | \ + sed -n "s/^>//p" | +while read -r sha1 rest +do - if test -z "$keep_empty" && is_empty_commit $sha1 && ! is_merge_commit $sha1 - then - comment_out="$comment_char " - else - comment_out= - fi + if test -z "$keep_empty" && is_empty_commit $sha1 && ! is_merge_commit $sha1 + then + comment_out="$comment_char " + else + comment_out= + fi + if test t != "$preserve_merges" + then + printf '%s\n' "${comment_out}pick $sha1 $rest" >>"$todo" + else if test -z "$rebase_root" then preserve=t @@ -971,8 +1239,8 @@ else touch "$rewritten"/$sha1 printf '%s\n' "${comment_out}pick $sha1 $rest" >>"$todo" fi - done -fi + fi +done # Watch for commits that been dropped by --cherry-pick if test t = "$preserve_merges" @@ -1002,7 +1270,7 @@ then fi test -s "$todo" || echo noop >> "$todo" -test -z "$autosquash" || git rebase--helper --rearrange-squash || exit +test -n "$autosquash" && rearrange_squash "$todo" test -n "$cmd" && add_exec_commands "$todo" todocount=$(git stripspace --strip-comments <"$todo" | wc -l) @@ -1038,24 +1306,13 @@ git_sequence_editor "$todo" || has_action "$todo" || return 2 -git rebase--helper --check-todo-list || { - ret=$? - checkout_onto - exit $ret -} +check_todo_list expand_todo_ids -test -d "$rewritten" || test -n "$force_rebase" || -onto="$(git rebase--helper --skip-unnecessary-picks)" || -die "Could not skip unnecessary pick commands" +test -d "$rewritten" || test -n "$force_rebase" || skip_unnecessary_picks checkout_onto -if test -z "$rebase_root" && test ! -d "$rewritten" -then - require_clean_work_tree "rebase" - exec git rebase--helper ${force_rebase:+--no-ff} --continue -fi do_rest } diff --git a/git.c b/git.c index eee36fd911..c82288ac8e 100644 --- a/git.c +++ b/git.c @@ -478,7 +478,6 @@ static struct cmd_struct commands[] = { { "pull", cmd_pull, RUN_SETUP | NEED_WORK_TREE }, { "push", cmd_push, RUN_SETUP }, { "read-tree", cmd_read_tree, RUN_SETUP }, - { "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 }, diff --git a/run-command.c b/run-command.c index 5bb957afdd..ca905a9e80 100644 --- a/run-command.c +++ b/run-command.c @@ -589,29 +589,6 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const cmd.clean_on_exit = opt & RUN_CLEAN_ON_EXIT ? 1 : 0; cmd.dir = dir; cmd.env = env; - - if (opt & RUN_HIDE_STDERR_ON_SUCCESS) { - struct strbuf buf = STRBUF_INIT; - int res; - - cmd.err = -1; - if (start_command(&cmd) < 0) - return -1; - - if (strbuf_read(&buf, cmd.err, 0) < 0) { - close(cmd.err); - finish_command(&cmd); /* throw away exit code */ - return -1; - } - - close(cmd.err); - res = finish_command(&cmd); - if (res) - fputs(buf.buf, stderr); - strbuf_release(&buf); - return res; - } - return run_command(&cmd); } diff --git a/run-command.h b/run-command.h index 65a21ddd4e..dd1c78c28d 100644 --- a/run-command.h +++ b/run-command.h @@ -72,7 +72,6 @@ extern int run_hook_ve(const char *const *env, const char *name, va_list args); #define RUN_SILENT_EXEC_FAILURE 8 #define RUN_USING_SHELL 16 #define RUN_CLEAN_ON_EXIT 32 -#define RUN_HIDE_STDERR_ON_SUCCESS 64 int run_command_v_opt(const char **argv, int opt); /* diff --git a/sequencer.c b/sequencer.c index 984c0fc2aa..30b10ba143 100644 --- a/sequencer.c +++ b/sequencer.c @@ -16,9 +16,6 @@ #include "refs.h" #include "argv-array.h" #include "quote.h" -#include "log-tree.h" -#include "wt-status.h" -#include "hashmap.h" #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@ -31,117 +28,31 @@ static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo") static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts") static GIT_PATH_FUNC(git_path_head_file, "sequencer/head") -static GIT_PATH_FUNC(rebase_path, "rebase-merge") -/* - * The file containing rebase commands, comments, and empty lines. - * This file is created by "git rebase -i" then edited by the user. As - * the lines are processed, they are removed from the front of this - * file and written to the tail of 'done'. - */ -static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo") -/* - * The rebase command lines that have already been processed. A line - * is moved here when it is first handled, before any associated user - * actions. - */ -static GIT_PATH_FUNC(rebase_path_done, "rebase-merge/done") -/* - * The file to keep track of how many commands were already processed (e.g. - * for the prompt). - */ -static GIT_PATH_FUNC(rebase_path_msgnum, "rebase-merge/msgnum"); -/* - * The file to keep track of how many commands are to be processed in total - * (e.g. for the prompt). - */ -static GIT_PATH_FUNC(rebase_path_msgtotal, "rebase-merge/end"); -/* - * The commit message that is planned to be used for any changes that - * need to be committed following a user interaction. - */ -static GIT_PATH_FUNC(rebase_path_message, "rebase-merge/message") -/* - * The file into which is accumulated the suggested commit message for - * squash/fixup commands. When the first of a series of squash/fixups - * is seen, the file is created and the commit message from the - * previous commit and from the first squash/fixup commit are written - * to it. The commit message for each subsequent squash/fixup commit - * is appended to the file as it is processed. - * - * The first line of the file is of the form - * # This is a combination of $count commits. - * where $count is the number of commits whose messages have been - * written to the file so far (including the initial "pick" commit). - * Each time that a commit message is processed, this line is read and - * updated. It is deleted just before the combined commit is made. - */ -static GIT_PATH_FUNC(rebase_path_squash_msg, "rebase-merge/message-squash") -/* - * If the current series of squash/fixups has not yet included a squash - * command, then this file exists and holds the commit message of the - * original "pick" commit. (If the series ends without a "squash" - * command, then this can be used as the commit message of the combined - * commit without opening the editor.) - */ -static GIT_PATH_FUNC(rebase_path_fixup_msg, "rebase-merge/message-fixup") /* * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and * GIT_AUTHOR_DATE that will be used for the commit that is currently * being rebased. */ static GIT_PATH_FUNC(rebase_path_author_script, "rebase-merge/author-script") -/* - * When an "edit" rebase command is being processed, the SHA1 of the - * commit to be edited is recorded in this file. When "git rebase - * --continue" is executed, if there are any staged changes then they - * will be amended to the HEAD commit, but only provided the HEAD - * commit is still the commit to be edited. When any other rebase - * command is processed, this file is deleted. - */ -static GIT_PATH_FUNC(rebase_path_amend, "rebase-merge/amend") -/* - * When we stop at a given patch via the "edit" command, this file contains - * the long commit name of the corresponding patch. - */ -static GIT_PATH_FUNC(rebase_path_stopped_sha, "rebase-merge/stopped-sha") -/* - * For the post-rewrite hook, we make a list of rewritten commits and - * their new sha1s. The rewritten-pending list keeps the sha1s of - * commits that have been processed, but not committed yet, - * e.g. because they are waiting for a 'squash' command. - */ -static GIT_PATH_FUNC(rebase_path_rewritten_list, "rebase-merge/rewritten-list") -static GIT_PATH_FUNC(rebase_path_rewritten_pending, - "rebase-merge/rewritten-pending") /* * The following files are written by git-rebase just after parsing the * command-line (and are only consumed, not modified, by the sequencer). */ static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt") -static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head") -static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose") -static GIT_PATH_FUNC(rebase_path_head_name, "rebase-merge/head-name") -static GIT_PATH_FUNC(rebase_path_onto, "rebase-merge/onto") -static GIT_PATH_FUNC(rebase_path_autostash, "rebase-merge/autostash") -static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy") -static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts") +/* We will introduce the 'interactive rebase' mode later */ static inline int is_rebase_i(const struct replay_opts *opts) { - return opts->action == REPLAY_INTERACTIVE_REBASE; + return 0; } static const char *get_dir(const struct replay_opts *opts) { - if (is_rebase_i(opts)) - return rebase_path(); return git_path_seq_dir(); } static const char *get_todo_path(const struct replay_opts *opts) { - if (is_rebase_i(opts)) - return rebase_path_todo(); return git_path_todo_file(); } @@ -257,15 +168,7 @@ int sequencer_remove_state(struct replay_opts *opts) static const char *action_name(const struct replay_opts *opts) { - switch (opts->action) { - case REPLAY_REVERT: - return N_("revert"); - case REPLAY_PICK: - return N_("cherry-pick"); - case REPLAY_INTERACTIVE_REBASE: - return N_("rebase -i"); - } - die(_("Unknown action: %d"), opts->action); + return opts->action == REPLAY_REVERT ? N_("revert") : N_("cherry-pick"); } struct commit_message { @@ -475,8 +378,6 @@ static int do_recursive_merge(struct commit *base, struct commit *next, o.ancestor = base ? base_label : "(empty tree)"; o.branch1 = "HEAD"; o.branch2 = next ? next_label : "(empty tree)"; - if (is_rebase_i(opts)) - o.buffer_output = 2; head_tree = parse_tree_indirect(head); next_tree = next ? next->tree : empty_tree(); @@ -488,18 +389,13 @@ static int do_recursive_merge(struct commit *base, struct commit *next, clean = merge_trees(&o, head_tree, next_tree, base_tree, &result); - if (is_rebase_i(opts) && clean <= 0) - fputs(o.obuf.buf, stdout); strbuf_release(&o.obuf); if (clean < 0) return clean; if (active_cache_changed && write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) - /* - * TRANSLATORS: %s will be "revert", "cherry-pick" or - * "rebase -i". - */ + /* TRANSLATORS: %s will be "revert" or "cherry-pick" */ return error(_("%s: Unable to write new index file"), _(action_name(opts))); rollback_lock_file(&index_lock); @@ -544,53 +440,6 @@ static int is_index_unchanged(void) return !hashcmp(active_cache_tree->sha1, head_commit->tree->object.oid.hash); } -static int write_author_script(const char *message) -{ - struct strbuf buf = STRBUF_INIT; - const char *eol; - int res; - - for (;;) - if (!*message || starts_with(message, "\n")) { -missing_author: - /* Missing 'author' line? */ - unlink(rebase_path_author_script()); - return 0; - } - else if (skip_prefix(message, "author ", &message)) - break; - else if ((eol = strchr(message, '\n'))) - message = eol + 1; - else - goto missing_author; - - strbuf_addstr(&buf, "GIT_AUTHOR_NAME='"); - while (*message && *message != '\n' && *message != '\r') - if (skip_prefix(message, " <", &message)) - break; - else if (*message != '\'') - strbuf_addch(&buf, *(message++)); - else - strbuf_addf(&buf, "'\\\\%c'", *(message++)); - strbuf_addstr(&buf, "'\nGIT_AUTHOR_EMAIL='"); - while (*message && *message != '\n' && *message != '\r') - if (skip_prefix(message, "> ", &message)) - break; - else if (*message != '\'') - strbuf_addch(&buf, *(message++)); - else - strbuf_addf(&buf, "'\\\\%c'", *(message++)); - strbuf_addstr(&buf, "'\nGIT_AUTHOR_DATE='@"); - while (*message && *message != '\n' && *message != '\r') - if (*message != '\'') - strbuf_addch(&buf, *(message++)); - else - strbuf_addf(&buf, "'\\\\%c'", *(message++)); - res = write_message(buf.buf, buf.len, rebase_path_author_script(), 1); - strbuf_release(&buf); - return res; -} - /* * Read the author-script file into an environment block, ready for use in * run_command(), that can be free()d afterwards. @@ -662,15 +511,10 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts, { char **env = NULL; struct argv_array array; - int opt = RUN_GIT_CMD, rc; + int rc; const char *value; if (is_rebase_i(opts)) { - if (!edit) { - opt |= RUN_COMMAND_STDOUT_TO_STDERR; - opt |= RUN_HIDE_STDERR_ON_SUCCESS; - } - env = read_author_script(); if (!env) { const char *gpg_opt = gpg_sign_opt_quoted(opts); @@ -707,7 +551,7 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts, if (opts->allow_empty_message) argv_array_push(&array, "--allow-empty-message"); - rc = run_command_v_opt_cd_env(array.argv, opt, NULL, + rc = run_command_v_opt_cd_env(array.argv, RUN_GIT_CMD, NULL, (const char *const *)env); argv_array_clear(&array); free(env); @@ -773,201 +617,33 @@ static int allow_empty(struct replay_opts *opts, struct commit *commit) return 1; } -/* - * Note that ordering matters in this enum. Not only must it match the mapping - * below, it is also divided into several sections that matter. When adding - * new commands, make sure you add it in the right section. - */ enum todo_command { - /* commands that handle commits */ TODO_PICK = 0, - TODO_REVERT, - TODO_EDIT, - TODO_REWORD, - TODO_FIXUP, - TODO_SQUASH, - /* commands that do something else than handling a single commit */ - TODO_EXEC, - /* commands that do nothing but are counted for reporting progress */ - TODO_NOOP, - TODO_DROP, - /* comments (not counted for reporting progress) */ - TODO_COMMENT + TODO_REVERT }; -static struct { - char c; - const char *str; -} todo_command_info[] = { - { 'p', "pick" }, - { 0, "revert" }, - { 'e', "edit" }, - { 'r', "reword" }, - { 'f', "fixup" }, - { 's', "squash" }, - { 'x', "exec" }, - { 0, "noop" }, - { 'd', "drop" }, - { 0, NULL } +static const char *todo_command_strings[] = { + "pick", + "revert" }; static const char *command_to_string(const enum todo_command command) { - if (command < TODO_COMMENT) - return todo_command_info[command].str; + if ((size_t)command < ARRAY_SIZE(todo_command_strings)) + return todo_command_strings[command]; die("Unknown command: %d", command); } -static int is_fixup(enum todo_command command) -{ - return command == TODO_FIXUP || command == TODO_SQUASH; -} - -static int update_squash_messages(enum todo_command command, - struct commit *commit, struct replay_opts *opts) -{ - struct strbuf buf = STRBUF_INIT; - int count, res; - const char *message, *body; - - if (file_exists(rebase_path_squash_msg())) { - char *p, *p2; - - if (strbuf_read_file(&buf, rebase_path_squash_msg(), 2048) <= 0) - return error(_("could not read '%s'"), - rebase_path_squash_msg()); - - if (buf.buf[0] == '\n' || !skip_prefix(buf.buf + 1, - " This is a combination of ", - (const char **)&p)) - return error(_("unexpected 1st line of squash message:" - "\n\n\t%.*s"), - (int)(strchrnul(buf.buf, '\n') - buf.buf), - buf.buf); - count = strtol(p, &p2, 10); - - if (count < 1 || *p2 != ' ') - return error(_("invalid 1st line of squash message:\n" - "\n\t%.*s"), - (int)(strchrnul(buf.buf, '\n') - buf.buf), - buf.buf); - - sprintf((char *)p, "%d", ++count); - if (!*p2) - *p2 = ' '; - else { - *(++p2) = 'c'; - strbuf_insert(&buf, p2 - buf.buf, " ", 1); - } - } - else { - unsigned char head[20]; - struct commit *head_commit; - const char *head_message, *body; - - if (get_sha1("HEAD", head)) - return error(_("need a HEAD to fixup")); - if (!(head_commit = lookup_commit_reference(head))) - return error(_("could not read HEAD")); - if (!(head_message = get_commit_buffer(head_commit, NULL))) - return error(_("could not read HEAD's commit message")); - - body = strstr(head_message, "\n\n"); - if (!body) - body = ""; - else - body = skip_blank_lines(body + 2); - if (write_message(body, strlen(body), - rebase_path_fixup_msg(), 0)) - return error(_("cannot write '%s'"), - rebase_path_fixup_msg()); - - count = 2; - strbuf_addf(&buf, _("%c This is a combination of 2 commits.\n" - "%c The first commit's message is:\n\n%s"), - comment_line_char, comment_line_char, body); - - unuse_commit_buffer(head_commit, head_message); - } - - if (!(message = get_commit_buffer(commit, NULL))) - return error(_("could not read commit message of %s"), - oid_to_hex(&commit->object.oid)); - body = strstr(message, "\n\n"); - if (!body) - body = ""; - else - body = skip_blank_lines(body + 2); - - if (command == TODO_SQUASH) { - unlink(rebase_path_fixup_msg()); - strbuf_addf(&buf, _("\n%c This is the commit message #%d:\n" - "\n%s"), - comment_line_char, count, body); - } - else if (command == TODO_FIXUP) { - strbuf_addf(&buf, _("\n%c The commit message #%d " - "will be skipped:\n\n"), - comment_line_char, count); - strbuf_add_commented_lines(&buf, body, strlen(body)); - } - else - return error(_("unknown command: %d"), command); - unuse_commit_buffer(commit, message); - - res = write_message(buf.buf, buf.len, rebase_path_squash_msg(), 0); - strbuf_release(&buf); - return res; -} - -static void flush_rewritten_pending(void) { - struct strbuf buf = STRBUF_INIT; - unsigned char newsha1[20]; - FILE *out; - - if (strbuf_read_file(&buf, rebase_path_rewritten_pending(), 82) > 0 && - !get_sha1("HEAD", newsha1) && - (out = fopen(rebase_path_rewritten_list(), "a"))) { - char *bol = buf.buf, *eol; - - while (*bol) { - eol = strchrnul(bol, '\n'); - fprintf(out, "%.*s %s\n", (int)(eol - bol), - bol, sha1_to_hex(newsha1)); - if (!*eol) - break; - bol = eol + 1; - } - fclose(out); - unlink(rebase_path_rewritten_pending()); - } -} - -static void record_in_rewritten(struct object_id *oid, - enum todo_command next_command) { - FILE *out = fopen(rebase_path_rewritten_pending(), "a"); - - if (!out) - return; - - fprintf(out, "%s\n", oid_to_hex(oid)); - fclose(out); - - if (!is_fixup(next_command)) - flush_rewritten_pending(); -} static int do_pick_commit(enum todo_command command, struct commit *commit, - struct replay_opts *opts, int final_fixup) + struct replay_opts *opts) { - int edit = opts->edit, cleanup_commit_message = 0; - const char *msg_file = edit ? NULL : git_path_merge_msg(); unsigned char head[20]; struct commit *base, *next, *parent; const char *base_label, *next_label; struct commit_message msg = { NULL, NULL, NULL, NULL }; struct strbuf msgbuf = STRBUF_INIT; - int res, unborn = 0, amend = 0, allow = 0; + int res, unborn = 0, allow; if (opts->no_commit) { /* @@ -1013,23 +689,11 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, else parent = commit->parents->item; - if (get_message(commit, &msg) != 0) - return error(_("cannot get commit message for %s"), - oid_to_hex(&commit->object.oid)); - - if (opts->allow_ff && !is_fixup(command) && + if (opts->allow_ff && ((parent && !hashcmp(parent->object.oid.hash, head)) || - (!parent && unborn))) { - if (is_rebase_i(opts)) - write_author_script(msg.message); - res = fast_forward_to(commit->object.oid.hash, head, unborn, - opts); - if (res || command != TODO_REWORD) - goto leave; - edit = amend = 1; - msg_file = NULL; - goto fast_forward_edit; - } + (!parent && unborn))) + return fast_forward_to(commit->object.oid.hash, head, unborn, opts); + if (parent && parse_commit(parent) < 0) /* TRANSLATORS: The first %s will be a "todo" command like "revert" or "pick", the second %s a SHA1. */ @@ -1037,6 +701,10 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, command_to_string(command), oid_to_hex(&parent->object.oid)); + if (get_message(commit, &msg) != 0) + return error(_("cannot get commit message for %s"), + oid_to_hex(&commit->object.oid)); + /* * "commit" is an existing commit. We would want to apply * the difference it introduces since its first parent "prev" @@ -1085,33 +753,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } } - if (command == TODO_REWORD) - edit = 1; - else if (is_fixup(command)) { - if (update_squash_messages(command, commit, opts)) - return -1; - amend = 1; - if (!final_fixup) - msg_file = rebase_path_squash_msg(); - else if (file_exists(rebase_path_fixup_msg())) { - cleanup_commit_message = 1; - msg_file = rebase_path_fixup_msg(); - } - else { - const char *dest = git_path("SQUASH_MSG"); - unlink(dest); - if (copy_file(dest, rebase_path_squash_msg(), 0666)) - return error(_("could not rename '%s' to '%s'"), - rebase_path_squash_msg(), dest); - unlink(git_path("MERGE_MSG")); - msg_file = dest; - edit = 1; - } - } - - if (is_rebase_i(opts) && write_author_script(msg.message) < 0) - res = -1; - else if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) { + if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) { res = do_recursive_merge(base, next, base_label, next_label, head, &msgbuf, opts); if (res < 0) @@ -1166,14 +808,8 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, goto leave; } if (!opts->no_commit) -fast_forward_edit: - res = run_git_commit(msg_file, opts, allow, edit, amend, - cleanup_commit_message); - - if (!res && final_fixup) { - unlink(rebase_path_fixup_msg()); - unlink(rebase_path_squash_msg()); - } + res = run_git_commit(opts->edit ? NULL : git_path_merge_msg(), + opts, allow, opts->edit, 0, 0); leave: free_message(commit, &msg); @@ -1231,7 +867,6 @@ struct todo_list { struct strbuf buf; struct todo_item *items; int nr, alloc, current; - int done_nr, total_nr; }; #define TODO_LIST_INIT { STRBUF_INIT } @@ -1259,46 +894,20 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) /* left-trim */ bol += strspn(bol, " \t"); - if (bol == eol || *bol == '\r' || *bol == comment_line_char) { - item->command = TODO_COMMENT; - item->commit = NULL; - item->arg = bol; - item->arg_len = eol - bol; - return 0; - } - - for (i = 0; i < TODO_COMMENT; i++) - if (skip_prefix(bol, todo_command_info[i].str, &bol)) { + for (i = 0; i < ARRAY_SIZE(todo_command_strings); i++) + if (skip_prefix(bol, todo_command_strings[i], &bol)) { item->command = i; break; } - else if (bol[1] == ' ' && *bol == todo_command_info[i].c) { - bol++; - item->command = i; - break; - } - if (i >= TODO_COMMENT) + if (i >= ARRAY_SIZE(todo_command_strings)) return -1; - if (item->command == TODO_NOOP) { - item->commit = NULL; - item->arg = bol; - item->arg_len = eol - bol; - return 0; - } - /* Eat up extra spaces/ tabs before object name */ padding = strspn(bol, " \t"); if (!padding) return -1; bol += padding; - if (item->command == TODO_EXEC) { - item->arg = bol; - item->arg_len = (int)(eol - bol); - return 0; - } - end_of_object_name = (char *) bol + strcspn(bol, " \t\n"); saved = *end_of_object_name; *end_of_object_name = '\0'; @@ -1319,7 +928,7 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list) { struct todo_item *item; char *p = buf, *next_p; - int i, res = 0, fixup_okay = file_exists(rebase_path_done()); + int i, res = 0; for (i = 1; *p; i++, p = next_p) { char *eol = strchrnul(p, '\n'); @@ -1334,32 +943,14 @@ static int parse_insn_buffer(char *buf, struct todo_list *todo_list) if (parse_insn_line(item, p, eol)) { res = error(_("invalid line %d: %.*s"), i, (int)(eol - p), p); - item->command = TODO_NOOP; + item->command = -1; } - - if (fixup_okay) - ; /* do nothing */ - else if (is_fixup(item->command)) - return error(_("cannot '%s' without a previous commit"), - command_to_string(item->command)); - else if (item->command < TODO_NOOP) - fixup_okay = 1; } - + if (!todo_list->nr) + return error(_("no commits parsed.")); return res; } -static int count_commands(struct todo_list *todo_list) -{ - int count = 0, i; - - for (i = 0; i < todo_list->nr; i++) - if (todo_list->items[i].command != TODO_COMMENT) - count++; - - return count; -} - static int read_populate_todo(struct todo_list *todo_list, struct replay_opts *opts) { @@ -1377,16 +968,8 @@ static int read_populate_todo(struct todo_list *todo_list, close(fd); res = parse_insn_buffer(todo_list->buf.buf, todo_list); - if (res) { - if (is_rebase_i(opts)) - return error(_("please fix this using " - "'git rebase --edit-todo'.")); + if (res) return error(_("unusable instruction sheet: '%s'"), todo_file); - } - - if (!todo_list->nr && - (!is_rebase_i(opts) || !file_exists(rebase_path_done()))) - return error(_("no commits parsed.")); if (!is_rebase_i(opts)) { enum todo_command valid = @@ -1402,26 +985,6 @@ static int read_populate_todo(struct todo_list *todo_list, return error(_("cannot revert during a cherry-pick.")); } - if (is_rebase_i(opts)) { - struct todo_list done = TODO_LIST_INIT; - FILE *f = fopen(rebase_path_msgtotal(), "w"); - - if (strbuf_read_file(&done.buf, rebase_path_done(), 0) > 0 && - !parse_insn_buffer(done.buf.buf, &done)) - todo_list->done_nr = count_commands(&done); - else - todo_list->done_nr = 0; - todo_list_release(&done); - - todo_list->total_nr = todo_list->done_nr - + count_commands(todo_list); - - if (f) { - fprintf(f, "%d\n", todo_list->total_nr); - fclose(f); - } - } - return 0; } @@ -1470,26 +1033,6 @@ static int populate_opts_cb(const char *key, const char *value, void *data) return 0; } -static void read_strategy_opts(struct replay_opts *opts, struct strbuf *buf) -{ - int i; - - strbuf_reset(buf); - if (!read_oneliner(buf, rebase_path_strategy(), 0)) - return; - opts->strategy = strbuf_detach(buf, NULL); - if (!read_oneliner(buf, rebase_path_strategy_opts(), 0)) - return; - - opts->xopts_nr = split_cmdline(buf->buf, (const char ***)&opts->xopts); - for (i = 0; i < opts->xopts_nr; i++) { - const char *arg = opts->xopts[i]; - - skip_prefix(arg, "--", &arg); - opts->xopts[i] = xstrdup(arg); - } -} - static int read_populate_opts(struct replay_opts *opts) { if (is_rebase_i(opts)) { @@ -1503,11 +1046,6 @@ static int read_populate_opts(struct replay_opts *opts) opts->gpg_sign = xstrdup(buf.buf + 2); } } - - if (file_exists(rebase_path_verbose())) - opts->verbose = 1; - - read_strategy_opts(opts, &buf); strbuf_release(&buf); return 0; @@ -1532,7 +1070,7 @@ static int walk_revs_populate_todo(struct todo_list *todo_list, { enum todo_command command = opts->action == REPLAY_PICK ? TODO_PICK : TODO_REVERT; - const char *command_string = todo_command_info[command].str; + const char *command_string = todo_command_strings[command]; struct commit *commit; if (prepare_revs(opts)) @@ -1666,9 +1204,6 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts) const char *todo_path = get_todo_path(opts); int next = todo_list->current, offset, fd; - if (is_rebase_i(opts)) - next++; - fd = hold_lock_file_for_update(&todo_lock, todo_path, 0); if (fd < 0) return error_errno(_("could not lock '%s'"), todo_path); @@ -1679,20 +1214,6 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts) return error_errno(_("could not write to '%s'"), todo_path); if (commit_lock_file(&todo_lock) < 0) return error(_("failed to finalize '%s'."), todo_path); - - if (is_rebase_i(opts)) { - const char *done_path = rebase_path_done(); - int fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666); - int prev_offset = !next ? 0 : - todo_list->items[next - 1].offset_in_buf; - - if (offset > prev_offset && write_in_full(fd, - todo_list->buf.buf + prev_offset, - offset - prev_offset) < 0) - return error_errno(_("could not write to '%s'"), - done_path); - close(fd); - } return 0; } @@ -1731,229 +1252,9 @@ static int save_opts(struct replay_opts *opts) return res; } -static int make_patch(struct commit *commit, struct replay_opts *opts) -{ - struct strbuf buf = STRBUF_INIT; - struct rev_info log_tree_opt; - const char *commit_buffer = get_commit_buffer(commit, NULL), *subject, *p; - int res = 0; - - p = short_commit_name(commit); - if (write_message(p, strlen(p), rebase_path_stopped_sha(), 1) < 0) - return -1; - - strbuf_addf(&buf, "%s/patch", get_dir(opts)); - memset(&log_tree_opt, 0, sizeof(log_tree_opt)); - init_revisions(&log_tree_opt, NULL); - log_tree_opt.abbrev = 0; - log_tree_opt.diff = 1; - log_tree_opt.diffopt.output_format = DIFF_FORMAT_PATCH; - log_tree_opt.disable_stdin = 1; - log_tree_opt.no_commit_id = 1; - log_tree_opt.diffopt.file = fopen(buf.buf, "w"); - log_tree_opt.diffopt.use_color = GIT_COLOR_NEVER; - if (!log_tree_opt.diffopt.file) - res |= error_errno(_("could not open '%s'"), buf.buf); - else { - res |= log_tree_commit(&log_tree_opt, commit); - fclose(log_tree_opt.diffopt.file); - } - strbuf_reset(&buf); - - strbuf_addf(&buf, "%s/message", get_dir(opts)); - if (!file_exists(buf.buf)) { - find_commit_subject(commit_buffer, &subject); - res |= write_message(subject, strlen(subject), buf.buf, 1); - unuse_commit_buffer(commit, commit_buffer); - } - strbuf_release(&buf); - - return res; -} - -static int intend_to_amend(void) -{ - unsigned char head[20]; - char *p; - - if (get_sha1("HEAD", head)) - return error(_("cannot read HEAD")); - - p = sha1_to_hex(head); - return write_message(p, strlen(p), rebase_path_amend(), 1); -} - -static int error_with_patch(struct commit *commit, - const char *subject, int subject_len, - struct replay_opts *opts, int exit_code, int to_amend) -{ - if (make_patch(commit, opts)) - return -1; - - if (to_amend) { - if (intend_to_amend()) - return -1; - - fprintf(stderr, "You can amend the commit now, with\n" - "\n" - " git commit --amend %s\n" - "\n" - "Once you are satisfied with your changes, run\n" - "\n" - " git rebase --continue\n", gpg_sign_opt_quoted(opts)); - } - else if (exit_code) - fprintf(stderr, "Could not apply %s... %.*s\n", - short_commit_name(commit), subject_len, subject); - - return exit_code; -} - -static int error_failed_squash(struct commit *commit, - struct replay_opts *opts, int subject_len, const char *subject) -{ - if (rename(rebase_path_squash_msg(), rebase_path_message())) - return error(_("could not rename '%s' to '%s'"), - rebase_path_squash_msg(), rebase_path_message()); - unlink(rebase_path_fixup_msg()); - unlink(git_path("MERGE_MSG")); - if (copy_file(git_path("MERGE_MSG"), rebase_path_message(), 0666)) - return error(_("could not copy '%s' to '%s'"), - rebase_path_message(), git_path("MERGE_MSG")); - return error_with_patch(commit, subject, subject_len, opts, 1, 0); -} - -static int do_exec(const char *command_line) -{ - const char *child_argv[] = { NULL, NULL }; - int dirty, status; - - fprintf(stderr, "Executing: %s\n", command_line); - child_argv[0] = command_line; - status = run_command_v_opt(child_argv, RUN_USING_SHELL); - - /* force re-reading of the cache */ - if (discard_cache() < 0 || read_cache() < 0) - return error(_("could not read index")); - - dirty = require_clean_work_tree("rebase", NULL, 1, 1); - - if (status) { - warning(_("execution failed: %s\n%s" - "You can fix the problem, and then run\n" - "\n" - " git rebase --continue\n" - "\n"), - command_line, - dirty ? N_("and made changes to the index and/or the " - "working tree\n") : ""); - if (status == 127) - /* command not found */ - status = 1; - } - else if (dirty) { - warning(_("execution succeeded: %s\nbut " - "left changes to the index and/or the working tree\n" - "Commit or stash your changes, and then run\n" - "\n" - " git rebase --continue\n" - "\n"), command_line); - status = 1; - } - - return status; -} - -static int is_final_fixup(struct todo_list *todo_list) -{ - int i = todo_list->current; - - if (!is_fixup(todo_list->items[i].command)) - return 0; - - while (++i < todo_list->nr) - if (is_fixup(todo_list->items[i].command)) - return 0; - else if (todo_list->items[i].command < TODO_NOOP) - break; - return 1; -} - -static enum todo_command peek_command(struct todo_list *todo_list, int offset) -{ - int i; - - for (i = todo_list->current + offset; i < todo_list->nr; i++) - if (todo_list->items[i].command < TODO_NOOP) - return todo_list->items[i].command; - - return -1; -} - -static int apply_autostash(struct replay_opts *opts) -{ - struct strbuf stash_sha1 = STRBUF_INIT; - struct child_process child = CHILD_PROCESS_INIT; - int ret = 0; - - if (!read_oneliner(&stash_sha1, rebase_path_autostash(), 1)) { - strbuf_release(&stash_sha1); - return 0; - } - strbuf_trim(&stash_sha1); - - child.git_cmd = 1; - argv_array_push(&child.args, "stash"); - argv_array_push(&child.args, "apply"); - argv_array_push(&child.args, stash_sha1.buf); - if (!run_command(&child)) - printf(_("Applied autostash.")); - else { - struct child_process store = CHILD_PROCESS_INIT; - - store.git_cmd = 1; - argv_array_push(&store.args, "stash"); - argv_array_push(&store.args, "store"); - argv_array_push(&store.args, "-m"); - argv_array_push(&store.args, "autostash"); - argv_array_push(&store.args, "-q"); - argv_array_push(&store.args, stash_sha1.buf); - if (run_command(&store)) - ret = error(_("cannot store %s"), stash_sha1.buf); - else - printf(_("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(&stash_sha1); - return ret; -} - -static const char *reflog_message(struct replay_opts *opts, - const char *sub_action, const char *fmt, ...) -{ - va_list ap; - static struct strbuf buf = STRBUF_INIT; - - va_start(ap, fmt); - strbuf_reset(&buf); - strbuf_addstr(&buf, action_name(opts)); - if (sub_action) - strbuf_addf(&buf, " (%s)", sub_action); - if (fmt) { - strbuf_addstr(&buf, ": "); - strbuf_vaddf(&buf, fmt, ap); - } - va_end(ap); - - return buf.buf; -} - static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) { - int res = 0; + int res; setenv(GIT_REFLOG_ACTION, action_name(opts), 0); if (opts->allow_ff) @@ -1966,161 +1267,12 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) struct todo_item *item = todo_list->items + todo_list->current; if (save_todo(todo_list, opts)) return -1; - if (is_rebase_i(opts)) { - if (item->command != TODO_COMMENT) { - FILE *f = fopen(rebase_path_msgnum(), "w"); - - todo_list->done_nr++; - - if (f) { - fprintf(f, "%d\n", todo_list->done_nr); - fclose(f); - } - fprintf(stderr, "Rebasing (%d/%d)%s", - todo_list->done_nr, - todo_list->total_nr, - opts->verbose ? "\n" : "\r"); - } - unlink(rebase_path_message()); - unlink(rebase_path_author_script()); - unlink(rebase_path_stopped_sha()); - unlink(rebase_path_amend()); - } - if (item->command <= TODO_SQUASH) { - if (is_rebase_i(opts)) - setenv("GIT_REFLOG_ACTION", reflog_message(opts, - command_to_string(item->command), NULL), - 1); - res = do_pick_commit(item->command, item->commit, - opts, is_final_fixup(todo_list)); - if (is_rebase_i(opts) && res < 0) { - /* Reschedule */ - todo_list->current--; - if (save_todo(todo_list, opts)) - return -1; - } - if (item->command == TODO_EDIT) { - struct commit *commit = item->commit; - if (!res) - warning(_("stopped at %s... %.*s"), - short_commit_name(commit), - item->arg_len, item->arg); - return error_with_patch(commit, - item->arg, item->arg_len, opts, res, - !res); - } - if (is_rebase_i(opts) && !res) - record_in_rewritten(&item->commit->object.oid, - peek_command(todo_list, 1)); - if (res && is_fixup(item->command)) { - if (res == 1) - intend_to_amend(); - return error_failed_squash(item->commit, opts, - item->arg_len, item->arg); - } - else if (res && is_rebase_i(opts)) - return res | error_with_patch(item->commit, - item->arg, item->arg_len, opts, res, - item->command == TODO_REWORD); - } - else if (item->command == TODO_EXEC) { - char *end_of_arg = (char *)(item->arg + item->arg_len); - int saved = *end_of_arg; - - *end_of_arg = '\0'; - res = do_exec(item->arg); - *end_of_arg = saved; - } - else if (item->command < TODO_NOOP) - return error(_("unknown command %d"), item->command); - + res = do_pick_commit(item->command, item->commit, opts); todo_list->current++; if (res) return res; } - if (is_rebase_i(opts)) { - struct strbuf head_ref = STRBUF_INIT, buf = STRBUF_INIT; - struct stat st; - - /* Stopped in the middle, as planned? */ - if (todo_list->current < todo_list->nr) - return 0; - - if (read_oneliner(&head_ref, rebase_path_head_name(), 0) && - starts_with(head_ref.buf, "refs/")) { - const char *msg; - unsigned char head[20], orig[20]; - - if (get_sha1("HEAD", head)) - return error(_("cannot read HEAD")); - if (!read_oneliner(&buf, rebase_path_orig_head(), 0) || - get_sha1_hex(buf.buf, orig)) - return error(_("could not read orig-head")); - if (!read_oneliner(&buf, rebase_path_onto(), 0)) - return error(_("could not read 'onto'")); - msg = reflog_message(opts, "finish", "%s onto %s", - head_ref.buf, buf.buf); - if (update_ref(msg, head_ref.buf, head, orig, - REF_NODEREF, UPDATE_REFS_MSG_ON_ERR)) - return error(_("could not update %s"), - head_ref.buf); - msg = reflog_message(opts, "finish", "returning to %s", - head_ref.buf); - if (create_symref("HEAD", head_ref.buf, msg)) - return error(_("could not update HEAD to %s"), - head_ref.buf); - strbuf_reset(&buf); - } - - if (opts->verbose) { - const char *argv[] = { - "diff-tree", "--stat", NULL, NULL - }; - - if (!read_oneliner(&buf, rebase_path_orig_head(), 0)) - return error(_("could not read '%s'"), - rebase_path_orig_head()); - strbuf_addstr(&buf, "..HEAD"); - argv[2] = buf.buf; - run_command_v_opt(argv, RUN_GIT_CMD); - strbuf_reset(&buf); - } - flush_rewritten_pending(); - if (!stat(rebase_path_rewritten_list(), &st) && - st.st_size > 0) { - struct child_process child = CHILD_PROCESS_INIT; - const char *post_rewrite_hook = - find_hook("post-rewrite"); - - child.in = open(rebase_path_rewritten_list(), O_RDONLY); - child.git_cmd = 1; - argv_array_push(&child.args, "notes"); - argv_array_push(&child.args, "copy"); - argv_array_push(&child.args, "--for-rewrite=rebase"); - /* we don't care if this copying failed */ - run_command(&child); - - if (post_rewrite_hook) { - struct child_process hook = CHILD_PROCESS_INIT; - - hook.in = open(rebase_path_rewritten_list(), - O_RDONLY); - argv_array_push(&hook.args, post_rewrite_hook); - argv_array_push(&hook.args, "rebase"); - /* we don't care if this hook failed */ - run_command(&hook); - } - } - apply_autostash(opts); - - fprintf(stderr, "Successfully rebased and updated %s.\n", - head_ref.buf); - - strbuf_release(&buf); - strbuf_release(&head_ref); - } - /* * Sequence of picks finished successfully; cleanup by * removing the .git/sequencer directory @@ -2138,47 +1290,6 @@ static int continue_single_pick(void) return run_command_v_opt(argv, RUN_GIT_CMD); } -static int commit_staged_changes(struct replay_opts *opts) -{ - int amend = 0; - - if (has_unstaged_changes(1)) - return error(_("cannot rebase: You have unstaged changes.")); - if (!has_uncommitted_changes(0)) { - const char *cherry_pick_head = git_path("CHERRY_PICK_HEAD"); - - if (file_exists(cherry_pick_head) && unlink(cherry_pick_head)) - return error(_("could not remove CHERRY_PICK_HEAD")); - return 0; - } - - if (file_exists(rebase_path_amend())) { - struct strbuf rev = STRBUF_INIT; - unsigned char head[20], to_amend[20]; - - if (get_sha1("HEAD", head)) - return error(_("cannot amend non-existing commit")); - if (!read_oneliner(&rev, rebase_path_amend(), 0)) - return error(_("invalid file: '%s'"), rebase_path_amend()); - if (get_sha1_hex(rev.buf, to_amend)) - return error(_("invalid contents: '%s'"), - rebase_path_amend()); - if (hashcmp(head, to_amend)) - return error(_("\nYou have uncommitted changes in your " - "working tree. Please, commit them\n" - "first and then run 'git rebase " - "--continue' again.")); - - strbuf_release(&rev); - amend = 1; - } - - if (run_git_commit(rebase_path_message(), opts, 1, 1, amend, 0)) - return error(_("could not commit staged changes.")); - unlink(rebase_path_amend()); - return 0; -} - int sequencer_continue(struct replay_opts *opts) { struct todo_list todo_list = TODO_LIST_INIT; @@ -2187,43 +1298,25 @@ int sequencer_continue(struct replay_opts *opts) if (read_and_refresh_cache(opts)) return -1; - if (is_rebase_i(opts)) { - if (commit_staged_changes(opts)) - return -1; - } - else if (!file_exists(get_todo_path(opts))) + if (!file_exists(get_todo_path(opts))) return continue_single_pick(); if (read_populate_opts(opts)) return -1; if ((res = read_populate_todo(&todo_list, opts))) goto release_todo_list; - if (!is_rebase_i(opts)) { - /* Verify that the conflict has been resolved */ - if (file_exists(git_path_cherry_pick_head()) || - file_exists(git_path_revert_head())) { - res = continue_single_pick(); - if (res) - goto release_todo_list; - } - if (index_differs_from("HEAD", 0, 0)) { - res = error_dirty_index(opts); + /* Verify that the conflict has been resolved */ + if (file_exists(git_path_cherry_pick_head()) || + file_exists(git_path_revert_head())) { + res = continue_single_pick(); + if (res) goto release_todo_list; - } - todo_list.current++; } - else if (file_exists(rebase_path_stopped_sha())) { - struct strbuf buf = STRBUF_INIT; - struct object_id oid; - - if (read_oneliner(&buf, rebase_path_stopped_sha(), 1)) { - if (!get_sha1_committish(buf.buf, oid.hash)) - record_in_rewritten(&oid, - peek_command(&todo_list, 0)); - } - strbuf_release(&buf); + if (index_differs_from("HEAD", 0, 0)) { + res = error_dirty_index(opts); + goto release_todo_list; } - + todo_list.current++; res = pick_commits(&todo_list, opts); release_todo_list: todo_list_release(&todo_list); @@ -2234,7 +1327,7 @@ static int single_pick(struct commit *cmit, struct replay_opts *opts) { setenv(GIT_REFLOG_ACTION, action_name(opts), 0); return do_pick_commit(opts->action == REPLAY_PICK ? - TODO_PICK : TODO_REVERT, cmit, opts, 0); + TODO_PICK : TODO_REVERT, cmit, opts); } int sequencer_pick_revisions(struct replay_opts *opts) @@ -2369,518 +1462,3 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag) strbuf_release(&sob); } - -int sequencer_make_script(int keep_empty, FILE *out, - int argc, const char **argv) -{ - char *format = "%s"; - struct pretty_print_context pp = {0}; - struct strbuf buf = STRBUF_INIT; - struct rev_info revs; - struct commit *commit; - - init_revisions(&revs, NULL); - revs.verbose_header = 1; - revs.max_parents = 1; - revs.cherry_pick = 1; - revs.limited = 1; - revs.reverse = 1; - revs.right_only = 1; - revs.sort_order = REV_SORT_IN_GRAPH_ORDER; - revs.topo_order = 1; - - revs.pretty_given = 1; - git_config_get_string("rebase.instructionFormat", &format); - get_commit_format(format, &revs); - pp.fmt = revs.commit_format; - pp.output_encoding = get_log_output_encoding(); - - if (setup_revisions(argc, argv, &revs, NULL) > 1) - return error(_("make_script: unhandled options")); - - if (prepare_revision_walk(&revs) < 0) - return error(_("make_script: error preparing revisions")); - - while ((commit = get_revision(&revs))) { - strbuf_reset(&buf); - if (!keep_empty && is_original_commit_empty(commit)) - strbuf_addf(&buf, "%c ", comment_line_char); - strbuf_addf(&buf, "pick %s ", oid_to_hex(&commit->object.oid)); - pretty_print_commit(&pp, commit, &buf); - strbuf_addch(&buf, '\n'); - fputs(buf.buf, out); - } - strbuf_release(&buf); - return 0; -} - - -int transform_todo_ids(int shorten_sha1s) -{ - const char *todo_file = rebase_path_todo(); - struct todo_list todo_list = TODO_LIST_INIT; - int fd, res, i; - FILE *out; - - strbuf_reset(&todo_list.buf); - fd = open(todo_file, O_RDONLY); - if (fd < 0) - return error_errno(_("could not open '%s'"), todo_file); - if (strbuf_read(&todo_list.buf, fd, 0) < 0) { - close(fd); - return error(_("could not read '%s'."), todo_file); - } - close(fd); - - res = parse_insn_buffer(todo_list.buf.buf, &todo_list); - if (res) { - todo_list_release(&todo_list); - return error(_("unusable instruction sheet: '%s'"), todo_file); - } - - out = fopen(todo_file, "w"); - if (!out) { - todo_list_release(&todo_list); - return error(_("unable to open '%s' for writing"), todo_file); - } - for (i = 0; i < todo_list.nr; i++) { - struct todo_item *item = todo_list.items + i; - int bol = item->offset_in_buf; - const char *p = todo_list.buf.buf + bol; - int eol = i + 1 < todo_list.nr ? - todo_list.items[i + 1].offset_in_buf : - todo_list.buf.len; - - if (item->command >= TODO_EXEC && item->command != TODO_DROP) - fwrite(p, eol - bol, 1, out); - else { - int eoc = strcspn(p, " \t"); - const char *sha1 = shorten_sha1s ? - short_commit_name(item->commit) : - oid_to_hex(&item->commit->object.oid); - - if (!eoc) { - p += strspn(p, " \t"); - eoc = strcspn(p, " \t"); - } - - fprintf(out, "%.*s %s %.*s\n", - eoc, p, sha1, item->arg_len, item->arg); - } - } - fclose(out); - todo_list_release(&todo_list); - return 0; -} - -enum check_level { - CHECK_IGNORE = 0, CHECK_WARN, CHECK_ERROR -}; - -static enum check_level get_missing_commit_check_level(void) -{ - const char *value; - - if (git_config_get_value("rebase.missingcommitscheck", &value) || - !strcasecmp("ignore", value)) - return CHECK_IGNORE; - if (!strcasecmp("warn", value)) - return CHECK_WARN; - if (!strcasecmp("error", value)) - return CHECK_ERROR; - warning(_("unrecognized setting %s for option" - "rebase.missingCommitsCheck. Ignoring."), value); - return CHECK_IGNORE; -} - -/* - * Check if the user dropped some commits by mistake - * Behaviour determined by rebase.missingCommitsCheck. - * Check if there is an unrecognized command or a - * bad SHA-1 in a command. - */ -int check_todo_list(void) -{ - enum check_level check_level = get_missing_commit_check_level(); - struct strbuf todo_file = STRBUF_INIT; - struct todo_list todo_list = TODO_LIST_INIT; - struct commit_list *missing = NULL; - int raise_error = 0, res = 0, fd, i; - - strbuf_addstr(&todo_file, rebase_path_todo()); - fd = open(todo_file.buf, O_RDONLY); - if (fd < 0) { - res = error_errno(_("could not open '%s'"), todo_file.buf); - goto leave_check; - } - if (strbuf_read(&todo_list.buf, fd, 0) < 0) { - close(fd); - res = error(_("could not read '%s'."), todo_file.buf); - goto leave_check; - } - close(fd); - raise_error = res = - parse_insn_buffer(todo_list.buf.buf, &todo_list); - - if (check_level == CHECK_IGNORE) - goto leave_check; - - /* Get the SHA-1 of the commits */ - for (i = 0; i < todo_list.nr; i++) { - struct commit *commit = todo_list.items[i].commit; - if (commit) - commit->util = todo_list.items + i; - } - - todo_list_release(&todo_list); - strbuf_addstr(&todo_file, ".backup"); - fd = open(todo_file.buf, O_RDONLY); - if (fd < 0) { - res = error_errno(_("could not open '%s'"), todo_file.buf); - goto leave_check; - } - if (strbuf_read(&todo_list.buf, fd, 0) < 0) { - close(fd); - res = error(_("could not read '%s'."), todo_file.buf); - goto leave_check; - } - close(fd); - strbuf_release(&todo_file); - res = !!parse_insn_buffer(todo_list.buf.buf, &todo_list); - - /* Find commits that are missing after editing */ - for (i = 0; i < todo_list.nr; i++) { - struct commit *commit = todo_list.items[i].commit; - if (commit && !commit->util) { - commit_list_insert(commit, &missing); - commit->util = todo_list.items + i; - } - } - - /* Warn about missing commits */ - if (!missing) - goto leave_check; - - if (check_level == CHECK_ERROR) - raise_error = res = 1; - - fprintf(stderr, - _("Warning: some commits may have been dropped accidentally.\n" - "Dropped commits (newer to older):\n")); - - /* Make the list user-friendly and display */ - while (missing) { - struct commit *commit = pop_commit(&missing); - struct todo_item *item = commit->util; - - fprintf(stderr, " - %s %.*s\n", short_commit_name(commit), - item->arg_len, item->arg); - } - free_commit_list(missing); - - fprintf(stderr, _("To avoid this message, use \"drop\" to " - "explicitly remove a commit.\n\n" - "Use 'git config rebase.missingCommitsCheck' to change " - "the level of warnings.\n" - "The possible behaviours are: ignore, warn, error.\n\n")); - -leave_check: - strbuf_release(&todo_file); - todo_list_release(&todo_list); - - if (raise_error) - fprintf(stderr, - _("You can fix this with 'git rebase --edit-todo' " - "and then run 'git rebase --continue'.\n" - "Or you can abort the rebase with 'git rebase" - " --abort'.\n")); - - return res; -} - -/* skip picking commits whose parents are unchanged */ -int skip_unnecessary_picks(void) -{ - const char *todo_file = rebase_path_todo(); - struct strbuf buf = STRBUF_INIT; - struct todo_list todo_list = TODO_LIST_INIT; - struct object_id onto_oid, *oid = &onto_oid, *parent_oid; - int fd, i; - - if (!read_oneliner(&buf, rebase_path_onto(), 0)) - return error(_("could not read 'onto'")); - if (get_sha1(buf.buf, onto_oid.hash)) { - strbuf_release(&buf); - return error(_("need a HEAD to fixup")); - } - strbuf_release(&buf); - - fd = open(todo_file, O_RDONLY); - if (fd < 0) { - return error_errno(_("could not open '%s'"), todo_file); - } - if (strbuf_read(&todo_list.buf, fd, 0) < 0) { - close(fd); - return error(_("could not read '%s'."), todo_file); - } - close(fd); - if (parse_insn_buffer(todo_list.buf.buf, &todo_list) < 0) { - todo_list_release(&todo_list); - return -1; - } - - for (i = 0; i < todo_list.nr; i++) { - struct todo_item *item = todo_list.items + i; - - if (item->command >= TODO_NOOP) - continue; - if (item->command != TODO_PICK) - break; - if (parse_commit(item->commit)) { - todo_list_release(&todo_list); - return error(_("could not parse commit '%s'"), - oid_to_hex(&item->commit->object.oid)); - } - if (!item->commit->parents) - break; /* root commit */ - if (item->commit->parents->next) - break; /* merge commit */ - parent_oid = &item->commit->parents->item->object.oid; - if (hashcmp(parent_oid->hash, oid->hash)) - break; - oid = &item->commit->object.oid; - } - if (i > 0) { - int offset = i < todo_list.nr ? - todo_list.items[i].offset_in_buf : todo_list.buf.len; - const char *done_path = rebase_path_done(); - - fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666); - if (write_in_full(fd, todo_list.buf.buf, offset) < 0) { - todo_list_release(&todo_list); - return error_errno(_("could not write to '%s'"), - done_path); - } - close(fd); - - fd = open(rebase_path_todo(), O_WRONLY, 0666); - if (write_in_full(fd, todo_list.buf.buf + offset, - todo_list.buf.len - offset) < 0) { - todo_list_release(&todo_list); - return error_errno(_("could not write to '%s'"), - rebase_path_todo()); - } - if (ftruncate(fd, todo_list.buf.len - offset) < 0) { - todo_list_release(&todo_list); - return error_errno(_("could not truncate '%s'"), - rebase_path_todo()); - } - close(fd); - - todo_list.current = i; - if (is_fixup(peek_command(&todo_list, 0))) - record_in_rewritten(oid, peek_command(&todo_list, 0)); - } - - todo_list_release(&todo_list); - printf("%s\n", oid_to_hex(oid)); - - return 0; -} - -struct subject2item_entry { - struct hashmap_entry entry; - int i; - char subject[FLEX_ARRAY]; -}; - -static int subject2item_cmp(const struct subject2item_entry *a, - const struct subject2item_entry *b, const void *key) -{ - return key ? strcmp(a->subject, key) : strcmp(a->subject, b->subject); -} - -/* - * Rearrange the todo list that has both "pick sha1 msg" and "pick sha1 - * fixup!/squash! msg" in it so that the latter is put immediately after the - * former, and change "pick" to "fixup"/"squash". - * - * Note that if the config has specified a custom instruction format, each log - * message will have to be retrieved from the commit (as the oneline in the - * script cannot be trusted) in order to normalize the autosquash arrangement. - */ -int rearrange_squash(void) -{ - const char *todo_file = rebase_path_todo(); - struct todo_list todo_list = TODO_LIST_INIT; - struct hashmap subject2item; - int res = 0, rearranged = 0, *next, *tail, fd, i; - char **subjects; - - fd = open(todo_file, O_RDONLY); - if (fd < 0) - return error_errno(_("could not open '%s'"), todo_file); - if (strbuf_read(&todo_list.buf, fd, 0) < 0) { - close(fd); - return error(_("could not read '%s'."), todo_file); - } - close(fd); - if (parse_insn_buffer(todo_list.buf.buf, &todo_list) < 0) { - todo_list_release(&todo_list); - return -1; - } - - /* - * The hashmap maps onelines to the respective todo list index. - * - * If any items need to be rearranged, the next[i] value will indicate - * which item was moved directly after the i'th. - * - * In that case, last[i] will indicate the index of the latest item to - * be moved to appear after the i'th. - */ - hashmap_init(&subject2item, (hashmap_cmp_fn) subject2item_cmp, - todo_list.nr); - ALLOC_ARRAY(next, todo_list.nr); - ALLOC_ARRAY(tail, todo_list.nr); - ALLOC_ARRAY(subjects, todo_list.nr); - for (i = 0; i < todo_list.nr; i++) { - struct strbuf buf = STRBUF_INIT; - struct todo_item *item = todo_list.items + i; - const char *commit_buffer, *subject, *p; - int i2 = -1; - struct subject2item_entry *entry; - - next[i] = tail[i] = -1; - if (item->command >= TODO_EXEC) { - subjects[i] = NULL; - continue; - } - - if (is_fixup(item->command)) { - todo_list_release(&todo_list); - return error(_("the script was already rearranged.")); - } - - item->commit->util = item; - - parse_commit(item->commit); - commit_buffer = get_commit_buffer(item->commit, NULL); - find_commit_subject(commit_buffer, &subject); - format_subject(&buf, subject, " "); - subject = subjects[i] = buf.buf; - unuse_commit_buffer(item->commit, commit_buffer); - if ((skip_prefix(subject, "fixup! ", &p) || - skip_prefix(subject, "squash! ", &p))) { - struct commit *commit2; - - for (;;) { - while (isspace(*p)) - p++; - if (!skip_prefix(p, "fixup! ", &p) && - !skip_prefix(p, "squash! ", &p)) - break; - } - - if ((entry = hashmap_get_from_hash(&subject2item, - strhash(p), p))) - /* found by title */ - i2 = entry->i; - else if (!strchr(p, ' ') && - (commit2 = - lookup_commit_reference_by_name(p)) && - commit2->util) - /* found by commit name */ - i2 = (struct todo_item *)commit2->util - - todo_list.items; - else { - /* copy can be a prefix of the commit subject */ - for (i2 = 0; i2 < i; i2++) - if (subjects[i2] && - starts_with(subjects[i2], p)) - break; - if (i2 == i) - i2 = -1; - } - } - if (i2 >= 0) { - rearranged = 1; - todo_list.items[i].command = - starts_with(subject, "fixup!") ? - TODO_FIXUP : TODO_SQUASH; - if (next[i2] < 0) - next[i2] = i; - else - next[tail[i2]] = i; - tail[i2] = i; - } - else if (!hashmap_get_from_hash(&subject2item, - strhash(subject), subject)) { - FLEX_ALLOC_MEM(entry, subject, buf.buf, buf.len); - entry->i = i; - hashmap_entry_init(entry, strhash(entry->subject)); - hashmap_put(&subject2item, entry); - } - strbuf_detach(&buf, NULL); - } - - if (rearranged) { - struct strbuf buf = STRBUF_INIT; - char *format = NULL; - - git_config_get_string("rebase.instructionFormat", &format); - for (i = 0; i < todo_list.nr; i++) { - enum todo_command command = todo_list.items[i].command; - int cur = i; - - /* - * Initially, all commands are 'pick's. If it is a - * fixup or a squash now, we have rearranged it. - */ - if (is_fixup(command)) - continue; - - while (cur >= 0) { - int offset = todo_list.items[cur].offset_in_buf; - int end_offset = cur + 1 < todo_list.nr ? - todo_list.items[cur + 1].offset_in_buf : - todo_list.buf.len; - char *bol = todo_list.buf.buf + offset; - char *eol = todo_list.buf.buf + end_offset; - - /* replace 'pick', by 'fixup' or 'squash' */ - command = todo_list.items[cur].command; - if (is_fixup(command)) { - strbuf_addstr(&buf, - todo_command_info[command].str); - bol += strcspn(bol, " \t"); - } - - strbuf_add(&buf, bol, eol - bol); - - cur = next[cur]; - } - } - - fd = open(todo_file, O_WRONLY); - if (fd < 0) - res = error_errno(_("could not open '%s'"), todo_file); - else if (write(fd, buf.buf, buf.len) < 0) - res = error_errno(_("could not read '%s'."), todo_file); - else if (ftruncate(fd, buf.len) < 0) - res = error_errno(_("could not finish '%s'"), - todo_file); - close(fd); - strbuf_release(&buf); - } - - free(next); - free(tail); - for (i = 0; i < todo_list.nr; i++) - free(subjects[i]); - free(subjects); - hashmap_free(&subject2item, 1); - todo_list_release(&todo_list); - - return res; -} diff --git a/sequencer.h b/sequencer.h index 1c94bec762..7a513c576b 100644 --- a/sequencer.h +++ b/sequencer.h @@ -7,8 +7,7 @@ const char *git_path_seq_dir(void); enum replay_action { REPLAY_REVERT, - REPLAY_PICK, - REPLAY_INTERACTIVE_REBASE + REPLAY_PICK }; struct replay_opts { @@ -24,7 +23,6 @@ struct replay_opts { int allow_empty; int allow_empty_message; int keep_redundant_commits; - int verbose; int mainline; @@ -45,14 +43,6 @@ int sequencer_continue(struct replay_opts *opts); int sequencer_rollback(struct replay_opts *opts); int sequencer_remove_state(struct replay_opts *opts); -int sequencer_make_script(int keep_empty, FILE *out, - int argc, const char **argv); - -int transform_todo_ids(int shorten_sha1s); -int check_todo_list(void); -int skip_unnecessary_picks(void); -int rearrange_squash(void); - extern const char sign_off_header[]; void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag); diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 71b9c8ef8b..c896a4c106 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -540,7 +540,7 @@ test_expect_success 'clean error after failed "exec"' ' echo "edited again" > file7 && git add file7 && test_must_fail git rebase --continue 2>error && - test_i18ngrep "you have staged changes in your working tree" error + test_i18ngrep "You have staged changes in your working tree." error ' test_expect_success 'rebase a detached HEAD' ' @@ -1226,13 +1226,20 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' ' test B = $(git cat-file commit HEAD^ | sed -ne \$p) ' +cat >expect <actual && - test_i18ngrep "badcmd $(git rev-list --oneline -1 master~1)" actual && - test_i18ngrep "You can fix this with .git rebase --edit-todo.." actual && + test_i18ncmp expect actual && FAKE_LINES="1 2 3 drop 4 5" git rebase --edit-todo && git rebase --continue && test E = $(git cat-file commit HEAD | sed -ne \$p) && @@ -1254,13 +1261,20 @@ test_expect_success 'tabs and spaces are accepted in the todolist' ' test E = $(git cat-file commit HEAD | sed -ne \$p) ' +cat >expect <actual && - test_i18ngrep "edit XXXXXXX False commit" actual && - test_i18ngrep "You can fix this with .git rebase --edit-todo.." actual && + test_i18ncmp expect actual && FAKE_LINES="1 2 4 5 6" git rebase --edit-todo && git rebase --continue && test E = $(git cat-file commit HEAD | sed -ne \$p) diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh index b9e26008a7..48346f1cc0 100755 --- a/t/t3415-rebase-autosquash.sh +++ b/t/t3415-rebase-autosquash.sh @@ -278,7 +278,7 @@ set_backup_editor () { test_set_editor "$PWD/backup-editor.sh" } -test_expect_success 'autosquash with multiple empty patches' ' +test_expect_failure 'autosquash with multiple empty patches' ' test_tick && git commit --allow-empty -m "empty" && test_tick && @@ -304,18 +304,4 @@ test_expect_success 'extra spaces after fixup!' ' test $base = $parent ' -test_expect_success 'wrapped original subject' ' - if test -d .git/rebase-merge; then git rebase --abort; fi && - base=$(git rev-parse HEAD) && - echo "wrapped subject" >wrapped && - git add wrapped && - test_tick && - git commit --allow-empty -m "$(printf "To\nfixup")" && - test_tick && - git commit --allow-empty -m "fixup! To fixup" && - git rebase -i --autosquash --keep-empty HEAD~2 && - parent=$(git rev-parse HEAD^) && - test $base = $parent -' - test_done