On Tue, Aug 10, 2010 at 14:12, Johannes Sixt [off-list ref] wrote:
quoted
You cannot apply single-command-export if the command is a shell function.
You must rewrite this as:
(
export FAKE_LINES="..." &&
test_must_fail git rebase ....
) &&
Except that's not portable either, it should be:
FAKE_LINES="..." &&
export FAKE_LINES &&
test_must_fail git rebase ...
See the other examples in t3404-rebase-interactive.sh
The helper functions are implemented, documented, and used in a few
places to validate them, but not everywhere to avoid useless code churn.
Signed-off-by: Matthieu Moy <redacted>
---
Just resending this one since I modified PATCH 1/2 and had to resolve
a minor conflict.
t/README | 7 +++++++
t/t3404-rebase-interactive.sh | 18 +++++++++---------
t/t3407-rebase-abort.sh | 6 +++---
t/test-lib.sh | 32 ++++++++++++++++++++++++++++++++
4 files changed, 51 insertions(+), 12 deletions(-)
@@ -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
@@ -79,18 +79,18 @@ test_expect_success 'rebase -i with the exec command' 'exportFAKE_LINES&&test_must_failgitrebase-iA)&&-test-ftouch-one&&-test-ftouch-two&&-!test-ftouch-three&&+test_path_is_filetouch-one&&+test_path_is_filetouch-two&&+test_path_is_missingtouch-three" (should have stopped before)"&&test$(gitrev-parseC)=$(gitrev-parseHEAD)||{echo"Stopped at wrong revision:"echo"($(gitdescribe--tagsHEAD) instead of C)"false}&&gitrebase--continue&&-test-ftouch-three&&-test-f"touch-file name with spaces"&&-test-ftouch-after-semicolon&&+test_path_is_filetouch-three&&+test_path_is_file"touch-file name with spaces"&&+test_path_is_filetouch-after-semicolon&&test$(gitrev-parsemaster)=$(gitrev-parseHEAD)||{echo"Stopped at wrong revision:"echo"($(gitdescribe--tagsHEAD) instead of master)"
@@ -105,7 +105,7 @@ test_expect_success 'rebase -i with the exec command runs from tree root' 'FAKE_LINES="1 exec_>touch-subdir"\gitrebase-iHEAD^&&cd..&&-test-ftouch-subdir&&+test_path_is_filetouch-subdir&&rm-frsubdir'
@@ -204,7 +204,7 @@ test_expect_success 'abort' 'gitrebase--abort&&test$(gitrev-parsenew-branch1)=$(gitrev-parseHEAD)&&test"$(gitsymbolic-ref-qHEAD)"="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''
@@ -213,7 +213,7 @@ test_expect_success 'abort with error when new base cannot be checked out' 'test_must_failgitrebase-imaster>output2>&1&&grep"Untracked working tree file .file1. would be overwritten"\output&&-!test-d.git/rebase-merge&&+test_path_is_missing.git/rebase-merge&&gitreset--hardHEAD^'
@@ -38,7 +38,7 @@ testrebase() {# Clean up the state from the previous onegitreset--hardpre-rebase&&test_must_failgitrebase$typemaster&&-test-d"$dotest"&&+test_path_is_dir"$dotest"&&gitrebase--abort&&test$(gitrev-parseto-rebase)=$(gitrev-parsepre-rebase)&&test!-d"$dotest"
@@ -49,7 +49,7 @@ testrebase() {# Clean up the state from the previous onegitreset--hardpre-rebase&&test_must_failgitrebase$typemaster&&-test-d"$dotest"&&+test_path_is_dir"$dotest"&&test_must_failgitrebase--skip&&test$(gitrev-parseHEAD)=$(gitrev-parsemaster)&&gitrebase--abort&&
@@ -62,7 +62,7 @@ testrebase() {# Clean up the state from the previous onegitreset--hardpre-rebase&&test_must_failgitrebase$typemaster&&-test-d"$dotest"&&+test_path_is_dir"$dotest"&&echoc>a&&echod>>a&&gitadda&&
@@ -541,6 +541,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[$#-ge1];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:#
The typical usage pattern would be to run a test (or simply a compilation
command) at given points in history.
The shell command is ran (from the worktree root), and the rebase is
stopped when the command fails, to give the user an opportunity to fix
the problem before continuing with "git rebase --continue".
This needs a little rework of skip_unnecessary_picks, which wasn't robust
enough to deal with lines like
exec >"file name with many spaces"
in the todolist. The new version extracts command, sha1 and rest from
each line, but outputs the line itself verbatim to avoid changing the
whitespace layout.
Signed-off-by: Matthieu Moy <redacted>
---
This fixes the non-POSIX behavior of the tests found by Ævar Arnfjörð
Bjarmason (FAKE_LINES=foo test_must_fail ... does not work).
Also, I replaced "touch foo" with ">foo" and found a small bug. This
is the skip_unnecessary_picks of the commit message and of the patch
below.
Documentation/git-rebase.txt | 24 ++++++++++++++++
git-rebase--interactive.sh | 38 +++++++++++++++++++++++--
t/lib-rebase.sh | 2 +
t/t3404-rebase-interactive.sh | 61 +++++++++++++++++++++++++++++++++++++++++
4 files changed, 122 insertions(+), 3 deletions(-)
@@ -459,6 +459,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 () {esacrecord_in_rewritten$sha1;;+x|"exec")+read-rcommandrest<"$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.+gitrev-parse--verifyHEAD>"$DOTEST"/stopped-sha+${SHELL:-@SHELL_PATH@}-c"$rest"# Actual execution+status=$?+iftest"$status"-ne0+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+exit1+fi+;;*)warn"Unknown command: $command$sha1$rest"ifgitrev-parse--verify-q"$sha1">/dev/null
@@ -591,10 +619,13 @@ do_rest () {# skip picking commits whose parents are unchanged skip_unnecessary_picks(){fd=3-whileread-rcommandsha1rest+whileread-rlinedo+command=$(echo"$line"|sed's/ */ /'|cut-d' '-f1)+sha1=$(echo"$line"|sed's/ */ /'|cut-d' '-f2)+rest=$(echo"$line"|sed's/ */ /'|cut-d' '-f3-)# fd=3 means we skip the command-case"$fd,$command,$(gitrev-parse--verify--quiet$sha1^)"in+case"$fd,$command,$(gitrev-parse--verify--quiet"$sha1"^)"in3,pick,"$ONTO"*|3,p,"$ONTO"*)# pick a commit whose parent is current $ONTO -> skipONTO=$sha1
@@ -957,6 +988,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.
@@ -47,6 +47,8 @@ for line in $FAKE_LINES; docase$lineinsquash|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''+gitcheckoutmaster&&+(+FAKE_LINES="1 exec_>touch-one+2exec_>touch-twoexec_falseexec_>touch-three+34exec_>\"touch-file__name_with_spaces\";_>touch-after-semicolon5" &&+exportFAKE_LINES&&+test_must_failgitrebase-iA+)&&+test-ftouch-one&&+test-ftouch-two&&+!test-ftouch-three&&+test$(gitrev-parseC)=$(gitrev-parseHEAD)||{+echo"Stopped at wrong revision:"+echo"($(gitdescribe--tagsHEAD) instead of C)"+false+}&&+gitrebase--continue&&+test-ftouch-three&&+test-f"touch-file name with spaces"&&+test-ftouch-after-semicolon&&+test$(gitrev-parsemaster)=$(gitrev-parseHEAD)||{+echo"Stopped at wrong revision:"+echo"($(gitdescribe--tagsHEAD) instead of master)"+false+}&&+rm-ftouch-*+'++test_expect_success'rebase -i with the exec command runs from tree root''+gitcheckoutmaster&&+mkdirsubdir&&cdsubdir&&+FAKE_LINES="1 exec_>touch-subdir"\+gitrebase-iHEAD^&&+cd..&&+test-ftouch-subdir&&+rm-frsubdir+'++test_expect_success'rebase -i with the exec command checks tree cleanness''+gitcheckoutmaster&&+(+FAKE_LINES="exec_echo_foo_>file1 1"&&+exportFAKE_LINES&&+test_must_failgitrebase-iHEAD^+)&&+test$(gitrev-parsemaster^)=$(gitrev-parseHEAD)||{+echo"Stopped at wrong revision:"+echo"($(gitdescribe--tagsHEAD) instead of master^)"+false+}&&+gitreset--hard&&+gitrebase--continue+'+ test_expect_success'no changes are a nop''gitcheckoutbranch2&&gitrebase-iF&&
From: Junio C Hamano <hidden> Date: 2016-06-15 22:49:17
Matthieu Moy [off-list ref] writes:
The typical usage pattern would be to run a test (or simply a compilation
command) at given points in history.
The shell command is ran (from the worktree root), and the rebase is
stopped when the command fails, to give the user an opportunity to fix
the problem before continuing with "git rebase --continue".
This needs a little rework of skip_unnecessary_picks, which wasn't robust
enough to deal with lines like
exec >"file name with many spaces"
in the todolist. The new version extracts command, sha1 and rest from
each line, but outputs the line itself verbatim to avoid changing the
whitespace layout.
@@ -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=
Tricky but true.
Do we have other callouts that we use $SHELL from the environment?
execv_shell_cmd() just runs "sh -c" from $PATH so diff (when running
external diff) nor ll-merge (when running external merge driver) that call
it via run_command_v_opt(RUN_USING_SHELL) are immune to this issue.
From: Jonathan Nieder <hidden> Date: 2016-06-15 22:50:25
Matthieu Moy wrote:
quoted hunk
+++ b/git-rebase--interactive.sh
@@ -957,6 +988,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.
Nit: the "exec" command is formatted differently from the commands
around it, making it stand out (which I don't think is intended).
While we're there, patch 2 adds some brief documentation for the
"noop" command.
Roughly based on [1] (which might be a nice patch to revive, by the
way). Sane?
Jonathan Nieder (2):
rebase -i: reword in-editor documentation of "exec"
rebase -i: explain how to discard all commits
git-rebase--interactive.sh | 3 ++-
1 files changed, 2 insertions(+), 1 deletions(-)
[1] http://thread.gmane.org/gmane.comp.version-control.git/161120/focus=162079
From: Jonathan Nieder <hidden> Date: 2016-06-15 22:50:25
The argument to the "exec" insn represents a command to be passed to
the user's shell. (At first I misread the description as meaning it
should itself be the name of a shell.)
While fixing that, format the description to more closely parallel
the descriptions of other commands.
Before:
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but [...]
# 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.
After:
[...]
# f, fixup = like "squash", but [...]
# x, exec = run command using shell, and stop if it fails
#
# If you remove a line [...]
Signed-off-by: Jonathan Nieder <redacted>
---
It would be nice to say "stop for amending if it fails" (or similar)
to make the relationship to the edit insn clearer, but it is not clear
how to make room for that.
git-rebase--interactive.sh | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
@@ -1021,7 +1021,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+# x, exec = run command using shell, 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.
From: Jonathan Nieder <hidden> Date: 2016-06-15 22:50:25
Preparing a patch series for submission (as explained under
INTERACTIVE MODE in the git rebase manual) sometimes involves
discarding commits representing changes that turned out to be a bad
idea. Usually this is quite simple to do by deleting the appropriate
"pick" lines, but if all commits are removed then the "remove
everything means abort" logic kicks in and the rebase is cancelled.
One can override that behavior by adding a line with the text "noop".
This is a follow-up to v1.6.0.3~21 (rebase -i: do not fail when there
is no commit to cherry-pick, 2008-10-10).
Signed-off-by: Jonathan Nieder <redacted>
---
git-rebase--interactive.sh | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
@@ -1025,6 +1025,7 @@ first and then run 'git rebase --continue' again."## If you remove a line here THAT COMMIT WILL BE LOST.# However, if you remove everything, the rebase will be aborted.+# Use the "noop" command if you really want to remove all commits.# EOF
From: Nicolas Sebrecht <hidden> Date: 2016-06-15 22:50:26
The 15/01/11, Jonathan Nieder wrote:
quoted hunk
Preparing a patch series for submission (as explained under
INTERACTIVE MODE in the git rebase manual) sometimes involves
discarding commits representing changes that turned out to be a bad
idea. Usually this is quite simple to do by deleting the appropriate
"pick" lines, but if all commits are removed then the "remove
everything means abort" logic kicks in and the rebase is cancelled.
One can override that behavior by adding a line with the text "noop".
This is a follow-up to v1.6.0.3~21 (rebase -i: do not fail when there
is no commit to cherry-pick, 2008-10-10).
Signed-off-by: Jonathan Nieder <redacted>
---
git-rebase--interactive.sh | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
@@ -1025,6 +1025,7 @@ first and then run 'git rebase --continue' again."## If you remove a line here THAT COMMIT WILL BE LOST.# However, if you remove everything, the rebase will be aborted.+# Use the "noop" command if you really want to remove all commits.# EOF
Sorry, I think it is confusing. With this help we could understand that
the "noop" will either
(a) discard the interactive rebase
or
(b) _really remove commits_ from that branch
I'm not sure to know how it will act myself. If (a), we could use
something like
"However, if you remove everything or use the "noop" command, the rebase will be aborted."
but if we are in case (b), I guess it is not necessary and we should
point to the 'git reset' command.
--
Nicolas Sebrecht
From: Jonathan Nieder <hidden> Date: 2016-06-15 22:50:26
Nicolas Sebrecht wrote:
The 15/01/11, Jonathan Nieder wrote:
quoted
This is a follow-up to v1.6.0.3~21 (rebase -i: do not fail when there
is no commit to cherry-pick, 2008-10-10).
[...]
quoted
# However, if you remove everything, the rebase will be aborted.
+# Use the "noop" command if you really want to remove all commits.
[...]
Sorry, I think it is confusing. With this help we could understand that
the "noop" will either
(a) discard the interactive rebase
or
(b) _really remove commits_ from that branch
I'm not sure to know how it will act myself. If (a), we could use
something like
"However, if you remove everything or use the "noop" command, the rebase will be aborted."
but if we are in case (b), I guess it is not necessary and we should
point to the 'git reset' command.
Okay. I agree that my particular wording was confusing. Are you
saying the "noop" command in general is confusing?
The "noop" is itself a non-operation; if you combine "noop" with other
instructions then the noop itself will have no effect. Meanwhile if
you have _no_ instructions then the rebase is cancelled, while if you
have a single "noop" instruction, that means "I have discarded all the
commits, but please rebase anyway".
Jonathan
From: Nicolas Sebrecht <hidden> Date: 2016-06-15 22:50:26
The 20/01/11, Jonathan Nieder wrote:
Okay. I agree that my particular wording was confusing. Are you
saying the "noop" command in general is confusing?
The "noop" is itself a non-operation; if you combine "noop" with other
instructions then the noop itself will have no effect. Meanwhile if
you have _no_ instructions then the rebase is cancelled, while if you
have a single "noop" instruction, that means "I have discarded all the
commits, but please rebase anyway".
Ok, I think I get it now. What about adding
Use "noop" with no other instruction to fallback to a non-interactive
rebase. If other instructions are present, "noop" has no effect.
?
--
Nicolas Sebrecht
From: Thomas Rast <hidden> Date: 2016-06-15 22:50:26
Nicolas Sebrecht wrote:
The 20/01/11, Jonathan Nieder wrote:
quoted
if you
have a single "noop" instruction, that means "I have discarded all the
commits, but please rebase anyway".
Ok, I think I get it now. What about adding
Use "noop" with no other instruction to fallback to a non-interactive
rebase. If other instructions are present, "noop" has no effect.
?
No, that's quite wrong.
The TODO list is the list of all commits that need to be rebased. It
does not contain commits that (according to patch-id) are already
contained in the upstream (i.e., the base you are rebasing on). If
the list is empty after filtering out such commits, rebase puts 'noop'
as the only command since "empty TODO" is already taken to mean
"abort"
If you then accept this 'noop' rebase, this effectively makes the
rebased branch the same as the base branch, sort of like resetting.
(I for one have never accepted such a rebase; if the TODO only
consists of noop, that means I made a mistake.)
--
Thomas Rast
trast@{inf,student}.ethz.ch