From: Junio C Hamano Date: Sun, 22 Aug 2010 06:29:11 +0000 (-0700) Subject: Merge branch 'mm/rebase-i-exec' X-Git-Tag: v1.7.3-rc0~46 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/5cba1229d8246da2a6d1d956eeed8227f69e185b?ds=inline;hp=-c 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 --- 5cba1229d8246da2a6d1d956eeed8227f69e185b diff --combined Documentation/git-rebase.txt index b4314568f5,9c68b667e9..3b87f1a1b6 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@@ -250,13 -250,6 +250,13 @@@ on top of the branch using t the 'ours' strategy simply discards all patches from the , which makes little sense. +-X :: +--strategy-option=:: + Pass the through to the merge strategy. + This implies `\--merge` and, if no strategy has been + specified, `-s recursive`. Note the reversal of 'ours' and + 'theirs' as noted in above for the `-m` option. + -q:: --quiet:: Be quiet. Implies --no-stat. @@@ -466,6 -459,30 +466,30 @@@ sure that the current HEAD is "B", and $ 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 ----------------- diff --combined git-rebase--interactive.sh index b94c2a0386,e16dbe201d..3419247d03 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@@ -119,7 -119,7 +119,7 @@@ run 'git rebase --continue' export GIT_CHERRY_PICK_HELP warn () { - echo "$*" >&2 + printf '%s\n' "$*" >&2 } output () { @@@ -537,6 -537,34 +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 +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 - ONTO=$sha1 + sha1=$(printf '%s' "$rest" | cut -d ' ' -f 1) + case "$(git rev-parse --verify --quiet "$sha1"^)" in + "$ONTO"*) + ONTO=$sha1 + ;; + *) + fd=1 + ;; + esac ;; - 3,#*|3,,*) + 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 @@@ -649,12 -685,12 +685,12 @@@ rearrange_squash () case " $used" in *" $sha1 "*) continue ;; esac - echo "$pick $sha1 $message" + printf '%s\n' "$pick $sha1 $message" while read -r squash action msg do case "$message" in "$msg"*) - echo "$action $squash $action! $msg" + printf '%s\n' "$action $squash $action! $msg" used="$used$squash " ;; esac @@@ -895,7 -931,7 +931,7 @@@ first and then run 'git rebase --contin do if test t != "$PRESERVE_MERGES" then - echo "pick $shortsha1 $rest" >> "$TODO" + printf '%s\n' "pick $shortsha1 $rest" >> "$TODO" else sha1=$(git rev-parse $shortsha1) if test -z "$REBASE_ROOT" @@@ -914,7 -950,7 +950,7 @@@ if test f = "$preserve" then touch "$REWRITTEN"/$sha1 - echo "pick $shortsha1 $rest" >> "$TODO" + printf '%s\n' "pick $shortsha1 $rest" >> "$TODO" fi fi done @@@ -957,6 -993,7 +993,7 @@@ # 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 a shell command , 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. diff --combined t/t3404-rebase-interactive.sh index 3af3f603fb,56891e6c74..af3b663aee --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@@ -64,6 -64,67 +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,17 -204,16 +204,17 @@@ 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' ' git rm --cached file1 && git commit -m "remove file in base" && test_must_fail git rebase -i master > output 2>&1 && - grep "Untracked working tree file .file1. would be overwritten" \ + 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^ ' @@@ -638,19 -698,13 +699,19 @@@ test_expect_success 'set up commits wit git commit -a -m "end with slash\\" && echo >>file1 && test_tick && + git commit -a -m "something (\000) that looks like octal" && + echo >>file1 && + test_tick && + git commit -a -m "something (\n) that looks like a newline" && + echo >>file1 && + test_tick && git commit -a -m "another commit" ' test_expect_success 'rebase-i history with funny messages' ' git rev-list A..funny >expect && test_tick && - FAKE_LINES="1 2" git rebase -i A && + FAKE_LINES="1 2 3 4" git rebase -i A && git rev-list A.. >actual && test_cmp expect actual ' diff --combined t/test-lib.sh index 29fd7209cf,e913286e2d..3a3d4c4723 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@@ -127,13 -127,14 +127,13 @@@ d -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) verbose=t; shift ;; -q|--q|--qu|--qui|--quie|--quiet) - quiet=t; shift ;; + # Ignore --quiet under a TAP::Harness. Saying how many tests + # passed without the ok/not ok details is always an error. + test -z "$HARNESS_ACTIVE" && quiet=t; shift ;; --with-dashes) with_dashes=t; shift ;; --no-color) color=; shift ;; - --no-python) - # noop now... - shift ;; --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind) valgrind=t; verbose=t; shift ;; --tee) @@@ -256,10 -257,6 +256,10 @@@ q_to_cr () tr Q '\015' } +q_to_tab () { + tr Q '\011' +} + append_cr () { sed -e 's/$/Q/' | tr Q '\015' } @@@ -545,6 -542,38 +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: #