Merge branch 'mm/rebase-i-exec'
* mm/rebase-i-exec: git-rebase--interactive.sh: use printf instead of echo to print commit message git-rebase--interactive.sh: rework skip_unnecessary_picks test-lib: user-friendly alternatives to test [-d|-f|-e] rebase -i: add exec command to launch a shell command Conflicts: git-rebase--interactive.sh t/t3404-rebase-interactive.sh
This commit is contained in:
commit
5cba1229d8
@ -466,6 +466,30 @@ sure that the current HEAD is "B", and call
|
||||
$ git rebase -i -p --onto Q O
|
||||
-----------------------------
|
||||
|
||||
Reordering and editing commits usually creates untested intermediate
|
||||
steps. You may want to check that your history editing did not break
|
||||
anything by running a test, or at least recompiling at intermediate
|
||||
points in history by using the "exec" command (shortcut "x"). You may
|
||||
do so by creating a todo list like this one:
|
||||
|
||||
-------------------------------------------
|
||||
pick deadbee Implement feature XXX
|
||||
fixup f1a5c00 Fix to feature XXX
|
||||
exec make
|
||||
pick c0ffeee The oneline of the next commit
|
||||
edit deadbab The oneline of the commit after
|
||||
exec cd subdir; make test
|
||||
...
|
||||
-------------------------------------------
|
||||
|
||||
The interactive rebase will stop when a command fails (i.e. exits with
|
||||
non-0 status) to give you an opportunity to fix the problem. You can
|
||||
continue with `git rebase --continue`.
|
||||
|
||||
The "exec" command launches the command in a shell (the one specified
|
||||
in `$SHELL`, or the default shell if `$SHELL` is not set), so you can
|
||||
use shell features (like "cd", ">", ";" ...). The command is run from
|
||||
the root of the working tree.
|
||||
|
||||
SPLITTING COMMITS
|
||||
-----------------
|
||||
|
@ -537,6 +537,34 @@ do_next () {
|
||||
esac
|
||||
record_in_rewritten $sha1
|
||||
;;
|
||||
x|"exec")
|
||||
read -r command rest < "$TODO"
|
||||
mark_action_done
|
||||
printf 'Executing: %s\n' "$rest"
|
||||
# "exec" command doesn't take a sha1 in the todo-list.
|
||||
# => can't just use $sha1 here.
|
||||
git rev-parse --verify HEAD > "$DOTEST"/stopped-sha
|
||||
${SHELL:-@SHELL_PATH@} -c "$rest" # Actual execution
|
||||
status=$?
|
||||
if test "$status" -ne 0
|
||||
then
|
||||
warn "Execution failed: $rest"
|
||||
warn "You can fix the problem, and then run"
|
||||
warn
|
||||
warn " git rebase --continue"
|
||||
warn
|
||||
exit "$status"
|
||||
fi
|
||||
# Run in subshell because require_clean_work_tree can die.
|
||||
if ! (require_clean_work_tree)
|
||||
then
|
||||
warn "Commit or stash your changes, and then run"
|
||||
warn
|
||||
warn " git rebase --continue"
|
||||
warn
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
warn "Unknown command: $command $sha1 $rest"
|
||||
if git rev-parse --verify -q "$sha1" >/dev/null
|
||||
@ -591,22 +619,30 @@ do_rest () {
|
||||
# skip picking commits whose parents are unchanged
|
||||
skip_unnecessary_picks () {
|
||||
fd=3
|
||||
while read -r command sha1 rest
|
||||
while read -r command rest
|
||||
do
|
||||
# fd=3 means we skip the command
|
||||
case "$fd,$command,$(git rev-parse --verify --quiet $sha1^)" in
|
||||
3,pick,"$ONTO"*|3,p,"$ONTO"*)
|
||||
case "$fd,$command" in
|
||||
3,pick|3,p)
|
||||
# pick a commit whose parent is current $ONTO -> skip
|
||||
sha1=$(printf '%s' "$rest" | cut -d ' ' -f 1)
|
||||
case "$(git rev-parse --verify --quiet "$sha1"^)" in
|
||||
"$ONTO"*)
|
||||
ONTO=$sha1
|
||||
;;
|
||||
3,#*|3,,*)
|
||||
*)
|
||||
fd=1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
3,#*|3,)
|
||||
# copy comments
|
||||
;;
|
||||
*)
|
||||
fd=1
|
||||
;;
|
||||
esac
|
||||
printf '%s\n' "$command${sha1:+ }$sha1${rest:+ }$rest" >&$fd
|
||||
printf '%s\n' "$command${rest:+ }$rest" >&$fd
|
||||
done <"$TODO" >"$TODO.new" 3>>"$DONE" &&
|
||||
mv -f "$TODO".new "$TODO" &&
|
||||
case "$(peek_next_command)" in
|
||||
@ -957,6 +993,7 @@ first and then run 'git rebase --continue' again."
|
||||
# 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 <cmd>, exec <cmd> = Run a shell command <cmd>, and stop if it fails
|
||||
#
|
||||
# If you remove a line here THAT COMMIT WILL BE LOST.
|
||||
# However, if you remove everything, the rebase will be aborted.
|
||||
|
7
t/README
7
t/README
@ -467,6 +467,13 @@ library for your script to use.
|
||||
<expected> file. This behaves like "cmp" but produces more
|
||||
helpful output when the test is run with "-v" option.
|
||||
|
||||
- test_path_is_file <file> [<diagnosis>]
|
||||
test_path_is_dir <dir> [<diagnosis>]
|
||||
test_path_is_missing <path> [<diagnosis>]
|
||||
|
||||
Check whether a file/directory exists or doesn't. <diagnosis> will
|
||||
be displayed if the test fails.
|
||||
|
||||
- test_when_finished <script>
|
||||
|
||||
Prepend <script> to a list of commands to run to clean up
|
||||
|
@ -47,6 +47,8 @@ for line in $FAKE_LINES; do
|
||||
case $line in
|
||||
squash|fixup|edit|reword)
|
||||
action="$line";;
|
||||
exec*)
|
||||
echo "$line" | sed 's/_/ /g' >> "$1";;
|
||||
"#")
|
||||
echo '# comment' >> "$1";;
|
||||
">")
|
||||
|
@ -64,6 +64,67 @@ test_expect_success 'setup' '
|
||||
done
|
||||
'
|
||||
|
||||
# "exec" commands are ran with the user shell by default, but this may
|
||||
# be non-POSIX. For example, if SHELL=zsh then ">file" doesn't work
|
||||
# to create a file. Unseting SHELL avoids such non-portable behavior
|
||||
# in tests.
|
||||
SHELL=
|
||||
|
||||
test_expect_success 'rebase -i with the exec command' '
|
||||
git checkout master &&
|
||||
(
|
||||
FAKE_LINES="1 exec_>touch-one
|
||||
2 exec_>touch-two exec_false exec_>touch-three
|
||||
3 4 exec_>\"touch-file__name_with_spaces\";_>touch-after-semicolon 5" &&
|
||||
export FAKE_LINES &&
|
||||
test_must_fail git rebase -i A
|
||||
) &&
|
||||
test_path_is_file touch-one &&
|
||||
test_path_is_file touch-two &&
|
||||
test_path_is_missing touch-three " (should have stopped before)" &&
|
||||
test $(git rev-parse C) = $(git rev-parse HEAD) || {
|
||||
echo "Stopped at wrong revision:"
|
||||
echo "($(git describe --tags HEAD) instead of C)"
|
||||
false
|
||||
} &&
|
||||
git rebase --continue &&
|
||||
test_path_is_file touch-three &&
|
||||
test_path_is_file "touch-file name with spaces" &&
|
||||
test_path_is_file touch-after-semicolon &&
|
||||
test $(git rev-parse master) = $(git rev-parse HEAD) || {
|
||||
echo "Stopped at wrong revision:"
|
||||
echo "($(git describe --tags HEAD) instead of master)"
|
||||
false
|
||||
} &&
|
||||
rm -f touch-*
|
||||
'
|
||||
|
||||
test_expect_success 'rebase -i with the exec command runs from tree root' '
|
||||
git checkout master &&
|
||||
mkdir subdir && cd subdir &&
|
||||
FAKE_LINES="1 exec_>touch-subdir" \
|
||||
git rebase -i HEAD^ &&
|
||||
cd .. &&
|
||||
test_path_is_file touch-subdir &&
|
||||
rm -fr subdir
|
||||
'
|
||||
|
||||
test_expect_success 'rebase -i with the exec command checks tree cleanness' '
|
||||
git checkout master &&
|
||||
(
|
||||
FAKE_LINES="exec_echo_foo_>file1 1" &&
|
||||
export FAKE_LINES &&
|
||||
test_must_fail git rebase -i HEAD^
|
||||
) &&
|
||||
test $(git rev-parse master^) = $(git rev-parse HEAD) || {
|
||||
echo "Stopped at wrong revision:"
|
||||
echo "($(git describe --tags HEAD) instead of master^)"
|
||||
false
|
||||
} &&
|
||||
git reset --hard &&
|
||||
git rebase --continue
|
||||
'
|
||||
|
||||
test_expect_success 'no changes are a nop' '
|
||||
git checkout branch2 &&
|
||||
git rebase -i F &&
|
||||
@ -143,7 +204,7 @@ test_expect_success 'abort' '
|
||||
git rebase --abort &&
|
||||
test $(git rev-parse new-branch1) = $(git rev-parse HEAD) &&
|
||||
test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch1" &&
|
||||
! test -d .git/rebase-merge
|
||||
test_path_is_missing .git/rebase-merge
|
||||
'
|
||||
|
||||
test_expect_success 'abort with error when new base cannot be checked out' '
|
||||
@ -153,7 +214,7 @@ test_expect_success 'abort with error when new base cannot be checked out' '
|
||||
grep "The following untracked working tree files would be overwritten by checkout:" \
|
||||
output &&
|
||||
grep "file1" output &&
|
||||
! test -d .git/rebase-merge &&
|
||||
test_path_is_missing .git/rebase-merge &&
|
||||
git reset --hard HEAD^
|
||||
'
|
||||
|
||||
|
@ -38,7 +38,7 @@ testrebase() {
|
||||
# Clean up the state from the previous one
|
||||
git reset --hard pre-rebase &&
|
||||
test_must_fail git rebase$type master &&
|
||||
test -d "$dotest" &&
|
||||
test_path_is_dir "$dotest" &&
|
||||
git rebase --abort &&
|
||||
test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) &&
|
||||
test ! -d "$dotest"
|
||||
@ -49,7 +49,7 @@ testrebase() {
|
||||
# Clean up the state from the previous one
|
||||
git reset --hard pre-rebase &&
|
||||
test_must_fail git rebase$type master &&
|
||||
test -d "$dotest" &&
|
||||
test_path_is_dir "$dotest" &&
|
||||
test_must_fail git rebase --skip &&
|
||||
test $(git rev-parse HEAD) = $(git rev-parse master) &&
|
||||
git rebase --abort &&
|
||||
@ -62,7 +62,7 @@ testrebase() {
|
||||
# Clean up the state from the previous one
|
||||
git reset --hard pre-rebase &&
|
||||
test_must_fail git rebase$type master &&
|
||||
test -d "$dotest" &&
|
||||
test_path_is_dir "$dotest" &&
|
||||
echo c > a &&
|
||||
echo d >> a &&
|
||||
git add a &&
|
||||
|
@ -545,6 +545,38 @@ test_external_without_stderr () {
|
||||
fi
|
||||
}
|
||||
|
||||
# debugging-friendly alternatives to "test [-f|-d|-e]"
|
||||
# The commands test the existence or non-existence of $1. $2 can be
|
||||
# given to provide a more precise diagnosis.
|
||||
test_path_is_file () {
|
||||
if ! [ -f "$1" ]
|
||||
then
|
||||
echo "File $1 doesn't exist. $*"
|
||||
false
|
||||
fi
|
||||
}
|
||||
|
||||
test_path_is_dir () {
|
||||
if ! [ -d "$1" ]
|
||||
then
|
||||
echo "Directory $1 doesn't exist. $*"
|
||||
false
|
||||
fi
|
||||
}
|
||||
|
||||
test_path_is_missing () {
|
||||
if [ -e "$1" ]
|
||||
then
|
||||
echo "Path exists:"
|
||||
ls -ld "$1"
|
||||
if [ $# -ge 1 ]; then
|
||||
echo "$*"
|
||||
fi
|
||||
false
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
# This is not among top-level (test_expect_success | test_expect_failure)
|
||||
# but is a prefix that can be used in the test script, like:
|
||||
#
|
||||
|
Loading…
Reference in New Issue
Block a user