Merge branch 'interactive-rebase'

This series of branches introduces the git-rebase--helper, a builtin
helping to accelerate the interactive rebase dramatically.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
This commit is contained in:
Johannes Schindelin
2016-10-13 14:28:04 +02:00
18 changed files with 2268 additions and 708 deletions

1
.gitignore vendored
View File

@@ -114,6 +114,7 @@
/git-read-tree
/git-rebase
/git-rebase--am
/git-rebase--helper
/git-rebase--interactive
/git-rebase--merge
/git-receive-pack

View File

@@ -925,6 +925,7 @@ BUILTIN_OBJS += builtin/prune.o
BUILTIN_OBJS += builtin/pull.o
BUILTIN_OBJS += builtin/push.o
BUILTIN_OBJS += builtin/read-tree.o
BUILTIN_OBJS += builtin/rebase--helper.o
BUILTIN_OBJS += builtin/receive-pack.o
BUILTIN_OBJS += builtin/reflog.o
BUILTIN_OBJS += builtin/remote.o

View File

@@ -102,6 +102,7 @@ extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);
extern int cmd_pull(int argc, const char **argv, const char *prefix);
extern int cmd_push(int argc, const char **argv, const char *prefix);
extern int cmd_read_tree(int argc, const char **argv, const char *prefix);
extern int cmd_rebase__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);

View File

@@ -183,7 +183,7 @@ static void determine_whence(struct wt_status *s)
whence = FROM_MERGE;
else if (file_exists(git_path_cherry_pick_head())) {
whence = FROM_CHERRY_PICK;
if (file_exists(git_path(SEQ_DIR)))
if (file_exists(git_path_seq_dir()))
sequencer_in_use = 1;
}
else

View File

@@ -17,6 +17,7 @@
#include "revision.h"
#include "tempfile.h"
#include "lockfile.h"
#include "wt-status.h"
enum rebase_type {
REBASE_INVALID = -1,
@@ -325,73 +326,6 @@ static int git_pull_config(const char *var, const char *value, void *cb)
return git_default_config(var, value, cb);
}
/**
* Returns 1 if there are unstaged changes, 0 otherwise.
*/
static int has_unstaged_changes(const char *prefix)
{
struct rev_info rev_info;
int result;
init_revisions(&rev_info, prefix);
DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES);
DIFF_OPT_SET(&rev_info.diffopt, QUICK);
diff_setup_done(&rev_info.diffopt);
result = run_diff_files(&rev_info, 0);
return diff_result_code(&rev_info.diffopt, result);
}
/**
* Returns 1 if there are uncommitted changes, 0 otherwise.
*/
static int has_uncommitted_changes(const char *prefix)
{
struct rev_info rev_info;
int result;
if (is_cache_unborn())
return 0;
init_revisions(&rev_info, prefix);
DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES);
DIFF_OPT_SET(&rev_info.diffopt, QUICK);
add_head_to_pending(&rev_info);
diff_setup_done(&rev_info.diffopt);
result = run_diff_index(&rev_info, 1);
return diff_result_code(&rev_info.diffopt, result);
}
/**
* If the work tree has unstaged or uncommitted changes, dies with the
* appropriate message.
*/
static void die_on_unclean_work_tree(const char *prefix)
{
struct lock_file *lock_file = xcalloc(1, sizeof(*lock_file));
int do_die = 0;
hold_locked_index(lock_file, 0);
refresh_cache(REFRESH_QUIET);
update_index_if_able(&the_index, lock_file);
rollback_lock_file(lock_file);
if (has_unstaged_changes(prefix)) {
error(_("Cannot pull with rebase: You have unstaged changes."));
do_die = 1;
}
if (has_uncommitted_changes(prefix)) {
if (do_die)
error(_("Additionally, your index contains uncommitted changes."));
else
error(_("Cannot pull with rebase: Your index contains uncommitted changes."));
do_die = 1;
}
if (do_die)
exit(1);
}
/**
* Appends merge candidates from FETCH_HEAD that are not marked not-for-merge
* into merge_heads.
@@ -875,7 +809,8 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
die(_("Updating an unborn branch with changes added to the index."));
if (!autostash)
die_on_unclean_work_tree(prefix);
require_clean_work_tree(N_("pull with rebase"),
"Please commit or stash them.", 1, 0);
if (get_rebase_fork_point(rebase_fork_point, repo, *refspecs))
hashclr(rebase_fork_point);

67
builtin/rebase--helper.c Normal file
View File

@@ -0,0 +1,67 @@
#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 [<options>]"),
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);
}

View File

@@ -71,7 +71,7 @@ static void verify_opt_compatible(const char *me, const char *base_opt, ...)
die(_("%s: %s cannot be used with %s"), me, this_opt, base_opt);
}
static void parse_args(int argc, const char **argv, struct replay_opts *opts)
static int run_sequencer(int argc, const char **argv, struct replay_opts *opts)
{
const char * const * usage_str = revert_or_cherry_pick_usage(opts);
const char *me = action_name(opts);
@@ -115,25 +115,15 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
if (opts->keep_redundant_commits)
opts->allow_empty = 1;
/* Set the subcommand */
if (cmd == 'q')
opts->subcommand = REPLAY_REMOVE_STATE;
else if (cmd == 'c')
opts->subcommand = REPLAY_CONTINUE;
else if (cmd == 'a')
opts->subcommand = REPLAY_ROLLBACK;
else
opts->subcommand = REPLAY_NONE;
/* Check for incompatible command line arguments */
if (opts->subcommand != REPLAY_NONE) {
if (cmd) {
char *this_operation;
if (opts->subcommand == REPLAY_REMOVE_STATE)
if (cmd == 'q')
this_operation = "--quit";
else if (opts->subcommand == REPLAY_CONTINUE)
else if (cmd == 'c')
this_operation = "--continue";
else {
assert(opts->subcommand == REPLAY_ROLLBACK);
assert(cmd == 'a');
this_operation = "--abort";
}
@@ -156,7 +146,7 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
"--edit", opts->edit,
NULL);
if (opts->subcommand != REPLAY_NONE) {
if (cmd) {
opts->revs = NULL;
} else {
struct setup_revision_opt s_r_opt;
@@ -174,20 +164,26 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
if (argc > 1)
usage_with_options(usage_str, options);
if (cmd == 'q')
return sequencer_remove_state(opts);
if (cmd == 'c')
return sequencer_continue(opts);
if (cmd == 'a')
return sequencer_rollback(opts);
return sequencer_pick_revisions(opts);
}
int cmd_revert(int argc, const char **argv, const char *prefix)
{
struct replay_opts opts;
struct replay_opts opts = REPLAY_OPTS_INIT;
int res;
memset(&opts, 0, sizeof(opts));
if (isatty(0))
opts.edit = 1;
opts.action = REPLAY_REVERT;
git_config(git_default_config, NULL);
parse_args(argc, argv, &opts);
res = sequencer_pick_revisions(&opts);
res = run_sequencer(argc, argv, &opts);
if (res < 0)
die(_("revert failed"));
return res;
@@ -195,14 +191,12 @@ int cmd_revert(int argc, const char **argv, const char *prefix)
int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
{
struct replay_opts opts;
struct replay_opts opts = REPLAY_OPTS_INIT;
int res;
memset(&opts, 0, sizeof(opts));
opts.action = REPLAY_PICK;
git_config(git_default_config, NULL);
parse_args(argc, argv, &opts);
res = sequencer_pick_revisions(&opts);
res = run_sequencer(argc, argv, &opts);
if (res < 0)
die(_("cherry-pick failed"));
return res;

View File

@@ -146,13 +146,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"
@@ -703,43 +703,6 @@ 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
@@ -750,7 +713,12 @@ transform_todo_ids () {
;;
*)
sha1=$(git rev-parse --verify --quiet "$@" ${rest%%[ ]*}) &&
rest="$sha1 ${rest#*[ ]}"
if test "a$rest" = "a${rest#*[ ]}"
then
rest=$sha1
else
rest="$sha1 ${rest#*[ ]}"
fi
;;
esac
printf '%s\n' "$command${rest:+ }$rest"
@@ -759,98 +727,11 @@ transform_todo_ids () {
}
expand_todo_ids() {
transform_todo_ids
git rebase--helper --expand-sha1s
}
collapse_todo_ids() {
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"
git rebase--helper --shorten-sha1s
}
# Add commands after a pick or after a squash/fixup serie
@@ -874,96 +755,6 @@ 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"
@@ -978,74 +769,6 @@ 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
@@ -1059,6 +782,10 @@ 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
@@ -1118,6 +845,10 @@ 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
;;
@@ -1192,26 +923,27 @@ else
revisions=$onto...$orig_head
shortrevisions=$shorthead
fi
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 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
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
@@ -1230,8 +962,8 @@ do
touch "$rewritten"/$sha1
printf '%s\n' "${comment_out}pick $sha1 $rest" >>"$todo"
fi
fi
done
done
fi
# Watch for commits that been dropped by --cherry-pick
if test t = "$preserve_merges"
@@ -1261,7 +993,7 @@ then
fi
test -s "$todo" || echo noop >> "$todo"
test -n "$autosquash" && rearrange_squash "$todo"
test -z "$autosquash" || git rebase--helper --rearrange-squash || exit
test -n "$cmd" && add_exec_commands "$todo"
todocount=$(git stripspace --strip-comments <"$todo" | wc -l)
@@ -1297,13 +1029,24 @@ git_sequence_editor "$todo" ||
has_action "$todo" ||
return 2
check_todo_list
git rebase--helper --check-todo-list || {
ret=$?
checkout_onto
exit $ret
}
expand_todo_ids
test -d "$rewritten" || test -n "$force_rebase" || skip_unnecessary_picks
test -d "$rewritten" || test -n "$force_rebase" ||
onto="$(git rebase--helper --skip-unnecessary-picks)" ||
die "Could not skip unnecessary pick commands"
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
}

1
git.c
View File

@@ -451,6 +451,7 @@ 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 },

View File

@@ -57,7 +57,8 @@ int checkout_fast_forward(const unsigned char *head,
refresh_cache(REFRESH_QUIET);
hold_locked_index(lock_file, 1);
if (hold_locked_index(lock_file, 0) < 0)
return -1;
memset(&trees, 0, sizeof(trees));
memset(&opts, 0, sizeof(opts));
@@ -90,7 +91,9 @@ int checkout_fast_forward(const unsigned char *head,
}
if (unpack_trees(nr_trees, t, &opts))
return -1;
if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
die(_("unable to write new index file"));
if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) {
rollback_lock_file(lock_file);
return error(_("unable to write new index file"));
}
return 0;
}

View File

@@ -575,6 +575,29 @@ 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);
}

View File

@@ -70,6 +70,7 @@ 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);
/*

File diff suppressed because it is too large Load Diff

View File

@@ -1,28 +1,18 @@
#ifndef SEQUENCER_H
#define SEQUENCER_H
#define SEQ_DIR "sequencer"
#define SEQ_HEAD_FILE "sequencer/head"
#define SEQ_TODO_FILE "sequencer/todo"
#define SEQ_OPTS_FILE "sequencer/opts"
const char *git_path_seq_dir(void);
#define APPEND_SIGNOFF_DEDUP (1u << 0)
enum replay_action {
REPLAY_REVERT,
REPLAY_PICK
};
enum replay_subcommand {
REPLAY_NONE,
REPLAY_REMOVE_STATE,
REPLAY_CONTINUE,
REPLAY_ROLLBACK
REPLAY_PICK,
REPLAY_INTERACTIVE_REBASE
};
struct replay_opts {
enum replay_action action;
enum replay_subcommand subcommand;
/* Boolean options */
int edit;
@@ -34,6 +24,7 @@ struct replay_opts {
int allow_empty;
int allow_empty_message;
int keep_redundant_commits;
int verbose;
int mainline;
@@ -46,9 +37,34 @@ struct replay_opts {
/* Only used by REPLAY_NONE */
struct rev_info *revs;
/* malloc()ed data entrusted to the sequencer */
void **owned;
int owned_nr, owned_alloc;
};
#define REPLAY_OPTS_INIT { -1 }
/*
* Make it the duty of sequencer_remove_state() to release the memory;
* For ease of use, return the same pointer.
*/
void *sequencer_entrust(struct replay_opts *opts, void *to_free);
int sequencer_pick_revisions(struct replay_opts *opts);
int sequencer_continue(struct replay_opts *opts);
int sequencer_rollback(struct replay_opts *opts);
int sequencer_remove_state(struct replay_opts *opts);
int sequencer_commit(const char *defmsg, struct replay_opts *opts,
int allow_empty, int edit, int amend,
int cleanup_commit_message);
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[];

View File

@@ -1215,20 +1215,13 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' '
test B = $(git cat-file commit HEAD^ | sed -ne \$p)
'
cat >expect <<EOF
Warning: the command isn't recognized in the following line:
- badcmd $(git rev-list --oneline -1 master~1)
You can fix this with 'git rebase --edit-todo' and then run 'git rebase --continue'.
Or you can abort the rebase with 'git rebase --abort'.
EOF
test_expect_success 'static check of bad command' '
rebase_setup_and_clean bad-cmd &&
set_fake_editor &&
test_must_fail env FAKE_LINES="1 2 3 bad 4 5" \
git rebase -i --root 2>actual &&
test_i18ncmp 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 &&
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) &&
@@ -1250,20 +1243,13 @@ test_expect_success 'tabs and spaces are accepted in the todolist' '
test E = $(git cat-file commit HEAD | sed -ne \$p)
'
cat >expect <<EOF
Warning: the SHA-1 is missing or isn't a commit in the following line:
- edit XXXXXXX False commit
You can fix this with 'git rebase --edit-todo' and then run 'git rebase --continue'.
Or you can abort the rebase with 'git rebase --abort'.
EOF
test_expect_success 'static check of bad SHA-1' '
rebase_setup_and_clean bad-sha &&
set_fake_editor &&
test_must_fail env FAKE_LINES="1 2 edit fakesha 3 4 5 #" \
git rebase -i --root 2>actual &&
test_i18ncmp expect actual &&
test_i18ngrep "edit XXXXXXX False commit" actual &&
test_i18ngrep "You can fix this with .git rebase --edit-todo.." 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)

View File

@@ -278,7 +278,7 @@ set_backup_editor () {
test_set_editor "$PWD/backup-editor.sh"
}
test_expect_failure 'autosquash with multiple empty patches' '
test_expect_success 'autosquash with multiple empty patches' '
test_tick &&
git commit --allow-empty -m "empty" &&
test_tick &&
@@ -304,4 +304,18 @@ 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

View File

@@ -16,6 +16,7 @@
#include "strbuf.h"
#include "utf8.h"
#include "worktree.h"
#include "lockfile.h"
static const char cut_line[] =
"------------------------ >8 ------------------------\n";
@@ -2209,3 +2210,79 @@ void wt_status_print(struct wt_status *s)
break;
}
}
/**
* Returns 1 if there are unstaged changes, 0 otherwise.
*/
int has_unstaged_changes(int ignore_submodules)
{
struct rev_info rev_info;
int result;
init_revisions(&rev_info, NULL);
if (ignore_submodules)
DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES);
DIFF_OPT_SET(&rev_info.diffopt, QUICK);
diff_setup_done(&rev_info.diffopt);
result = run_diff_files(&rev_info, 0);
return diff_result_code(&rev_info.diffopt, result);
}
/**
* Returns 1 if there are uncommitted changes, 0 otherwise.
*/
int has_uncommitted_changes(int ignore_submodules)
{
struct rev_info rev_info;
int result;
if (is_cache_unborn())
return 0;
init_revisions(&rev_info, NULL);
if (ignore_submodules)
DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES);
DIFF_OPT_SET(&rev_info.diffopt, QUICK);
add_head_to_pending(&rev_info);
diff_setup_done(&rev_info.diffopt);
result = run_diff_index(&rev_info, 1);
return diff_result_code(&rev_info.diffopt, result);
}
/**
* If the work tree has unstaged or uncommitted changes, dies with the
* appropriate message.
*/
int require_clean_work_tree(const char *action, const char *hint, int ignore_submodules, int gently)
{
struct lock_file *lock_file = xcalloc(1, sizeof(*lock_file));
int err = 0;
hold_locked_index(lock_file, 0);
refresh_cache(REFRESH_QUIET);
update_index_if_able(&the_index, lock_file);
rollback_lock_file(lock_file);
if (has_unstaged_changes(ignore_submodules)) {
error(_("Cannot %s: You have unstaged changes."), _(action));
err = 1;
}
if (has_uncommitted_changes(ignore_submodules)) {
if (err)
error(_("Additionally, your index contains uncommitted changes."));
else
error(_("Cannot %s: Your index contains uncommitted changes."),
_(action));
err = 1;
}
if (err) {
if (hint)
error("%s", hint);
if (!gently)
exit(err);
}
return err;
}

View File

@@ -128,4 +128,10 @@ void status_printf_ln(struct wt_status *s, const char *color, const char *fmt, .
__attribute__((format (printf, 3, 4)))
void status_printf(struct wt_status *s, const char *color, const char *fmt, ...);
/* The following functions expect that the caller took care of reading the index. */
int has_unstaged_changes(int ignore_submodules);
int has_uncommitted_changes(int ignore_submodules);
int require_clean_work_tree(const char *action, const char *hint,
int ignore_submodules, int gently);
#endif /* STATUS_H */