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:
Junio C Hamano 2010-08-21 23:29:11 -07:00
commit 5cba1229d8
7 changed files with 174 additions and 11 deletions

View File

@ -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
-----------------

View File

@ -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.

View File

@ -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

View File

@ -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";;
">")

View File

@ -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^
'

View File

@ -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 &&

View File

@ -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:
#