rebase -i: Implement `reword` and `squash` in terms of `do_pick`
cover rebase -i: Implement `reword` and `squash` in terms of `do_pick`
Hi,
This patch series is part of the undertaking to add command line options to to-do list commands. The goal is to be able to write something similar to
pick --reset-author --signoff a Some changes
pick b Some more changes
squash --no-edit c Ack Some more changes.
The first submission to the mailing list adds options parsing to do_pick and reimplements the current set of to-do list commands in terms[1] of a parametrized do_pick. It neither adds new commands to to-do lists nor exposes the command line options feature to the user. Still it makes the implementation of do_next slightly more straight-forward and the function do_pick behave more like an internal git-cherry-pick.
The patches try to accomplish one thing at a time but are not standalone in the sense that each of them constitutes a frame only in which a subsequent patch can express its one thing. For instance, only committing once and not dying in do_pick are things specific to implementing squash in terms of do_pick but presented as separate patches so that their correctness can be discussed independently.
The last patch contains some notes about when to run the post-rewrite hook, something I could not figure out myself.
Thanks for your time,
Fabian
[1] pick, reword, squash, fixup
Fabian Ruch (7):
rebase -i: Make option handling in pick_one more flexible rebase -i: Teach do_pick the option --edit rebase -i: Stop on root commits with empty log messages rebase -i: Commit only once when rewriting picks rebase -i: Do not die in do_pick
rebase -i: Prepare for squash in terms of do_pick --amend rebase -i: Teach do_pick the options --amend and --file
git-rebase--interactive.sh | 194 +++++++++++++++++++++++++++++++----------- t/t3404-rebase-interactive.sh | 8 ++ t/t3412-rebase-root.sh | 39 +++++++++ 3 files changed, 193 insertions(+), 48 deletions(-)
Signature · 1 line
2.0.0
v2 cover Enable options --signoff, --reset-author for pick, reword
Hi,
this reroll applies the comments from Eric, Junio and Michael. In particular,
* It turned out that pick_one does not need option handling at all and the option-like argument -n determines whether pick_one or do_pick creates the replay commit. The latter happens if the task wants to rewrite the commit being picked (f.i., for the purpose of editing the log message or resetting the authorship).
do_pick still seems to require a flexible parsing of options, i.e. a relatively expensive loop, since it receives the whitelisted user-supplied options. Unsupported and unknown options are treated as an "unknown command" error.
* The do_pick options are documented in the same order they are listed in the function signature. Moreover, it is mentioned which options rewrite commits being picked.
* The test cases output differing actual values as changes to the expected values and not the other way around. Moreover, the failed rebases are not cleaned up until the respective test succeeds.
Two stages (and two sub-stages) can be identified in the rerolled patch series:
1. Route primary to-do list commands through do_pick
a. Implement reword in terms of do_pick (5/19) b. Implement squash in terms of do_pick (14/19)
2. Add user options to main commands
Enable options --signoff, --reset-author for pick, reword (19/19)
The last stage was added in this reroll. It enables the parsing of line options for to-do list commands, which is still restricted to options without arguments because it is unclear how spaces can be parsed as characters rather than separators where needed. For instance, if we were to support
pick --author="A U Thor" fa1afe1 Some changes
then read(1) would hand us the tokens --author="A, U and Thor" instead of --author=A U Thor, which we would want to relay to do_pick. Interpreting the shell quoting would help. However, eval(1) seems to disqualify itself as an interpreter because the to-do list entry could potentially contain any shell command line. This could be both a security and a usability issue. For this reason, the patch series still hasn't graduated from being RFC.
Fabian
Fabian Ruch (19):
rebase -i: Failed reword prints redundant error message rebase -i: reword complains about empty commit despite --keep-empty rebase -i: reword executes pre-commit hook on interim commit rebase -i: Teach do_pick the option --edit
rebase -i: Implement reword in terms of do_pick
rebase -i: Stop on root commits with empty log messages rebase -i: The replay of root commits is not shown with --verbose rebase -i: Root commits are replayed with an unnecessary option rebase -i: Commit only once when rewriting picks
rebase -i: Do not die in do_pick
rebase -i: Teach do_pick the option --amend
rebase -i: Teach do_pick the option --file
rebase -i: Prepare for squash in terms of do_pick --amend rebase -i: Implement squash in terms of do_pick
rebase -i: Explicitly distinguish replay commands and exec tasks rebase -i: Parse to-do list command line options
rebase -i: Teach do_pick the option --reset-author
rebase -i: Teach do_pick the option --signoff
rebase -i: Enable options --signoff, --reset-author for pick, reword
git-rebase--interactive.sh | 277 ++++++++++++++++++++++++++++++++++-------- t/t3404-rebase-interactive.sh | 8 ++ t/t3412-rebase-root.sh | 39 ++++++ 3 files changed, 273 insertions(+), 51 deletions(-)
Signature · 1 line
2.0.0
RFC PATCH v2 1/19 rebase -i: Failed reword prints redundant error message
The to-do list command reword replays a commit like pick but lets the user also edit the commit's log message. If the edited log message is empty or is found ill-formatted by one of the commit hooks, git-rebase--interactive prints three error messages to the console.
1. The git-commit output, which contains all the output from hook scripts.
2. A rebase diagnosis saying at which task on the to-do list it got stuck.
3. Generic presumptions about what could have triggered the error.
The third message contains redundant information and does not add any enlightenment either, which makes the output unnecessarily longish and different from the other command's output. For instance, this is what the output looks like if the log message is empty (contains duplicate Signed-off-by lines).
(1.) Aborting commit due to empty commit message. (Duplicate Signed-off-by lines.) (2.) Could not amend commit after successfully picking fa1afe1... Some change (3.) This is most likely due to an empty commit message, or the pre-commit hook
failed. If the pre-commit hook failed, you may need to resolve the issue before you are able to reword the commit.
Discard the third message.
It is true that a failed hook script might not output any diagnosis but then the generic message is not of much help either. Since this lack of information affects the built-in git commands for commit, merge and cherry-pick first, the solution would be to keep track of the failed hooks in their output so that the user knows which of her hooks require improvement.
--- git-rebase--interactive.sh | 3 --- 1 file changed, 3 deletions(-)
git-rebase--interactive.sh +0 -3
@@ -506,9 +506,6 @@ do_next () { do_pick $sha1 "$rest" git commit --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} || { warn "Could not amend commit after successfully picking $sha1... $rest" - warn "This is most likely due to an empty commit message, or the pre-commit hook" - warn "failed. If the pre-commit hook failed, you may need to resolve the issue before" - warn "you are able to reword the commit." exit_with_patch $sha1 1 } record_in_rewritten $sha1
Signature · 1 line
2.0.0
Re: [PATCH RFC v2 01/19] rebase -i: Failed reword prints redundant error message
Quoted 25 lines · Fabian Ruch
The to-do list command
rewordreplays a commit likepickbut lets the user also edit the commit's log message. If the edited log message is empty or is found ill-formatted by one of the commit hooks, git-rebase--interactive prints three error messages to the console.1. The git-commit output, which contains all the output from hook scripts.
2. A rebase diagnosis saying at which task on the to-do list it got stuck.
3. Generic presumptions about what could have triggered the error.The third message contains redundant information and does not add any enlightenment either, which makes the output unnecessarily longish and different from the other command's output. For instance, this is what the output looks like if the log message is empty (contains duplicate Signed-off-by lines).
(1.) Aborting commit due to empty commit message. (Duplicate Signed-off-by lines.) (2.) Could not amend commit after successfully picking fa1afe1... Some change (3.) This is most likely due to an empty commit message, or the pre-commit hook
failed. If the pre-commit hook failed, you may need to resolve the issue before you are able to reword the commit.
Quoted 3 lines
Discard the third message.
It is true that a failed hook script might not output any diagnosis...
I think the message originally came from 0becb3e4 (rebase -i:
interrupt rebase when "commit --amend" failed during "reword", 2011-11-30); it seems that the primary point of the change was to make sure it exits and the warning message may not have been well thought out, but before discarding the result of somebody else's work, it may not be a bad idea to ask just in case you may have overlooked something (Andrew Wong Cc'ed).
Quoted 25 lines
but then the generic message is not of much help either. Since this lack of information affects the built-in git commands for commit, merge and cherry-pick first, the solution would be to keep track of the failed hooks in their output so that the user knows which of her hooks require improvement.
Signed-off-by Fabian Ruch <[email protected]>--- git-rebase--interactive.sh | 3 --- 1 file changed, 3 deletions(-)git-rebase--interactive.sh +0
-3@@ -506,9 +506,6 @@ do_next () { do_pick $sha1 "$rest" git commit --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} || { warn "Could not amend commit after successfully picking $sha1... $rest" - warn "This is most likely due to an empty commit message, or the pre-commit hook" - warn "failed. If the pre-commit hook failed, you may need to resolve the issue before" - warn "you are able to reword the commit." exit_with_patch $sha1 1 } record_in_rewritten $sha1
Re: [PATCH RFC v2 01/19] rebase -i: Failed reword prints redundant error message
Quoted 20 lines · Junio C Hamano
Quoted 1 line · Fabian Ruch
It is true that a failed hook script might not output any diagnosis...
I think the message originally came from
0becb3e4(rebase -i:
interrupt rebase when "commit --amend" failed during "reword", 2011-11-30); it seems that the primary point of the change was to make sure it exits and the warning message may not have been well thought out, but before discarding the result of somebody else's work, it may not be a bad idea to ask just in case you may have overlooked something (Andrew Wong Cc'ed).Quoted 5 lines
but then the generic message is not of much help either. Since this lack of information affects the built-in git commands for commit, merge and cherry-pick first, the solution would be to keep track of the failed hooks in their output so that the user knows which of her hooks require improvement.
Since "git commit" already prints out error messages for failing due to empty commit message, the third message is really about giving hints in the case where pre-commit fails. We could probably assume that pre-commit would also print out error messages. So I'd be fine with removing the third message. But I wonder if we need to explain that the user needs to run "git rebase --continue" to resume the rebase?
Re: [PATCH RFC v2 01/19] rebase -i: Failed reword prints redundant error message
Hi Andrew,
thanks for your review and sorry that I forgot to cc the bug fix to you.
Quoted 18 lines · Andrew Wong
Quoted 16 lines · Junio C Hamano
Quoted 1 line · Fabian Ruch
It is true that a failed hook script might not output any diagnosis...
I think the message originally came from
0becb3e4(rebase -i:
interrupt rebase when "commit --amend" failed during "reword", 2011-11-30); it seems that the primary point of the change was to make sure it exits and the warning message may not have been well thought out, but before discarding the result of somebody else's work, it may not be a bad idea to ask just in case you may have overlooked something (Andrew Wong Cc'ed).Quoted 5 lines
but then the generic message is not of much help either. Since this lack of information affects the built-in git commands for commit, merge and cherry-pick first, the solution would be to keep track of the failed hooks in their output so that the user knows which of her hooks require improvement.
Quoted 7 lines
Since "git commit" already prints out error messages for failing due to empty commit message, the third message is really about giving hints in the case where pre-commit fails. We could probably assume that pre-commit would also print out error messages. So I'd be fine with removing the third message. But I wonder if we need to explain that the user needs to run "git rebase --continue" to resume the rebase?
That is still taken care of by exit_with_patch here. When called in the error case, it prints
You can amend the commit now, with
git commit --amend
Once you are satisfied with your changes, run
git rebase --continue
to standard error. I might have overlooked this in one of the later patches though.
Fabian
Re: [PATCH RFC v2 01/19] rebase -i: Failed reword prints redundant error message
Quoted 1 line · Fabian Ruch
That is still taken care of by
exit_with_patchhere.
Oh, that's right. Ok, go ahead and remove the third warning then. Thanks!
RFC PATCH v2 2/19 rebase -i: reword complains about empty commit despite --keep-empty
In reply to Fabian RuchThe to-do list command reword replays a commit like pick but lets the user also edit the commit's log message. If --keep-empty is passed as option to git-rebase--interactive, empty commits ought to be replayed without complaints. However, if the users chooses to reword an empty commit by changing the respective to-do list entry from
pick fa1afe1 Empty commit
to
reword fa1afe1 Empty commit
, then git-rebase--interactive suddenly fails to replay the empty commit. This is especially counterintuitive because reword is thought of as a pick that alters the log message in some way but nothing more and the unchanged to-do list entry would not fail.
Handle reword by cherry-picking the named commit and editing the log message using
git commit --allow-empty --amend
instead of
git commit --amend.
Add test.
--- git-rebase--interactive.sh | 2 +- t/t3404-rebase-interactive.sh | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-)
git-rebase--interactive.sh +1 -1
@@ -504,7 +504,7 @@ do_next () { mark_action_done do_pick $sha1 "$rest" - git commit --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} || { + git commit --allow-empty --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} || { warn "Could not amend commit after successfully picking $sha1... $rest" exit_with_patch $sha1 1 }
t/t3404-rebase-interactive.sh +8 -0
@@ -75,6 +75,14 @@ test_expect_success 'rebase --keep-empty' ' test_line_count = 6 actual ' +test_expect_success 'rebase --keep-empty' ' + git checkout emptybranch && + set_fake_editor && + FAKE_LINES="1 reword 2" git rebase --keep-empty -i HEAD~2 && + git log --oneline >actual && + test_line_count = 6 actual +' + test_expect_success 'rebase -i with the exec command' ' git checkout master && (
Signature · 1 line
2.0.0
Re: [PATCH RFC v2 02/19] rebase -i: reword complains about empty commit despite --keep-empty
Quoted 1 line · Fabian Ruch
Subject: rebase -i: reword complains about empty commit despite --keep-empty
I had to read the title and then the log twice before understanding that the above is not "change the complaint message" (i.e. "reword complaints" spelled incorrectly) but is a description of the current behaviour (i.e. "the command complains when 'reword' is used on an empty commit") that is not accompanied by an evaluation ("ok, it complains; are you saying it is a good thing or a bad thing?") or an action ("if it is a bad thing, what are you doing about it?").
Perhaps
rebase -i: allow rewording an empty commit
or something?
Quoted 67 lines
The to-do list command
rewordreplays a commit likepickbut lets the user also edit the commit's log message. If--keep-emptyis passed as option to git-rebase--interactive, empty commits ought to be replayed without complaints. However, if the users chooses to reword an empty commit by changing the respective to-do list entry frompick fa1afe1 Empty commit
to
reword fa1afe1 Empty commit
, then git-rebase--interactive suddenly fails to replay the empty commit. This is especially counterintuitive because
rewordis thought of as apickthat alters the log message in some way but nothing more and the unchanged to-do list entry would not fail.Handle
rewordby cherry-picking the named commit and editing the log message usinggit commit --allow-empty --amendinstead of
git commit --amend.Add test.
Signed-off-by Fabian Ruch <[email protected]>--- git-rebase--interactive.sh | 2 +- t/t3404-rebase-interactive.sh | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-)git-rebase--interactive.sh +1
-1@@ -504,7 +504,7 @@ do_next () { mark_action_done do_pick $sha1 "$rest" - git commit --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} || { + git commit --allow-empty --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} || { warn "Could not amend commit after successfully picking $sha1... $rest" exit_with_patch $sha1 1 }t/t3404-rebase-interactive.sh +8
-0@@ -75,6 +75,14 @@ test_expect_success 'rebase --keep-empty' ' test_line_count = 6 actual ' +test_expect_success 'rebase --keep-empty' ' + git checkout emptybranch && + set_fake_editor && + FAKE_LINES="1 reword 2" git rebase --keep-empty -i HEAD~2 && + git log --oneline >actual && + test_line_count = 6 actual +' + test_expect_success 'rebase -i with the exec command' ' git checkout master && (
Re: [PATCH RFC v2 02/19] rebase -i: reword complains about empty commit despite --keep-empty
Quoted 11 lines · Junio C Hamano
Quoted 1 line · Fabian Ruch
Subject: rebase -i: reword complains about empty commit despite --keep-empty
I had to read the title and then the log twice before understanding that the above is not "change the complaint message" (i.e. "reword complaints" spelled incorrectly) but is a description of the current behaviour (i.e. "the command complains when 'reword' is used on an empty commit") that is not accompanied by an evaluation ("ok, it complains; are you saying it is a good thing or a bad thing?") or an action ("if it is a bad thing, what are you doing about it?").
Quoted 5 lines
Perhaps
rebase -i: allow rewording an empty commit
or something?
I thought "...despite --keep-empty" would already imply that "reword complains about empty commit" is not supposed to happen, the action would have been obvious. However, I understand that --keep-empty is first of all concerned with which commits of $upstream...$orig_head end up on the initial to-do list and the git-rebase manual page doesn't mention that it picks commits using the --allow-empty option. It is simply a necessity of a script not to complain about something it put on the to-do list itself.
The subject reads now
rebase -i: allow rewording an empty commit without complaints
trying to convey that this is not a new feature but a bug fix.
Fabian
RFC PATCH v2 3/19 rebase -i: reword executes pre-commit hook on interim commit
In reply to Fabian RuchThe to-do list command reword replays a commit like pick but lets the user also edit the commit's log message. This happens in two steps. Firstly, the named commit is cherry-picked. Secondly, the commit created in the first step is amended using an unchanged index to edit the log message. The pre-commit hook is meant to verify the changes introduced by a commit (for instance, catching inserted trailing white space). Since the committed tree is not changed between the two steps, do not execute the pre-commit hook in the second step.
Specify the git-commit option --no-verify to disable the pre-commit hook when editing the log message. Because --no-verify also skips the commit-msg hook, execute the hook from within git-rebase--interactive after the commit is created. Fortunately, the commit message is still available in $GIT_DIR/COMMIT_EDITMSG after git-commit terminates. Caveat: In case the commit-msg hook finds the new log message ill-formatted, the user is only notified of the failed commit-msg hook but the log message is used for the commit anyway. git-commit ought to offer more fine-grained control over which hooks are executed.
--- git-rebase--interactive.sh | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-)
git-rebase--interactive.sh +13 -4
@@ -504,10 +504,19 @@ do_next () { mark_action_done do_pick $sha1 "$rest" - git commit --allow-empty --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} || { - warn "Could not amend commit after successfully picking $sha1... $rest" - exit_with_patch $sha1 1 - } + # TODO: Work around the fact that git-commit lets us + # disable either both the pre-commit and the commit-msg + # hook or none. Disable the pre-commit hook because the + # tree is left unchanged but run the commit-msg hook + # from here because the log message is altered. + git commit --allow-empty --amend --no-post-rewrite -n ${gpg_sign_opt:+"$gpg_sign_opt"} && + if test -x "$GIT_DIR"/hooks/commit-msg + then + "$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG + fi || { + warn "Could not amend commit after successfully picking $sha1... $rest" + exit_with_patch $sha1 1 + } record_in_rewritten $sha1 ;; edit|e)
Signature · 1 line
2.0.0
Re: [PATCH RFC v2 03/19] rebase -i: reword executes pre-commit hook on interim commit
Quoted 9 lines · Fabian Ruch
The to-do list command
rewordreplays a commit likepickbut lets the user also edit the commit's log message. This happens in two steps. Firstly, the named commit is cherry-picked. Secondly, the commit created in the first step is amended using an unchanged index to edit the log message. The pre-commit hook is meant to verify the changes introduced by a commit (for instance, catching inserted trailing white space). Since the committed tree is not changed between the two steps, do not execute the pre-commit hook in the second step.
It is not like the second step is built as a child commit of the result from the first step, recording the same tree without any change. Why is it a good thing not to run the pre-commit hook (or other hooks for that matter)? After all, the result of the second step is what is recorded in the final history; it just feels wrong not to test that one, even if it were a good idea to test only once.
Quoted 44 lines
Specify the git-commit option
--no-verifyto disable the pre-commit hook when editing the log message. Because--no-verifyalso skips the commit-msg hook, execute the hook from within git-rebase--interactive after the commit is created. Fortunately, the commit message is still available in$GIT_DIR/COMMIT_EDITMSGafter git-commit terminates. Caveat: In case the commit-msg hook finds the new log message ill-formatted, the user is only notified of the failed commit-msg hook but the log message is used for the commit anyway. git-commit ought to offer more fine-grained control over which hooks are executed.Signed-off-by Fabian Ruch <[email protected]>--- git-rebase--interactive.sh | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-)git-rebase--interactive.sh +13
-4@@ -504,10 +504,19 @@ do_next () { mark_action_done do_pick $sha1 "$rest" - git commit --allow-empty --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} || { - warn "Could not amend commit after successfully picking $sha1... $rest" - exit_with_patch $sha1 1 - } + # TODO: Work around the fact that git-commit lets us + # disable either both the pre-commit and the commit-msg + # hook or none. Disable the pre-commit hook because the + # tree is left unchanged but run the commit-msg hook + # from here because the log message is altered. + git commit --allow-empty --amend --no-post-rewrite -n ${gpg_sign_opt:+"$gpg_sign_opt"} && + if test -x "$GIT_DIR"/hooks/commit-msg + then + "$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG + fi || { + warn "Could not amend commit after successfully picking $sha1... $rest" + exit_with_patch $sha1 1 + } record_in_rewritten $sha1 ;; edit|e)
Re: [PATCH RFC v2 03/19] rebase -i: reword executes pre-commit hook on interim commit
Hi Junio,
Quoted 11 lines · Junio C Hamano
Quoted 9 lines · Fabian Ruch
The to-do list command
rewordreplays a commit likepickbut lets the user also edit the commit's log message. This happens in two steps. Firstly, the named commit is cherry-picked. Secondly, the commit created in the first step is amended using an unchanged index to edit the log message. The pre-commit hook is meant to verify the changes introduced by a commit (for instance, catching inserted trailing white space). Since the committed tree is not changed between the two steps, do not execute the pre-commit hook in the second step.
Quoted 6 lines
It is not like the second step is built as a child commit of the result from the first step, recording the same tree without any change. Why is it a good thing not to run the pre-commit hook (or other hooks for that matter)? After all, the result of the second step is what is recorded in the final history; it just feels wrong not to test that one, even if it were a good idea to test only once.
if I understand correctly, the tree of the (amended) commit isn't tested at all by git-rebase--interactive because git-cherry-pick, and therefore pick_one, commits with both the pre-commit and commit-msg hook disabled (see the "git commit -n" command line being built in sequencer.c#run_git_commit since revision 9fa4db5).
The "commit --amend" we are concerned with here amends using an untouched tree so that the history ought to record exactly the same trees as prior to "commit --amend". If git-cherry-pick was testing the tree, then it would be unnecessary to test the tree a second time. Since git-cherry-pick is not testing as of yet, testing and possibly rejecting a tree in the second step would actually be wrong. I totally neglected to look for a test case when I submitted this patch, so I'd like to supply one late now:
Quoted 8 lines
test_expect_success'reword a commit violating pre-commit' 'mkdir -p .git/hooks && PRE_COMMIT=.git/hooks/pre-commit && cat >"$PRE_COMMIT" <<EOF#!/bin/sh
echo running pre-commit
exit 1
EOF
Quoted 8 lines
chmod +x "$PRE_COMMIT" && test_must_fail test_commit file && test_commit --no-verify file && set_fake_editor && FAKE_LINES="pick 1" git rebase -i HEAD^ && FAKE_LINES="pick 1" git rebase -i --no-ff HEAD^ && FAKE_LINES="reword 1" git rebase -i HEAD^'
(This addition to t3404-rebase--interactive.sh is based on the test case "rebase a commit violating pre-commit"; "test_commit --no-verify" will be implemented the obvious way.)
In both cases, it's correct to disable the pre-commit hook when editing the log message and it just makes sense to test changes where you make them. Unfortunately, it is not obvious that "git commit --amend" merely edits the log message rather than committing a new tree.
For what it's worth, if we were to create an empty child commit and squash it into the parent instead of amending immediately, then git-rebase--interactive would disable tree verification in the second step as well. This behaviour was introduced by commit c5b09fe. Although the change seems to have been triggered by something completely different back then, the correctness criterion remains the same, i.e. recording previously committed trees.
Best regards,
Fabian
Quoted 44 lines
Quoted 44 lines
Specify the git-commit option
--no-verifyto disable the pre-commit hook when editing the log message. Because--no-verifyalso skips the commit-msg hook, execute the hook from within git-rebase--interactive after the commit is created. Fortunately, the commit message is still available in$GIT_DIR/COMMIT_EDITMSGafter git-commit terminates. Caveat: In case the commit-msg hook finds the new log message ill-formatted, the user is only notified of the failed commit-msg hook but the log message is used for the commit anyway. git-commit ought to offer more fine-grained control over which hooks are executed.Signed-off-by Fabian Ruch <[email protected]>--- git-rebase--interactive.sh | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-)git-rebase--interactive.sh +13
-4@@ -504,10 +504,19 @@ do_next () { mark_action_done do_pick $sha1 "$rest" - git commit --allow-empty --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} || { - warn "Could not amend commit after successfully picking $sha1... $rest" - exit_with_patch $sha1 1 - } + # TODO: Work around the fact that git-commit lets us + # disable either both the pre-commit and the commit-msg + # hook or none. Disable the pre-commit hook because the + # tree is left unchanged but run the commit-msg hook + # from here because the log message is altered. + git commit --allow-empty --amend --no-post-rewrite -n ${gpg_sign_opt:+"$gpg_sign_opt"} && + if test -x "$GIT_DIR"/hooks/commit-msg + then + "$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG + fi || { + warn "Could not amend commit after successfully picking $sha1... $rest" + exit_with_patch $sha1 1 + } record_in_rewritten $sha1 ;; edit|e)
RFC PATCH v2 4/19 rebase -i: Teach do_pick the option --edit
In reply to Fabian Ruchdo_pick is the git-cherry-pick wrapper in git-rebase--interactive that is used to implement the to-do list command pick. To cater for the different pick behaviours (like reword), do_pick accepts several options not only from the git-cherry-pick but also the git-commit interface. Add the common option --edit to let the user edit the log message of the named commit.
Loop over $@ to parse the do_pick arguments. Assign the local variable edit if one of the options is --edit so that the remainder of do_pick can easily check whether the client code asked to edit the commit message. If one of the options is unknown, mention it on the console and die. Break the loop on the first non-option and do some sanity checking to ensure that there exactly two non-options, which are interpreted by the remainder as <commit> and <title> like before.
do_pick ought to act as a wrapper around cherry-pick. Unfortunately, it cannot just forward --edit to the cherry-pick command line. The assembled command line is executed within a command substitution for controlling the verbosity of rebase--interactive. Passing --edit would either hang the terminal or clutter the substituted command output with control sequences. Execute the reword code from do_next instead if the option --edit is specified.
--- git-rebase--interactive.sh | 52 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+)
git-rebase--interactive.sh +52 -0
@@ -461,7 +461,42 @@ record_in_rewritten() { esac } +# Apply the changes introduced by the given commit to the current head. +# +# do_pick [--edit] <commit> <title> +# +# Wrapper around git-cherry-pick. +# +# -e, --edit +# After picking <commit>, open an editor and let the user edit the +# commit message. The editor contents becomes the commit message of +# the new head. This creates a fresh commit. +# +# <commit> +# The commit to cherry-pick. +# +# <title> +# The commit message title of <commit>. Used for information +# purposes only. do_pick () { + edit= + while test $# -gt 0 + do + case "$1" in + -e|--edit) + edit=y + ;; + -*) + die "do_pick: unrecognized option -- $1" + ;; + *) + break + ;; + esac + shift + done + test $# -ne 2 && die "do_pick: wrong number of arguments" + if test "$(git rev-parse HEAD)" = "$squash_onto" then # Set the correct commit message and author info on the
@@ -483,6 +518,23 @@ do_pick () { pick_one $1 || die_with_patch $1 "Could not apply $1... $2" fi + + if test -n "$edit" + then + # TODO: Work around the fact that git-commit lets us + # disable either both the pre-commit and the commit-msg + # hook or none. Disable the pre-commit hook because the + # tree is left unchanged but run the commit-msg hook + # from here because the log message is altered. + git commit --allow-empty --amend --no-post-rewrite -n ${gpg_sign_opt:+"$gpg_sign_opt"} && + if test -x "$GIT_DIR"/hooks/commit-msg + then + "$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG + fi || { + warn "Could not amend commit after successfully picking $1... $2" + exit_with_patch $1 1 + } + fi } do_next () {
Signature · 1 line
2.0.0
RFC PATCH v2 5/19 rebase -i: Implement reword in terms of do_pick
In reply to Fabian RuchThe to-do list command reword replays a commit like pick but lets the user also edit the commit's log message. If one thinks of pick entries as scheduled cherry-pick command lines, then reword becomes an alias for the command line cherry-pick --edit. The porcelain rebase--interactive defines a function do_pick for processing the pick entries on to-do lists. Reimplement reword in terms of do_pick --edit.
If the user picks a commit using the to-do list line
reword fa1afe1 Some change
execute the command do_pick --edit fa1afe1 "Some change" which carries out exactly the same steps as the case arm for reword in do_next so far.
--- git-rebase--interactive.sh | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-)
git-rebase--interactive.sh +1 -14
@@ -555,20 +555,7 @@ do_next () { comment_for_reflog reword mark_action_done - do_pick $sha1 "$rest" - # TODO: Work around the fact that git-commit lets us - # disable either both the pre-commit and the commit-msg - # hook or none. Disable the pre-commit hook because the - # tree is left unchanged but run the commit-msg hook - # from here because the log message is altered. - git commit --allow-empty --amend --no-post-rewrite -n ${gpg_sign_opt:+"$gpg_sign_opt"} && - if test -x "$GIT_DIR"/hooks/commit-msg - then - "$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG - fi || { - warn "Could not amend commit after successfully picking $sha1... $rest" - exit_with_patch $sha1 1 - } + do_pick --edit $sha1 "$rest" record_in_rewritten $sha1 ;; edit|e)
Signature · 1 line
2.0.0
Re: [PATCH RFC v2 05/19] rebase -i: Implement reword in terms of do_pick
Quoted 11 lines · Fabian Ruch
git-rebase--interactive.sh +0
-5@@ -555,20 +555,7 @@ do_next () { comment_for_reflog reword mark_action_done - do_pick $sha1 "$rest" - # TODO: Work around the fact that git-commit lets us - # disable either both the pre-commit and the commit-msg - # hook or none. Disable the pre-commit hook because the - # tree is left unchanged but run the commit-msg hook
git-rebase--interactive.sh do_next () { · line 558
Quoted 10 lines
git-rebase--interactive.sh +1
-9- # from here because the log message is altered. - git commit --allow-empty --amend --no-post-rewrite -n ${gpg_sign_opt:+"$gpg_sign_opt"} && - if test -x "$GIT_DIR"/hooks/commit-msg - then - "$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG - fi || { - warn "Could not amend commit after successfully picking $sha1... $rest" - exit_with_patch $sha1 1 - } + do_pick --edit $sha1 "$rest"
I would have found this easier to review if squashed into the previous patch. My reaction reading the previous patch was "Uh, why duplicate code?", and reading this one "Ah, that's OK". A single patch doing both would have avoided the confusion.
Signature · 2 lines
Matthieu Moy http://www-verimag.imag.fr/~moy/
Re: [PATCH RFC v2 05/19] rebase -i: Implement reword in terms of do_pick
Hi Matthieu,
thanks for taking a look at this patch series. I might have caused some confusion by starting with version v1 again after removing the RFC tag. The current reroll[1] is tagged with PATCH v1, not PATCH RFC v2.
I'm sorry if this is the reason why your reply appears on this sub-thread.
Your concerns below are of course noted.
Fabian
[1] http://article.gmane.org/gmane.comp.version-control.git/254361
Quoted 27 lines · Matthieu Moy
Quoted 11 lines · Fabian Ruch
git-rebase--interactive.sh +0
-5@@ -555,20 +555,7 @@ do_next () { comment_for_reflog reword mark_action_done - do_pick $sha1 "$rest" - # TODO: Work around the fact that git-commit lets us - # disable either both the pre-commit and the commit-msg - # hook or none. Disable the pre-commit hook because the - # tree is left unchanged but run the commit-msg hookgit-rebase--interactive.sh do_next () { · line 558
Quoted 10 lines
git-rebase--interactive.sh +1
-9- # from here because the log message is altered. - git commit --allow-empty --amend --no-post-rewrite -n ${gpg_sign_opt:+"$gpg_sign_opt"} && - if test -x "$GIT_DIR"/hooks/commit-msg - then - "$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG - fi || { - warn "Could not amend commit after successfully picking $sha1... $rest" - exit_with_patch $sha1 1 - } + do_pick --edit $sha1 "$rest"I would have found this easier to review if squashed into the previous patch. My reaction reading the previous patch was "Uh, why duplicate code?", and reading this one "Ah, that's OK". A single patch doing both would have avoided the confusion.
Re: [PATCH RFC v2 00/19] Enable options --signoff, --reset-author for pick, reword
In reply to Fabian RuchQuoted 13 lines · Fabian Ruch
[...]
2. Add user options to main commandsEnable options --signoff, --reset-author for pick, reword (19/19)
The last stage was added in this reroll. It enables the parsing of line options for to-do list commands, which is still restricted to options without arguments because it is unclear how spaces can be parsed as characters rather than separators where needed. For instance, if we were to support
pick --author="A U Thor" fa1afe1 Some changes
Quoted 8 lines
then read(1) would hand us the tokens
--author="A,UandThor"instead of--author=A U Thor, which we would want to relay todo_pick. Interpreting the shell quoting would help. However, eval(1) seems to disqualify itself as an interpreter because the to-do list entry could potentially contain any shell command line. This could be both a security and a usability issue. For this reason, the patch series still hasn't graduated from being RFC. [...]
It is not required that a patch series solves all of the problems in the universe. If these patches implements some useful features robustly, and if there is no reason to expect that future enhancements will require the user interface to be changed in a backwards-compatible way, then there is no reason that this patch series has to be held as an RFC hostage to some hypothetical future.
Michael
Signature · 2 lines
Michael Haggerty [email protected]
RFC PATCH v2 6/19 rebase -i: Stop on root commits with empty log messages
In reply to Fabian RuchThe command line used to recreate root commits specifies the erroneous option --allow-empty-message. If the root commit has an empty log message, the replay of this commit should fail and the rebase should be interrupted like for any other commit that is on the to-do list and has an empty commit message. Remove the option.
The option might have been introduced by copy-and-paste of the first part of the command line which initializes the authorship of the sentinel commit. Indeed, the sentinel commit has an empty log message and this should not trigger a failure, which is why the option --allow-empty-message is correctly specified here.
Add test.
--- git-rebase--interactive.sh | 2 +- t/t3412-rebase-root.sh | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-)
git-rebase--interactive.sh +1 -1
@@ -510,7 +510,7 @@ do_pick () { git commit --allow-empty --allow-empty-message --amend \ --no-post-rewrite -n -q -C $1 && pick_one -n $1 && - git commit --allow-empty --allow-empty-message \ + git commit --allow-empty \ --amend --no-post-rewrite -n -q -C $1 \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_with_patch $1 "Could not apply $1... $2"
t/t3412-rebase-root.sh +39 -0
@@ -278,4 +278,43 @@ test_expect_success 'rebase -i -p --root with conflict (second part)' ' test_cmp expect-conflict-p out ' +test_expect_success 'stop rebase --root on empty root log message' ' + # create a root commit with a non-empty tree so that rebase does + # not fail because of an empty commit, and an empty log message + echo root-commit >file && + git add file && + tree=$(git write-tree) && + root=$(git commit-tree $tree </dev/null) && + git checkout -b no-message-root-commit $root && + # do not ff because otherwise neither the patch nor the message + # are looked at and checked for emptiness + test_when_finished git rebase --abort && + test_must_fail env EDITOR=true git rebase -i --force-rebase --root && + echo root-commit >file.expected && + test_cmp file.expected file +' + +test_expect_success 'stop rebase --root on empty child log message' ' + # create a root commit with a non-empty tree and provide a log + # message so that rebase does not fail until the root commit is + # successfully replayed + echo root-commit >file && + git add file && + tree=$(git write-tree) && + root=$(git commit-tree $tree -m root-commit) && + git checkout -b no-message-child-commit $root && + # create a child commit with a non-empty patch so that rebase + # does not fail because of an empty commit, but an empty log + # message + echo child-commit >file && + git add file && + git commit --allow-empty-message --no-edit && + # do not ff because otherwise neither the patch nor the message + # are looked at and checked for emptiness + test_when_finished git rebase --abort && + test_must_fail env EDITOR=true git rebase -i --force-rebase --root && + echo child-commit >file.expected && + test_cmp file.expected file +' + test_done
Signature · 1 line
2.0.0
Re: [PATCH RFC v2 06/19] rebase -i: Stop on root commits with empty log messages
Quoted 11 lines · Fabian Ruch
The command line used to recreate root commits specifies the erroneous option
--allow-empty-message. If the root commit has an empty log message, the replay of this commit should fail and the rebase should be interrupted like for any other commit that is on the to-do list and has an empty commit message. Remove the option.The option might have been introduced by copy-and-paste of the first part of the command line which initializes the authorship of the sentinel commit. Indeed, the sentinel commit has an empty log message and this should not trigger a failure, which is why the option
--allow-empty-messageis correctly specified here.
The first "commit --amend" uses -C "$1" to give the amended result not just the authorship but also the log message taken from "$1". If we are allowing a commit without any message to be used as "$1", I think --allow-empty-message needs to be there. If "$1" requires the option here, why doesn't the second one, that records the updated tree with the metainformation taken from the same commit "$1" can successfully commit without the option?
Puzzled...
Quoted 69 lines
Add test.
Signed-off-by Fabian Ruch <[email protected]>--- git-rebase--interactive.sh | 2 +- t/t3412-rebase-root.sh | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-)git-rebase--interactive.sh +1
-1@@ -510,7 +510,7 @@ do_pick () { git commit --allow-empty --allow-empty-message --amend \ --no-post-rewrite -n -q -C $1 && pick_one -n $1 && - git commit --allow-empty --allow-empty-message \ + git commit --allow-empty \ --amend --no-post-rewrite -n -q -C $1 \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_with_patch $1 "Could not apply $1... $2"t/t3412-rebase-root.sh +39
-0@@ -278,4 +278,43 @@ test_expect_success 'rebase -i -p --root with conflict (second part)' ' test_cmp expect-conflict-p out ' +test_expect_success 'stop rebase --root on empty root log message' ' + # create a root commit with a non-empty tree so that rebase does + # not fail because of an empty commit, and an empty log message + echo root-commit >file && + git add file && + tree=$(git write-tree) && + root=$(git commit-tree $tree </dev/null) && + git checkout -b no-message-root-commit $root && + # do not ff because otherwise neither the patch nor the message + # are looked at and checked for emptiness + test_when_finished git rebase --abort && + test_must_fail env EDITOR=true git rebase -i --force-rebase --root && + echo root-commit >file.expected && + test_cmp file.expected file +' + +test_expect_success 'stop rebase --root on empty child log message' ' + # create a root commit with a non-empty tree and provide a log + # message so that rebase does not fail until the root commit is + # successfully replayed + echo root-commit >file && + git add file && + tree=$(git write-tree) && + root=$(git commit-tree $tree -m root-commit) && + git checkout -b no-message-child-commit $root && + # create a child commit with a non-empty patch so that rebase + # does not fail because of an empty commit, but an empty log + # message + echo child-commit >file && + git add file && + git commit --allow-empty-message --no-edit && + # do not ff because otherwise neither the patch nor the message + # are looked at and checked for emptiness + test_when_finished git rebase --abort && + test_must_fail env EDITOR=true git rebase -i --force-rebase --root && + echo child-commit >file.expected && + test_cmp file.expected file +' + test_done
Re: [PATCH RFC v2 06/19] rebase -i: Stop on root commits with empty log messages
Hi Junio,
Quoted 13 lines · Junio C Hamano
Quoted 11 lines · Fabian Ruch
The command line used to recreate root commits specifies the erroneous option
--allow-empty-message. If the root commit has an empty log message, the replay of this commit should fail and the rebase should be interrupted like for any other commit that is on the to-do list and has an empty commit message. Remove the option.The option might have been introduced by copy-and-paste of the first part of the command line which initializes the authorship of the sentinel commit. Indeed, the sentinel commit has an empty log message and this should not trigger a failure, which is why the option
--allow-empty-messageis correctly specified here.
Quoted 7 lines
The first "commit --amend" uses -C "$1" to give the amended result not just the authorship but also the log message taken from "$1". If we are allowing a commit without any message to be used as "$1", I think --allow-empty-message needs to be there. If "$1" requires the option here, why doesn't the second one, that records the updated tree with the metainformation taken from the same commit "$1" can successfully commit without the option?
(I realize now that the emptiness of the sentinel log message is irrelevant to the success of the first "commit --amend" since we are amending using -C. I'll rewrite the second paragraph of the patch description.)
The first "commit --amend" requires --allow-empty-message because we do not want to fail without the authorship or log message of $1 being in place. It's not a matter of allowing or disallowing empty log messages yet.
git-rebase--interactive can come across an empty log message in three different ways, which are, depicted as to-do list tasks, the following.
1) pick --ff $1
2) pick --no-ff $1
3) reword $1
This patch is concerned with consistency in the second case. git-rebase--root does not handle the first case yet and the third case is handled somewhere else in the script independent of the first two.
The --root option handling was added to the script as a special case later in the revision history. It's that option handling which introduced the inconsistency that non-fast-forwards of commits with empty log messages succeed if they are root commits but have always failed otherwise.
Your reply suggests that git-rebase--interactive was wrong from the beginning and that the replay of commits without any message should be allowed. This would reconcile the first case with the second. In fact, since neither of them alters the changes introduced by $1 or its log message, it might be incorrect to complain about a missing message in the first place.
Do you want me to replace this patch with a patch
rebase -i: Always allow picking of commits with empty log messages
that makes git-rebase--interactive cherry-pick commits using --allow-empty-message? The script would still abort an empty reword with the new patch and the user could then still force the empty log message with "git commit --amend --allow-empty-message".
Fabian
Quoted 23 lines
Puzzled...
Quoted 21 lines
Add test.
Signed-off-by Fabian Ruch <[email protected]>--- git-rebase--interactive.sh | 2 +- t/t3412-rebase-root.sh | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-)git-rebase--interactive.sh +1
-1@@ -510,7 +510,7 @@ do_pick () { git commit --allow-empty --allow-empty-message --amend \ --no-post-rewrite -n -q -C $1 && pick_one -n $1 && - git commit --allow-empty --allow-empty-message \ + git commit --allow-empty \ --amend --no-post-rewrite -n -q -C $1 \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_with_patch $1 "Could not apply $1... $2"
Re: [PATCH RFC v2 06/19] rebase -i: Stop on root commits with empty log messages
Quoted 8 lines · Fabian Ruch
Your reply suggests that git-rebase--interactive was wrong from the beginning and that the replay of commits without any message should be allowed. This would reconcile the first case with the second. In fact, since neither of them alters the changes introduced by $1 or its log message, it might be incorrect to complain about a missing message in the first place.
...
Do you want me to replace this patch with a patch
Now you explained your line of thought more clearly and I have thought about it a bit more, I think I agree with the conclusion of the above.
An alternative may be to teach a new option --allow-empty-message to rebase to allow people to replay/reproduce an existing history with commits without any message, and uniformly tighten to refuse replaying a commit without message by default. But I am not sure if that is a good direction to go. Doesn't "rebase" without "-i" by default replay a commit without message faithfully? It might be debatable to allow a commit that we would normally would flag as suspicious (i.e. no change relative to its parent, or no log message) when replaying with "reword" or "edit", but when replaying with "pick", "rebase -i" should behave just like "rebase" without interactive.
Thanks.
Re: [PATCH RFC v2 06/19] rebase -i: Stop on root commits with empty log messages
In reply to Fabian RuchQuoted 7 lines · Fabian Ruch
...
Do you want me to replace this patch with a patchrebase -i: Always allow picking of commits with empty log messages
that makes git-rebase--interactive cherry-pick commits using --allow-empty-message?
I do not "want" any particular behaviour change. I wanted you to clarify what changed behaviour you are trying to implement and why, and that was why I was asking these questions.
RFC PATCH v2 7/19 rebase -i: The replay of root commits is not shown with --verbose
In reply to Fabian RuchThe command line used to recreate root commits specifies the erroneous option -q which suppresses the commit summary message. However, git-rebase--interactive tends to tell the user about the commits it creates, if she wishes (cf. command line option --verbose). The code parts handling non-root commits or squash commits all output commit summary messages. Do not make the replay of root commits an exception. Remove the option.
It is OK to suppress the commit summary when git-commit is used to initialize the authorship of the sentinel commit because the existence of this additional commit is a detail of git-rebase--interactive's implementation. The option -q was probably introduced as a copy-and-paste error stemming from that part of the root commit handling code.
--- git-rebase--interactive.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
git-rebase--interactive.sh +1 -1
@@ -511,7 +511,7 @@ do_pick () { --no-post-rewrite -n -q -C $1 && pick_one -n $1 && git commit --allow-empty \ - --amend --no-post-rewrite -n -q -C $1 \ + --amend --no-post-rewrite -n -C $1 \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_with_patch $1 "Could not apply $1... $2" else
Signature · 1 line
2.0.0
Re: [PATCH RFC v2 07/19] rebase -i: The replay of root commits is not shown with --verbose
Quoted 15 lines · Fabian Ruch
The command line used to recreate root commits specifies the erroneous option
-qwhich suppresses the commit summary message. However, git-rebase--interactive tends to tell the user about the commits it creates, if she wishes (cf. command line option--verbose). The code parts handling non-root commits or squash commits all output commit summary messages. Do not make the replay of root commits an exception. Remove the option.It is OK to suppress the commit summary when git-commit is used to initialize the authorship of the sentinel commit because the existence of this additional commit is a detail of git-rebase--interactive's implementation. The option
-qwas probably introduced as a copy-and-paste error stemming from that part of the root commit handling code.
Quoted 2 lines
Signed-off-by Fabian Ruch <[email protected]>---
This one I can buy; there is no reason to drop "-q" from both (which would give us the same thing twice and the one before the "pick_one -n" runs is not the final one anyway) and the later one that records the updated tree would be the one to report what it did.
Quoted 16 lines
git-rebase--interactive.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)git-rebase--interactive.sh +1
-1@@ -511,7 +511,7 @@ do_pick () { --no-post-rewrite -n -q -C $1 && pick_one -n $1 && git commit --allow-empty \ - --amend --no-post-rewrite -n -q -C $1 \ + --amend --no-post-rewrite -n -C $1 \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_with_patch $1 "Could not apply $1... $2" else
Re: [PATCH RFC v2 07/19] rebase -i: The replay of root commits is not shown with --verbose
In reply to Fabian RuchHi Chris,
you're the original author of the code touched by this patch. Is the second -q option really a simple copy-and-paste of the first or am I overlooking something here? I'd like to confirm this as, in retrospect, I feel a bit uncertain about the hasty claim in the log message.
Kind regards,
Fabian
Quoted 33 lines · Fabian Ruch
The command line used to recreate root commits specifies the erroneous option
-qwhich suppresses the commit summary message. However, git-rebase--interactive tends to tell the user about the commits it creates, if she wishes (cf. command line option--verbose). The code parts handling non-root commits or squash commits all output commit summary messages. Do not make the replay of root commits an exception. Remove the option.It is OK to suppress the commit summary when git-commit is used to initialize the authorship of the sentinel commit because the existence of this additional commit is a detail of git-rebase--interactive's implementation. The option
-qwas probably introduced as a copy-and-paste error stemming from that part of the root commit handling code.Signed-off-by Fabian Ruch <[email protected]>--- git-rebase--interactive.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)git-rebase--interactive.sh +1
-1@@ -511,7 +511,7 @@ do_pick () { --no-post-rewrite -n -q -C $1 && pick_one -n $1 && git commit --allow-empty \ - --amend --no-post-rewrite -n -q -C $1 \ + --amend --no-post-rewrite -n -C $1 \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_with_patch $1 "Could not apply $1... $2" else
Re: [PATCH RFC v2 07/19] rebase -i: The replay of root commits is not shown with --verbose
Quoted 4 lines · Fabian Ruch
you're the original author of the code touched by this patch. Is the second -q option really a simple copy-and-paste of the first or am I overlooking something here? I'd like to confirm this as, in retrospect, I feel a bit uncertain about the hasty claim in the log message.
It was a while ago and I don't remember the details of this patch I'm afraid, but your rationale here looks sensible to me. I tend to find git too noisy by default rather than too quiet, so it wouldn't surprise me if I never thought to try the --verbose version.
Cheers,
Chris.
RFC PATCH v2 8/19 rebase -i: Root commits are replayed with an unnecessary option
In reply to Fabian RuchThe command line used to recreate root commits specifies the effectless option -C. It is used to reuse commit message and authorship from the named commit but the commit being amended here, which is the sentinel commit, already carries the authorship and log message of the processed commit. Note that the committer email and commit date fields do not match the root commit either way. Remove the option. However, -C (other than -c) does not invoke the editor and the --amend option invokes it by default. Disable editor invocation again by specifying --no-edit.
--- git-rebase--interactive.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
git-rebase--interactive.sh +1 -1
@@ -511,7 +511,7 @@ do_pick () { --no-post-rewrite -n -q -C $1 && pick_one -n $1 && git commit --allow-empty \ - --amend --no-post-rewrite -n -C $1 \ + --amend --no-post-rewrite -n --no-edit \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_with_patch $1 "Could not apply $1... $2" else
Signature · 1 line
2.0.0
Re: [PATCH RFC v2 08/19] rebase -i: Root commits are replayed with an unnecessary option
Quoted 9 lines · Fabian Ruch
The command line used to recreate root commits specifies the effectless option
-C. It is used to reuse commit message and authorship from the named commit but the commit being amended here, which is the sentinel commit, already carries the authorship and log message of the processed commit. Note that the committer email and commit date fields do not match the root commit either way. Remove the option. However,-C(other than-c) does not invoke the editor and the--amendoption invokes it by default. Disable editor invocation again by specifying--no-edit.
I'd say this is a no-value change, in the sense that it can be written either way for the same effect and if the original were written with "--amend --no-edit" then there would be no reason to change it to "-C $1" because such a change would also be equally a no-value change.
What exactly are we gaining? Performance? Correctness?
Quoted 18 lines
Signed-off-by Fabian Ruch <[email protected]>--- git-rebase--interactive.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)git-rebase--interactive.sh +1
-1@@ -511,7 +511,7 @@ do_pick () { --no-post-rewrite -n -q -C $1 && pick_one -n $1 && git commit --allow-empty \ - --amend --no-post-rewrite -n -C $1 \ + --amend --no-post-rewrite -n --no-edit \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_with_patch $1 "Could not apply $1... $2" else
Re: [PATCH RFC v2 08/19] rebase -i: Root commits are replayed with an unnecessary option
Hi Junio,
Quoted 11 lines · Junio C Hamano
Quoted 9 lines · Fabian Ruch
The command line used to recreate root commits specifies the effectless option
-C. It is used to reuse commit message and authorship from the named commit but the commit being amended here, which is the sentinel commit, already carries the authorship and log message of the processed commit. Note that the committer email and commit date fields do not match the root commit either way. Remove the option. However,-C(other than-c) does not invoke the editor and the--amendoption invokes it by default. Disable editor invocation again by specifying--no-edit.
Quoted 7 lines
I'd say this is a no-value change, in the sense that it can be written either way for the same effect and if the original were written with "--amend --no-edit" then there would be no reason to change it to "-C $1" because such a change would also be equally a no-value change.
What exactly are we gaining? Performance? Correctness?
I submitted this change separately from the next ("rebase -i: Commit only once when rewriting picks") for the following reasons, at least I thought they were.
It makes the next patch easier to understand because the finalising command line "git commit --allow-empty --amend --no-post-rewrite -n --no-edit" seems to be simply moved to the end of do_pick. Substituting --no-edit for -C there would make that log message overly long and the paragraph saying why this substitution is correct would be distracting (it's a little unfortunate that there is this special case in the first place). While the resulting history stays the same of course, it might be confusing to someone reading the code that the log message gets replaced with itself.
I know the last point is rather weak but aren't the two patches and log messages easier to read?
Fabian
Quoted 18 lines
Quoted 18 lines
Signed-off-by Fabian Ruch <[email protected]>--- git-rebase--interactive.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)git-rebase--interactive.sh +1
-1@@ -511,7 +511,7 @@ do_pick () { --no-post-rewrite -n -q -C $1 && pick_one -n $1 && git commit --allow-empty \ - --amend --no-post-rewrite -n -C $1 \ + --amend --no-post-rewrite -n --no-edit \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_with_patch $1 "Could not apply $1... $2" else
Re: [PATCH RFC v2 08/19] rebase -i: Root commits are replayed with an unnecessary option
Quoted 4 lines · Fabian Ruch
It makes the next patch easier to understand because the finalising command line "git commit --allow-empty --amend --no-post-rewrite -n --no-edit" seems to be simply moved to the end of
do_pick. Substituting --no-edit for -C there would make that log message overly long ...
And the reason why the same "-C $1" is not used and "--no-edit" is used in the final version that is moved to the end of do_pick is...?
Re: [PATCH RFC v2 08/19] rebase -i: Root commits are replayed with an unnecessary option
Hi Junio,
Quoted 8 lines · Junio C Hamano
Quoted 4 lines · Fabian Ruch
It makes the next patch easier to understand because the finalising command line "git commit --allow-empty --amend --no-post-rewrite -n --no-edit" seems to be simply moved to the end of
do_pick. Substituting --no-edit for -C there would make that log message overly long ...And the reason why the same "-C $1" is not used and "--no-edit" is used in the final version that is moved to the end of
do_pickis...?
The finalising command line is executed by do_pick to rewrite $1 when it is being replayed.
For instance, one purpose of rewriting is amending the current head with the changes introduced by $1. In this case, we don't want the log message of $1 to be the final log message. Another purpose of rewriting is using a text file or a string as log message, which would be defeated by "-C $1" as well.
It's true that, by the time the next patch is introduced, do_pick doesn't yet implement either rewriting mechanism (they are both used to thread "squash" through do_pick later on) but these considerations suggest that "-C $1" might not be what we want to use to disable the log message editor.
Fabian
RFC PATCH v2 9/19 rebase -i: Commit only once when rewriting picks
In reply to Fabian RuchThe options passed to do_pick determine whether the picked commit will be rewritten or not. If the commit gets rewritten, because the user requested to edit the commit message for instance, let pick_one merely apply the changes introduced by the commit and do not commit the resulting tree yet. If the commit is replayed as is, leave it to pick_one to recreate the commit (possibly by fast-forwarding the head). This makes it easier to combine git-commit options like --edit and --amend in do_pick because git-cherry-pick does not support --amend.
In the case of --edit, do not exit_with_patch but assign rewrite to pick the changes with -n. If the pick conflicts, no commit is created which we would have to amend when continuing the rebase. To complete the pick after the conflicts are resolved the user just resumes with git rebase --continue.
git-commit lets the user edit the commit log message by default. We do not want that for the rewriting git-commit command line because the default behaviour of git-cherry-pick is exactly the opposite. Pass --no-edit when rewriting a picked commit. An explicit --edit passed to do_pick (for instance, when reword is executed) enables the editor launch again.
If rebase--interactive is used to rebase a complete branch onto some head, rebase creates a sentinel commit that requires special treatment by do_pick. Do not finalize the pick here either because its commit message can be altered as for any other pick. Since the orphaned root commit gets a temporary parent, it is always rewritten. Safely use the rewrite infrastructure of do_pick to create the final commit.
--- git-rebase--interactive.sh | 55 +++++++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 22 deletions(-)
git-rebase--interactive.sh +33 -22
@@ -63,7 +63,8 @@ msgnum="$state_dir"/msgnum author_script="$state_dir"/author-script # When an "edit" rebase command is being processed, the SHA1 of the -# commit to be edited is recorded in this file. When "git rebase +# commit to be edited is recorded in this file. The same happens when +# rewriting a commit fails, for instance "reword". When "git rebase # --continue" is executed, if there are any staged changes then they # will be amended to the HEAD commit, but only provided the HEAD # commit is still the commit to be edited. When any other rebase
@@ -479,12 +480,15 @@ record_in_rewritten() { # The commit message title of <commit>. Used for information # purposes only. do_pick () { - edit= + rewrite= + rewrite_amend= + rewrite_edit= while test $# -gt 0 do case "$1" in -e|--edit) - edit=y + rewrite=y + rewrite_edit=y ;; -*) die "do_pick: unrecognized option -- $1"
@@ -499,6 +503,10 @@ do_pick () { if test "$(git rev-parse HEAD)" = "$squash_onto" then + rewrite=y + rewrite_amend=y + git rev-parse --verify HEAD >"$amend" + # Set the correct commit message and author info on the # sentinel root before cherry-picking the original changes # without committing (-n). Finally, update the sentinel again
@@ -509,31 +517,34 @@ do_pick () { # rebase --continue. git commit --allow-empty --allow-empty-message --amend \ --no-post-rewrite -n -q -C $1 && - pick_one -n $1 && - git commit --allow-empty \ - --amend --no-post-rewrite -n --no-edit \ - ${gpg_sign_opt:+"$gpg_sign_opt"} || + pick_one -n $1 || die_with_patch $1 "Could not apply $1... $2" else - pick_one $1 || + pick_one ${rewrite:+-n} $1 || die_with_patch $1 "Could not apply $1... $2" fi - if test -n "$edit" + if test -n "$rewrite" then - # TODO: Work around the fact that git-commit lets us - # disable either both the pre-commit and the commit-msg - # hook or none. Disable the pre-commit hook because the - # tree is left unchanged but run the commit-msg hook - # from here because the log message is altered. - git commit --allow-empty --amend --no-post-rewrite -n ${gpg_sign_opt:+"$gpg_sign_opt"} && - if test -x "$GIT_DIR"/hooks/commit-msg - then - "$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG - fi || { - warn "Could not amend commit after successfully picking $1... $2" - exit_with_patch $1 1 - } + git commit --allow-empty --no-post-rewrite -n --no-edit \ + ${rewrite_amend:+--amend} \ + ${rewrite_edit:+--edit} \ + ${gpg_sign_opt:+"$gpg_sign_opt"} || + die_with_patch $1 "Could not rewrite commit after successfully picking $1... $2" + fi + + # TODO: Work around the fact that git-commit lets us + # disable either both the pre-commit and the commit-msg + # hook or none. Disable the pre-commit hook because the + # tree is left unchanged but run the commit-msg hook + # from here because the log message is altered. + if test -n "$rewrite_edit" + then + if test -x "$GIT_DIR"/hooks/commit-msg + then + "$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG + fi || + die_with_patch $1 "Could not rewrite commit after successfully picking $1... $2" fi }
Signature · 1 line
2.0.0
RFC PATCH v2 10/19 rebase -i: Do not die in do_pick
In reply to Fabian RuchSince do_pick might be executed in a sub-shell (a modified author environment for instance), calling die in do_pick has no effect but exiting the sub-shell with a failure exit status. The git-rebase--interactive script is not terminated. Moreover, if do_pick is called while a squash or fixup is in effect, die_with_patch will discard $squash_msg as commit message. Lastly, after a die in do_pick do_next has no chance to reschedule tasks that failed before changes could be applied.
Indicate an error in do_pick using return statements and properly kill the script at the call sites. Although possible in principle, the issued error messages are no more indicating whether do_pick failed while applying or while committing the changes. This reduces code complexity at the call site and does not matter from a user's point of view because a glance at the index reveals whether there are conflicts or not and in-depth troubleshooting is still possible using the --verbose option.
Remove the commit message title argument from do_pick's interface, which has become unused.
--- git-rebase--interactive.sh | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-)
git-rebase--interactive.sh +20 -17
@@ -464,7 +464,7 @@ record_in_rewritten() { # Apply the changes introduced by the given commit to the current head. # -# do_pick [--edit] <commit> <title> +# do_pick [--edit] <commit> # # Wrapper around git-cherry-pick. #
@@ -476,9 +476,11 @@ record_in_rewritten() { # <commit> # The commit to cherry-pick. # -# <title> -# The commit message title of <commit>. Used for information -# purposes only. +# The return value is 1 if applying the changes resulted in a conflict +# and 2 if the specified arguments were incorrect. If the changes could +# be applied successfully but creating the commit failed, a value +# greater than 2 is returned. No commit is created in either case and +# the index is left with the (conflicting) changes in place. do_pick () { rewrite= rewrite_amend=
@@ -491,7 +493,8 @@ do_pick () { rewrite_edit=y ;; -*) - die "do_pick: unrecognized option -- $1" + warn "do_pick: unrecognized option -- $1" + return 2 ;; *) break
@@ -499,7 +502,11 @@ do_pick () { esac shift done - test $# -ne 2 && die "do_pick: wrong number of arguments" + if test $# -ne 1 + then + warn "do_pick: wrong number of arguments" + return 2 + fi if test "$(git rev-parse HEAD)" = "$squash_onto" then
@@ -517,11 +524,9 @@ do_pick () { # rebase --continue. git commit --allow-empty --allow-empty-message --amend \ --no-post-rewrite -n -q -C $1 && - pick_one -n $1 || - die_with_patch $1 "Could not apply $1... $2" + pick_one -n $1 || return 1 else - pick_one ${rewrite:+-n} $1 || - die_with_patch $1 "Could not apply $1... $2" + pick_one ${rewrite:+-n} $1 || return 1 fi if test -n "$rewrite"
@@ -529,8 +534,7 @@ do_pick () { git commit --allow-empty --no-post-rewrite -n --no-edit \ ${rewrite_amend:+--amend} \ ${rewrite_edit:+--edit} \ - ${gpg_sign_opt:+"$gpg_sign_opt"} || - die_with_patch $1 "Could not rewrite commit after successfully picking $1... $2" + ${gpg_sign_opt:+"$gpg_sign_opt"} || return 3 fi # TODO: Work around the fact that git-commit lets us
@@ -543,8 +547,7 @@ do_pick () { if test -x "$GIT_DIR"/hooks/commit-msg then "$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG - fi || - die_with_patch $1 "Could not rewrite commit after successfully picking $1... $2" + fi || return 3 fi }
@@ -559,21 +562,21 @@ do_next () { comment_for_reflog pick mark_action_done - do_pick $sha1 "$rest" + do_pick $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" record_in_rewritten $sha1 ;; reword|r) comment_for_reflog reword mark_action_done - do_pick --edit $sha1 "$rest" + do_pick --edit $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" record_in_rewritten $sha1 ;; edit|e) comment_for_reflog edit mark_action_done - do_pick $sha1 "$rest" + do_pick $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" warn "Stopped at $sha1... $rest" exit_with_patch $sha1 0 ;;
Signature · 1 line
2.0.0
RFC PATCH v2 11/19 rebase -i: Teach do_pick the option --amend
In reply to Fabian Ruchdo_pick is the git-cherry-pick wrapper in git-rebase--interactive that is used to implement the to-do list commands pick, reword and edit. To cater for the different pick behaviours (like squash), do_pick accepts several options not only from the git-cherry-pick but also the git-commit interface.
Add the option --amend from the git-commit interface to the options pool of do_pick. It creates a new commit for the changes introduced by the picked commit and the previous one. The previous commit is then replaced with the new commit. If no other options are specified, the log message of the previous commit is used.
Be careful when --amend is used to pick a root commit because HEAD might point to the sentinel commit but there is still nothing to amend. Be sure to initialize amend so that commits are squashed even when git-rebase--interactive is interrupted for resolving conflicts. It is not a mistake to do the initialization regardless of any conflicts because amend is always cleared before the next to-do item is processed.
--- git-rebase--interactive.sh | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-)
git-rebase--interactive.sh +17 -1
@@ -464,10 +464,16 @@ record_in_rewritten() { # Apply the changes introduced by the given commit to the current head. # -# do_pick [--edit] <commit> +# do_pick [--amend] [--edit] <commit> # # Wrapper around git-cherry-pick. # +# --amend +# After picking <commit>, replace the current head commit with a new +# commit that also introduces the changes of <commit>. +# +# _This is not a git-cherry-pick option._ +# # -e, --edit # After picking <commit>, open an editor and let the user edit the # commit message. The editor contents becomes the commit message of
@@ -488,6 +494,16 @@ do_pick () { while test $# -gt 0 do case "$1" in + --amend) + if test "$(git rev-parse HEAD)" = "$squash_onto" || ! git rev-parse --verify HEAD + then + warn "do_pick: nothing to amend" + return 2 + fi + rewrite=y + rewrite_amend=y + git rev-parse --verify HEAD >"$amend" + ;; -e|--edit) rewrite=y rewrite_edit=y
Signature · 1 line
2.0.0
RFC PATCH v2 12/19 rebase -i: Teach do_pick the option --file
In reply to Fabian Ruchdo_pick is the git-cherry-pick wrapper in git-rebase--interactive that is used to implement the to-do list command pick, reword and edit. To cater for the different pick behaviours (like squash), do_pick accepts several options not only from the git-cherry-pick but also the git-commit interface.
Add the option --file from the git-commit interface to the options pool of do_pick. It expects an argument itself which is interpreted as a file path and takes the commit message from the given file. If --file is passed to do_pick, assign the given file path to the local variable rewrite_message and relay the option
--file "$rewrite_message"
to the git-commit command line which creates the commit.
--- git-rebase--interactive.sh | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-)
git-rebase--interactive.sh +19 -1
@@ -464,7 +464,7 @@ record_in_rewritten() { # Apply the changes introduced by the given commit to the current head. # -# do_pick [--amend] [--edit] <commit> +# do_pick [--amend] [--file <file>] [--edit] <commit> # # Wrapper around git-cherry-pick. #
@@ -474,6 +474,12 @@ record_in_rewritten() { # # _This is not a git-cherry-pick option._ # +# -F <file>, --file <file> +# Take the commit message from the given file. This creates a fresh +# commit. +# +# _This is not a git-cherry-pick option._ +# # -e, --edit # After picking <commit>, open an editor and let the user edit the # commit message. The editor contents becomes the commit message of
@@ -491,6 +497,7 @@ do_pick () { rewrite= rewrite_amend= rewrite_edit= + rewrite_message= while test $# -gt 0 do case "$1" in
@@ -504,6 +511,16 @@ do_pick () { rewrite_amend=y git rev-parse --verify HEAD >"$amend" ;; + -F|--file) + if test $# -eq 0 + then + warn "do_pick: option --file specified but no <file> given" + return 2 + fi + rewrite=y + rewrite_message=$2 + shift + ;; -e|--edit) rewrite=y rewrite_edit=y
@@ -550,6 +567,7 @@ do_pick () { git commit --allow-empty --no-post-rewrite -n --no-edit \ ${rewrite_amend:+--amend} \ ${rewrite_edit:+--edit} \ + ${rewrite_message:+--file "$rewrite_message"} \ ${gpg_sign_opt:+"$gpg_sign_opt"} || return 3 fi
Signature · 1 line
2.0.0
RFC PATCH v2 13/19 rebase -i: Prepare for squash in terms of do_pick --amend
In reply to Fabian RuchRewrite squash and fixup handling in do_next using the sequence
pick_one
commit
in order to test the correctness of a single do_squash or parameterised do_pick and make the subsequent patch reimplementing squash in terms of such a single function more readable.
Do not call rm -f "$GIT_DIR"/MERGE_MSG since it has no effect on the state after git-rebase--interactive terminates. The option -F makes git-commit ignore MERGE_MSG for the log message. If git-commit succeeds, MERGE_MSG is removed, and if it fails, MERGE_MSG is overwritten by the error sequence die_failed_squash. In summary, removing MERGE_MSG neither influences the squash commit message nor the file state after git-commit returns.
Moreover, pick_one ignores $GIT_DIR/SQUASH_MSG and does not touch $squash_msg so that it is correct to execute pick_one immediately before git-commit.
Might be squashed into the subsequent commit.
--- git-rebase--interactive.sh | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-)
git-rebase--interactive.sh +15 -6
@@ -633,15 +633,15 @@ do_next () { author_script_content=$(get_author_ident_from_commit HEAD) echo "$author_script_content" > "$author_script" eval "$author_script_content" - if ! pick_one -n $sha1 - then - git rev-parse --verify HEAD >"$amend" - die_failed_squash $sha1 "$rest" - fi case "$(peek_next_command)" in squash|s|fixup|f) # This is an intermediate commit; its message will only be # used in case of trouble. So use the long version: + if ! pick_one -n $sha1 + then + git rev-parse --verify HEAD >"$amend" + die_failed_squash $sha1 "Could not apply $sha1... $rest" + fi do_with_author output git commit --amend --no-verify -F "$squash_msg" \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest"
@@ -650,12 +650,21 @@ do_next () { # This is the final command of this squash/fixup group if test -f "$fixup_msg" then + if ! pick_one -n $sha1 + then + git rev-parse --verify HEAD >"$amend" + die_failed_squash $sha1 "Could not apply $sha1... $rest" + fi do_with_author git commit --amend --no-verify -F "$fixup_msg" \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest" else cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit - rm -f "$GIT_DIR"/MERGE_MSG + if ! pick_one -n $sha1 + then + git rev-parse --verify HEAD >"$amend" + die_failed_squash $sha1 "Could not apply $sha1... $rest" + fi do_with_author git commit --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest"
Signature · 1 line
2.0.0
RFC PATCH v2 14/19 rebase -i: Implement squash in terms of do_pick
In reply to Fabian RuchThe to-do list command squash and its close relative fixup replay the changes of a commit like pick but do not recreate the commit. Instead they replace the previous commit with a new commit that also introduces the changes of the squashed commit. This is roughly like cherry-picking without committing and using git-commit to amend the previous commit.
The to-do list
pick a Some changes
squash b Some more changes
gets translated into the sequence of git commands
git cherry-pick a
git cherry-pick -n b
git commit --amend
and if git-cherry-pick supported --amend this would look even more like the to-do list it is based on
git cherry-pick a
git cherry-pick --amend b.
Since do_pick takes care of pick entries and the above suggests squash as an alias for pick --amend, reimplement squash in terms of do_pick --amend. Introduce $squash_msg as the commit message via the --file option. When the last commit of a squash series is processed, the user is asked to review the log message. Pass --edit as additional option in this case. The only difference in the options passed to git-commit and do_pick is the omitted --no-verify. However, do_pick does not execute the verification hooks anyway because it solely replays commits and assumes that they have been verified before.
--- git-rebase--interactive.sh | 30 ++++++------------------------ 1 file changed, 6 insertions(+), 24 deletions(-)
git-rebase--interactive.sh +6 -24
@@ -637,37 +637,19 @@ do_next () { squash|s|fixup|f) # This is an intermediate commit; its message will only be # used in case of trouble. So use the long version: - if ! pick_one -n $sha1 - then - git rev-parse --verify HEAD >"$amend" - die_failed_squash $sha1 "Could not apply $sha1... $rest" - fi - do_with_author output git commit --amend --no-verify -F "$squash_msg" \ - ${gpg_sign_opt:+"$gpg_sign_opt"} || - die_failed_squash $sha1 "$rest" + do_with_author do_pick --amend -F "$squash_msg" $sha1 \ + || die_failed_squash $sha1 "Could not apply $sha1... $rest" ;; *) # This is the final command of this squash/fixup group if test -f "$fixup_msg" then - if ! pick_one -n $sha1 - then - git rev-parse --verify HEAD >"$amend" - die_failed_squash $sha1 "Could not apply $sha1... $rest" - fi - do_with_author git commit --amend --no-verify -F "$fixup_msg" \ - ${gpg_sign_opt:+"$gpg_sign_opt"} || - die_failed_squash $sha1 "$rest" + do_with_author do_pick --amend -F "$fixup_msg" $sha1 \ + || die_failed_squash $sha1 "Could not apply $sha1... $rest" else cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit - if ! pick_one -n $sha1 - then - git rev-parse --verify HEAD >"$amend" - die_failed_squash $sha1 "Could not apply $sha1... $rest" - fi - do_with_author git commit --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \ - ${gpg_sign_opt:+"$gpg_sign_opt"} || - die_failed_squash $sha1 "$rest" + do_with_author do_pick --amend -F "$GIT_DIR"/SQUASH_MSG -e $sha1 \ + || die_failed_squash $sha1 "Could not apply $sha1... $rest" fi rm -f "$squash_msg" "$fixup_msg" ;;
Signature · 1 line
2.0.0
RFC PATCH v2 15/19 rebase -i: Explicitly distinguish replay commands and exec tasks
In reply to Fabian RuchThere are two kinds of to-do list commands available. One kind replays a commit (pick, reword, edit, squash and fixup that is) and the other executes a shell command (exec). We will call the first kind replay commands.
The two kinds of tasks are scheduled using different line formats. Replay commands expect a commit hash argument following the command name and exec concatenates all arguments to assemble a command line.
Adhere to the distinction of formats by not trying to parse the sha1 field unless we are dealing with a replay command. Move the replay command handling code to a new function do_replay which assumes the first argument to be a commit hash and make no more such assumptions in do_next.
--- git-rebase--interactive.sh | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-)
git-rebase--interactive.sh +28 -14
@@ -585,13 +585,12 @@ do_pick () { fi } -do_next () { - rm -f "$msg" "$author_script" "$amend" || exit - read -r command sha1 rest < "$todo" +do_replay () { + command=$1 + sha1=$2 + rest=$3 + case "$command" in - "$comment_char"*|''|noop) - mark_action_done - ;; pick|p) comment_for_reflog pick
@@ -656,6 +655,28 @@ do_next () { esac record_in_rewritten $sha1 ;; + *) + read -r command <"$todo" + warn "Unknown command: $command" + fixtodo="Please fix this using 'git rebase --edit-todo'." + if git rev-parse --verify -q "$sha1" >/dev/null + then + die_with_patch $sha1 "$fixtodo" + else + die "$fixtodo" + fi + ;; + esac +} + +do_next () { + rm -f "$msg" "$author_script" "$amend" || exit + read -r command sha1 rest <"$todo" + + case "$command" in + "$comment_char"*|''|noop) + mark_action_done + ;; x|"exec") read -r command rest < "$todo" mark_action_done
@@ -695,14 +716,7 @@ do_next () { fi ;; *) - warn "Unknown command: $command $sha1 $rest" - fixtodo="Please fix this using 'git rebase --edit-todo'." - if git rev-parse --verify -q "$sha1" >/dev/null - then - die_with_patch $sha1 "$fixtodo" - else - die "$fixtodo" - fi + do_replay $command $sha1 "$rest" ;; esac test -s "$todo" && return
Signature · 1 line
2.0.0
Re: [PATCH RFC v2 15/19] rebase -i: Explicitly distinguish replay commands and exec tasks
Quoted 9 lines · Fabian Ruch
There are two kinds of to-do list commands available. One kind replays a commit (
pick,reword,edit,squashandfixupthat is) and the other executes a shell command (exec). We will call the first kind replay commands.The two kinds of tasks are scheduled using different line formats. Replay commands expect a commit hash argument following the command name and exec concatenates all arguments to assemble a command line.
Quoted 5 lines
Adhere to the distinction of formats by not trying to parse the
sha1field unless we are dealing with a replay command. Move the replay command handling code to a new functiondo_replaywhich assumes the first argument to be a commit hash and make no more such assumptions indo_next.
In do_next(), we used read the first line of the insn sheet into three variables, assuming the common case of handling one of the replay commands, and (somewhat wastefully) re-read the same line into two variables when we realize that the command was "exec".
After you split do_replay() out of do_next() with this patch, we seem to do exactly the same thing.
What exactly is the problem this change wants to fix?
Puzzled...
Quoted 74 lines
Signed-off-by Fabian Ruch <[email protected]>--- git-rebase--interactive.sh | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-)git-rebase--interactive.sh +28
-14@@ -585,13 +585,12 @@ do_pick () { fi } -do_next () { - rm -f "$msg" "$author_script" "$amend" || exit - read -r command sha1 rest < "$todo" +do_replay () { + command=$1 + sha1=$2 + rest=$3 + case "$command" in - "$comment_char"*|''|noop) - mark_action_done - ;; pick|p) comment_for_reflog pick@@ -656,6 +655,28 @@ do_next () { esac record_in_rewritten $sha1 ;; + *) + read -r command <"$todo" + warn "Unknown command: $command" + fixtodo="Please fix this using 'git rebase --edit-todo'." + if git rev-parse --verify -q "$sha1" >/dev/null + then + die_with_patch $sha1 "$fixtodo" + else + die "$fixtodo" + fi + ;; + esac +} + +do_next () { + rm -f "$msg" "$author_script" "$amend" || exit + read -r command sha1 rest <"$todo" + + case "$command" in + "$comment_char"*|''|noop) + mark_action_done + ;; x|"exec") read -r command rest < "$todo" mark_action_done@@ -695,14 +716,7 @@ do_next () { fi ;; *) - warn "Unknown command: $command $sha1 $rest" - fixtodo="Please fix this using 'git rebase --edit-todo'." - if git rev-parse --verify -q "$sha1" >/dev/null - then - die_with_patch $sha1 "$fixtodo" - else - die "$fixtodo" - fi + do_replay $command $sha1 "$rest" ;; esac test -s "$todo" && return
RFC PATCH v2 16/19 rebase -i: Parse to-do list command line options
In reply to Fabian RuchRead in to-do list lines as
command args
instead of
command sha1 rest
so that to-do list command lines can specify additional arguments apart from the commit hash and the log message title, which become the non-options in args. Loop over args, put all options (an argument beginning with a dash) in opts, stop the loop on the first non-option and assign it to sha1. The loop does not know the options it parses so that options that take an argument themselves are not supported at the moment. Neither are options that contain spaces because the shell expansion of args in do_next interprets white space characters as argument separator, that is a command line like
pick --author "A U Thor" fa1afe1 Some change
is parsed as the pick command
pick --author
and the commit hash
"A
which obviously results in an unknown revision error. For the sake of completeness, in the example above the message title variable rest is assigned the string 'U Thor" fa1afe1 Some change' (without the single quotes).
Print an error message for unknown or unsupported command line options, which means an error for all specified options at the moment. Cleanly break the do_next loop by assigning the special value 'unknown' to the local variable command, which triggers the unknown command case in do_cmd.
The to-do list is also parsed when the commit hashes are translated between long and short format before and after the to-do list is edited. Apply the same procedure as in do_cmd with the exception that we only care about where the options stop and the commit hash begins. Do not reject any options when transforming the commit hashes.
--- git-rebase--interactive.sh | 49 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 8 deletions(-)
git-rebase--interactive.sh +41 -8
@@ -587,8 +587,26 @@ do_pick () { do_replay () { command=$1 - sha1=$2 - rest=$3 + shift + + opts= + while test $# -gt 0 + do + case "$1" in + -*) + warn "Unknown option: $1" + command=unknown + ;; + *) + break + ;; + esac + opts="$opts $(git rev-parse --sq-quote "$1")" + shift + done + sha1=$1 + shift + rest=$* case "$command" in pick|p)
@@ -671,7 +689,7 @@ do_replay () { do_next () { rm -f "$msg" "$author_script" "$amend" || exit - read -r command sha1 rest <"$todo" + read -r command args <"$todo" case "$command" in "$comment_char"*|''|noop)
@@ -716,7 +734,7 @@ do_next () { fi ;; *) - do_replay $command $sha1 "$rest" + do_replay $command $args ;; esac test -s "$todo" && return
@@ -796,19 +814,34 @@ skip_unnecessary_picks () { } transform_todo_ids () { - while read -r command rest + while read -r command args do case "$command" in "$comment_char"* | exec) # Be careful for oddball commands like 'exec' # that do not have a SHA-1 at the beginning of $rest. + newargs=\ $args ;; *) - sha1=$(git rev-parse --verify --quiet "$@" ${rest%% *}) && - rest="$sha1 ${rest#* }" + newargs= + sha1= + for arg in $args + do + case "$arg" in + -*) + newargs="$newargs $arg" + ;; + *) + test -z "$sha1" && + sha1=$(git rev-parse --verify --quiet "$@" $arg) && + arg=$sha1 + newargs="$newargs $arg" + ;; + esac + done ;; esac - printf '%s\n' "$command${rest:+ }$rest" + printf '%s\n' "$command$newargs" done <"$todo" >"$todo.new" && mv -f "$todo.new" "$todo" }
Signature · 1 line
2.0.0
RFC PATCH v2 17/19 rebase -i: Teach do_pick the option --reset-author
In reply to Fabian Ruchdo_pick is the git-cherry-pick wrapper in git-rebase--interactive that is used to implement many of the to-do list commands. Eventually, the complete do_pick interface will be exposed to the user in some form or another and those commands will become simple aliases for the do_pick options now used to implement them.
Add the git-commit option --reset-author to the options pool of do_pick. It rewrites the author date and name of the picked commit to match the committer date and name.
If --reset-author is passed to do_pick, set the rewrite flag and relay the option to the git-commit command line which creates the final commit. If --amend is not passed as well, the fresh authorship effect is achieved by the mere fact that we are creating a new commit.
--- git-rebase--interactive.sh | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-)
git-rebase--interactive.sh +22 -1
@@ -464,10 +464,18 @@ record_in_rewritten() { # Apply the changes introduced by the given commit to the current head. # -# do_pick [--amend] [--file <file>] [--edit] <commit> +# do_pick [--reset-author] [--amend] [--file <file>] [--edit] <commit> # # Wrapper around git-cherry-pick. # +# --reset-author +# Pretend the changes were made for the first time. Declare that the +# authorship of the resulting commit now belongs to the committer. +# This also renews the author timestamp. This creates a fresh +# commit. +# +# _This is not a git-cherry-pick option._ +# # --amend # After picking <commit>, replace the current head commit with a new # commit that also introduces the changes of <commit>.
@@ -501,6 +509,10 @@ do_pick () { while test $# -gt 0 do case "$1" in + --reset-author) + rewrite=y + rewrite_author=y + ;; --amend) if test "$(git rev-parse HEAD)" = "$squash_onto" || ! git rev-parse --verify HEAD then
@@ -562,12 +574,21 @@ do_pick () { pick_one ${rewrite:+-n} $1 || return 1 fi + if test -n "$rewrite_author" && test -z "$rewrite_amend" + then + # keep rewrite flag to create a new commit, rewrite + # without --reset-author though because it can only be + # used with -C, -c or --amend + rewrite_author= + fi + if test -n "$rewrite" then git commit --allow-empty --no-post-rewrite -n --no-edit \ ${rewrite_amend:+--amend} \ ${rewrite_edit:+--edit} \ ${rewrite_message:+--file "$rewrite_message"} \ + ${rewrite_author:+--reset-author} \ ${gpg_sign_opt:+"$gpg_sign_opt"} || return 3 fi
Signature · 1 line
2.0.0
RFC PATCH v2 18/19 rebase -i: Teach do_pick the option --signoff
In reply to Fabian Ruchdo_pick is the git-cherry-pick wrapper in git-rebase--interactive that is currently used to implement most of the to-do list commands and offers additional options that will eventually find their way onto to-do lists.
To extend the repertoire of available options, add the git-commit and git-cherry-pick option --signoff to the do_pick interface. It appends a Signed-off-by: line using the committer identity to the log message of the picked commit.
--- git-rebase--interactive.sh | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-)
git-rebase--interactive.sh +11 -1
@@ -464,10 +464,15 @@ record_in_rewritten() { # Apply the changes introduced by the given commit to the current head. # -# do_pick [--reset-author] [--amend] [--file <file>] [--edit] <commit> +# do_pick [--signoff] [--reset-author] [--amend] [--file <file>] +# [--edit] <commit> # # Wrapper around git-cherry-pick. # +# -s, --signoff +# Insert a Signed-off-by: line using the committer identity at the +# end of the commit log message. This creates a fresh commit. +# # --reset-author # Pretend the changes were made for the first time. Declare that the # authorship of the resulting commit now belongs to the committer.
@@ -509,6 +514,10 @@ do_pick () { while test $# -gt 0 do case "$1" in + -s|--signoff) + rewrite=y + rewrite_signoff=y + ;; --reset-author) rewrite=y rewrite_author=y
@@ -585,6 +594,7 @@ do_pick () { if test -n "$rewrite" then git commit --allow-empty --no-post-rewrite -n --no-edit \ + ${rewrite_signoff:+--signoff} \ ${rewrite_amend:+--amend} \ ${rewrite_edit:+--edit} \ ${rewrite_message:+--file "$rewrite_message"} \
Signature · 1 line
2.0.0
RFC PATCH v2 19/19 rebase -i: Enable options --signoff, --reset-author for pick, reword
In reply to Fabian Ruchpick and reword are atomic to-do list commands in the sense that they open a new task which is closed after the respective command is completed. squash and fixup are not atomic. They create a new task which is not completed until the last squash or fixup is processed.
Lift the general unknown option blockade for the pick and reword commands. If do_cmd comes across one of the options --signoff and --reset-author while parsing a to-do entry and the scheduled command is either pick or reword, relay the option to do_pick.
The do_pick options --gpg-sign and --file are not yet supported because do_cmd cannot handle option arguments and options with spaces at the moment. It is true that edit is one of the atomic commands but it displays hash information when the rebase is stopped and some options rewrite the picked commit which alters that information. squash and fixup still do not accept user options as the interplay of --reset-author and the author script are yet to be determined.
--- git-rebase--interactive.sh | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-)
git-rebase--interactive.sh +16 -3
@@ -624,6 +624,16 @@ do_replay () { while test $# -gt 0 do case "$1" in + --signoff|--reset-author) + case "$command" in + pick|reword) + ;; + *) + warn "Unsupported option: $1" + command=unknown + ;; + esac + ;; -*) warn "Unknown option: $1" command=unknown
@@ -644,21 +654,24 @@ do_replay () { comment_for_reflog pick mark_action_done - do_pick $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" + eval do_pick $opts $sha1 \ + || die_with_patch $sha1 "Could not apply $sha1... $rest" record_in_rewritten $sha1 ;; reword|r) comment_for_reflog reword mark_action_done - do_pick --edit $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" + eval do_pick --edit $opts $sha1 \ + || die_with_patch $sha1 "Could not apply $sha1... $rest" record_in_rewritten $sha1 ;; edit|e) comment_for_reflog edit mark_action_done - do_pick $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" + eval do_pick $opts $sha1 \ + || die_with_patch $sha1 "Could not apply $sha1... $rest" warn "Stopped at $sha1... $rest" exit_with_patch $sha1 0 ;;
Signature · 1 line
2.0.0
Re: [PATCH RFC v2 00/19] Enable options --signoff, --reset-author for pick, reword
In reply to Fabian RuchQuoted 12 lines · Fabian Ruch
Fabian Ruch (19):
rebase -i: Failed reword prints redundant error message rebase -i: reword complains about empty commit despite --keep-empty rebase -i: reword executes pre-commit hook on interim commit rebase -i: Teachdo_pickthe option --edit
rebase -i: Implement reword in terms ofdo_pick
rebase -i: Stop on root commits with empty log messages rebase -i: The replay of root commits is not shown with --verbose rebase -i: Root commits are replayed with an unnecessary option rebase -i: Commit only once when rewriting picks
rebase -i: Do not die indo_pick
rebase -i: Teachdo_pickthe option --amend
Quoted 8 lines
rebase -i: Teach
do_pickthe option --file
rebase -i: Prepare for squash in terms ofdo_pick--amend rebase -i: Implement squash in terms ofdo_pick
rebase -i: Explicitly distinguish replay commands and exec tasks rebase -i: Parse to-do list command line options
rebase -i: Teachdo_pickthe option --reset-author
rebase -i: Teachdo_pickthe option --signoff
rebase -i: Enable options --signoff, --reset-author for pick, reword
After "rebase -i:", some begin with lowercase and many begin with capital, which makes the short-log output look distracting.
Re: [PATCH RFC v2 00/19] Enable options --signoff, --reset-author for pick, reword
Quoted 22 lines · Junio C Hamano
Quoted 12 lines · Fabian Ruch
Fabian Ruch (19):
rebase -i: Failed reword prints redundant error message rebase -i: reword complains about empty commit despite --keep-empty rebase -i: reword executes pre-commit hook on interim commit rebase -i: Teachdo_pickthe option --edit
rebase -i: Implement reword in terms ofdo_pick
rebase -i: Stop on root commits with empty log messages rebase -i: The replay of root commits is not shown with --verbose rebase -i: Root commits are replayed with an unnecessary option rebase -i: Commit only once when rewriting picks
rebase -i: Do not die indo_pick
rebase -i: Teachdo_pickthe option --amendQuoted 8 lines
rebase -i: Teach
do_pickthe option --file
rebase -i: Prepare for squash in terms ofdo_pick--amend rebase -i: Implement squash in terms ofdo_pick
rebase -i: Explicitly distinguish replay commands and exec tasks rebase -i: Parse to-do list command line options
rebase -i: Teachdo_pickthe option --reset-author
rebase -i: Teachdo_pickthe option --signoff
rebase -i: Enable options --signoff, --reset-author for pick, reword
Quoted 2 lines
After "rebase -i:", some begin with lowercase and many begin with capital, which makes the short-log output look distracting.
The ones that begin with lower-case letters are the ones that begin with the command name "reword". All first lines are typed in lower case now.
Sorry for the noise.
Fabian
Re: [PATCH RFC v2 00/19] Enable options --signoff, --reset-author for pick, reword
Quoted 23 lines · Fabian Ruch
Quoted 22 lines · Junio C Hamano
Quoted 20 lines · Fabian Ruch
Fabian Ruch (19):
rebase -i: Failed reword prints redundant error message rebase -i: reword complains about empty commit despite --keep-empty rebase -i: reword executes pre-commit hook on interim commit rebase -i: Teachdo_pickthe option --edit
rebase -i: Implement reword in terms ofdo_pick
rebase -i: Stop on root commits with empty log messages rebase -i: The replay of root commits is not shown with --verbose rebase -i: Root commits are replayed with an unnecessary option rebase -i: Commit only once when rewriting picks
rebase -i: Do not die indo_pick
rebase -i: Teachdo_pickthe option --amend
rebase -i: Teachdo_pickthe option --file
rebase -i: Prepare for squash in terms ofdo_pick--amend rebase -i: Implement squash in terms ofdo_pick
rebase -i: Explicitly distinguish replay commands and exec tasks rebase -i: Parse to-do list command line options
rebase -i: Teachdo_pickthe option --reset-author
rebase -i: Teachdo_pickthe option --signoff
rebase -i: Enable options --signoff, --reset-author for pick, reword
Quoted 5 lines
Quoted 2 lines
After "rebase -i:", some begin with lowercase and many begin with capital, which makes the short-log output look distracting.
The ones that begin with lower-case letters are the ones that begin with the command name "reword". All first lines are typed in lower case now.
You could spell it 'reword' (with the quotes), which also disambiguates the command from the verb.
Signature · 2 lines
Thomas Rast [email protected]
cover Enable options --signoff, --reset-author for pick, reword
In reply to Fabian RuchHi,
this is a reroll of the patch series that enables rudimentary support of line options for git-rebase's to-do list commands and reimplements the well-known commands reword and squash in terms of a parameterised do_pick.
I tried to address all the issues raised by Junio in this reroll. Here is a short summary.
- Test that reword behaves like pick regarding the pre-commit hook and executes commit-msg for the new log message.
- Instead of disallowing empty log messages for root commits allow empty log messages for all unchanged commits.
- The patch "root commits are replayed with an unnecessary option" does not change the behaviour of git-rebase but it makes the subsequent patch easier to read.
The hidden agenda is to have one git-commit command line in do_pick that takes care of rewriting commits as they are being replayed, be they parentless or not. The advantage of having one rewrite command instead of one for root commits and one for non-root commits is that changes to the rewrite mechanism entail less code changes. As more rewriting mechanisms are being added to do_pick, it becomes important that they compose well with each other to avoid having a series of amending commits. The -C option conflicts with other ways of substituting the log message, like files and strings, which is why it seems not to compose so well.
- The patch "explicitly distinguish replay commands and exec tasks" does not change behaviour either but is again a separated refactoring patch.
The distinction of the exec command and the replay commands, which all share the same line format command args sha1 rest, allows us to employ the positional parameters feature of the shell without introducing another indentation level in do_pick.
Thanks for your time,
Fabian
Fabian Ruch (19):
rebase -i: failed reword prints redundant error message rebase -i: allow rewording an empty commit without complaints rebase -i: reword executes pre-commit hook on interim commit rebase -i: teach do_pick the option --edit
rebase -i: implement reword in terms of do_pick
rebase -i: allow replaying commits with empty log messages rebase -i: log the replay of root commits
rebase -i: root commits are replayed with an unnecessary option rebase -i: commit only once when rewriting picks
rebase -i: do not die in do_pick
rebase -i: teach do_pick the option --amend
rebase -i: teach do_pick the option --file
rebase -i: prepare for squash in terms of do_pick --amend rebase -i: implement squash in terms of do_pick
rebase -i: explicitly distinguish replay commands and exec tasks rebase -i: parse to-do list command line options
rebase -i: teach do_pick the option --reset-author
rebase -i: teach do_pick the option --signoff
rebase -i: enable options --signoff, --reset-author for pick, reword
git-rebase--interactive.sh | 284 ++++++++++++++++++++++++++++++++++-------- t/t3404-rebase-interactive.sh | 70 +++++++++++ t/t3412-rebase-root.sh | 19 +++ t/test-lib-functions.sh | 6 +- 4 files changed, 325 insertions(+), 54 deletions(-)
Signature · 1 line
2.0.1
PATCH 1/19 rebase -i: failed reword prints redundant error message
The to-do list command reword replays a commit like pick but lets the user also edit the commit's log message. If the edited log message is empty or is found ill-formatted by one of the commit hooks, git-rebase--interactive prints three error messages to the console.
1. The git-commit output, which contains all the output from hook scripts.
2. A rebase diagnosis saying at which task on the to-do list it got stuck.
3. Generic presumptions about what could have triggered the error.
The third message contains redundant information and does not add any enlightenment either, which makes the output unnecessarily longish and different from the other command's output. For instance, this is what the output looks like if the log message is empty (contains duplicate Signed-off-by lines).
(1.) Aborting commit due to empty commit message. (Duplicate Signed-off-by lines.) (2.) Could not amend commit after successfully picking fa1afe1... Some change (3.) This is most likely due to an empty commit message, or the pre-commit hook
failed. If the pre-commit hook failed, you may need to resolve the issue before you are able to reword the commit.
Discard the third message.
It is true that a failed hook script might not output any diagnosis but then the generic message is not of much help either. Since this lack of information affects the built-in git commands for commit, merge and cherry-pick first, the solution would be to keep track of the failed hooks in their output so that the user knows which of her hooks require improvement.
--- git-rebase--interactive.sh | 3 --- 1 file changed, 3 deletions(-)
git-rebase--interactive.sh +0 -3
@@ -506,9 +506,6 @@ do_next () { do_pick $sha1 "$rest" git commit --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} || { warn "Could not amend commit after successfully picking $sha1... $rest" - warn "This is most likely due to an empty commit message, or the pre-commit hook" - warn "failed. If the pre-commit hook failed, you may need to resolve the issue before" - warn "you are able to reword the commit." exit_with_patch $sha1 1 } record_in_rewritten $sha1
Signature · 1 line
2.0.1
PATCH 2/19 rebase -i: allow rewording an empty commit without complaints
In reply to Fabian RuchThe to-do list command reword replays a commit like pick but lets the user also edit the commit's log message. If --keep-empty is passed as option to git-rebase--interactive, empty commits ought to be replayed without complaints. However, if the users chooses to reword an empty commit by changing the respective to-do list entry from
pick fa1afe1 Empty commit
to
reword fa1afe1 Empty commit
, then git-rebase--interactive suddenly fails to replay the empty commit. This is especially counterintuitive because reword is thought of as a pick that alters the log message in some way but nothing more and the unchanged to-do list entry would not fail.
Handle reword by cherry-picking the named commit and editing the log message using
git commit --allow-empty --amend
instead of
git commit --amend.
Add test.
--- git-rebase--interactive.sh | 2 +- t/t3404-rebase-interactive.sh | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-)
git-rebase--interactive.sh +1 -1
@@ -504,7 +504,7 @@ do_next () { mark_action_done do_pick $sha1 "$rest" - git commit --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} || { + git commit --allow-empty --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} || { warn "Could not amend commit after successfully picking $sha1... $rest" exit_with_patch $sha1 1 }
t/t3404-rebase-interactive.sh +8 -0
@@ -75,6 +75,14 @@ test_expect_success 'rebase --keep-empty' ' test_line_count = 6 actual ' +test_expect_success 'rebase --keep-empty' ' + git checkout emptybranch && + set_fake_editor && + FAKE_LINES="1 reword 2" git rebase --keep-empty -i HEAD~2 && + git log --oneline >actual && + test_line_count = 6 actual +' + test_expect_success 'rebase -i with the exec command' ' git checkout master && (
Signature · 1 line
2.0.1
PATCH 3/19 rebase -i: reword executes pre-commit hook on interim commit
In reply to Fabian RuchThe to-do list command reword replays a commit like pick but lets the user also edit the commit's log message. This happens in two steps. Firstly, the named commit is cherry-picked. Secondly, the commit created in the first step is amended using an unchanged index to edit the log message. The pre-commit hook is meant to verify the changes introduced by a commit (for instance, catching inserted trailing white space). Since the committed tree is not changed between the two steps and we do not verify rebased patches, do not execute the pre-commit hook in the second step.
Specify the git-commit option --no-verify to disable the pre-commit hook when editing the log message. Because --no-verify also skips the commit-msg hook, execute the hook from within git-rebase--interactive after the commit is created. Fortunately, the commit message is still available in $GIT_DIR/COMMIT_EDITMSG after git-commit terminates. Caveat: In case the commit-msg hook finds the new log message ill-formatted, the user is only notified of the failed commit-msg hook but the log message is used for the commit anyway. git-commit ought to offer more fine-grained control over which hooks are executed.
Teach test_commit the --no-verify option and add test.
--- git-rebase--interactive.sh | 17 +++++++++++++---- t/t3404-rebase-interactive.sh | 38 ++++++++++++++++++++++++++++++++++++++ t/test-lib-functions.sh | 6 +++++- 3 files changed, 56 insertions(+), 5 deletions(-)
git-rebase--interactive.sh +13 -4
@@ -504,10 +504,19 @@ do_next () { mark_action_done do_pick $sha1 "$rest" - git commit --allow-empty --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} || { - warn "Could not amend commit after successfully picking $sha1... $rest" - exit_with_patch $sha1 1 - } + # TODO: Work around the fact that git-commit lets us + # disable either both the pre-commit and the commit-msg + # hook or none. Disable the pre-commit hook because the + # tree is left unchanged but run the commit-msg hook + # from here because the log message is altered. + git commit --allow-empty --amend --no-post-rewrite -n ${gpg_sign_opt:+"$gpg_sign_opt"} && + if test -x "$GIT_DIR"/hooks/commit-msg + then + "$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG + fi || { + warn "Could not amend commit after successfully picking $sha1... $rest" + exit_with_patch $sha1 1 + } record_in_rewritten $sha1 ;; edit|e)
t/t3404-rebase-interactive.sh +38 -0
@@ -577,6 +577,44 @@ test_expect_success 'rebase a commit violating pre-commit' ' ' +test_expect_success 'reword a commit violating pre-commit' ' + test_when_finished rm -r .git/hooks && + mkdir -p .git/hooks && + PRE_COMMIT=.git/hooks/pre-commit && + cat >"$PRE_COMMIT" <<EOF +#!/bin/sh +echo running pre-commit: exit 1 +exit 1 +EOF + chmod +x "$PRE_COMMIT" && + test_must_fail test_commit pre-commit-violated && + test_commit --no-verify pre-commit-violated && + test_when_finished reset_rebase && + set_fake_editor && + FAKE_LINES="pick 1" git rebase -i HEAD^ && + FAKE_LINES="pick 1" git rebase -i --no-ff HEAD^ && + FAKE_LINES="reword 1" git rebase -i HEAD^ +' + +test_expect_success 'reword a commit violating commit-msg' ' + test_when_finished rm -r .git/hooks && + mkdir -p .git/hooks && + COMMIT_MSG=.git/hooks/commit-msg && + cat >"$COMMIT_MSG" <<EOF +#!/bin/sh +echo running commit-msg: exit 1 +exit 1 +EOF + chmod +x "$COMMIT_MSG" && + test_must_fail test_commit commit-msg-violated && + test_commit --no-verify commit-msg-violated && + test_when_finished reset_rebase && + set_fake_editor && + FAKE_LINES="pick 1" git rebase -i HEAD^ && + FAKE_LINES="pick 1" git rebase -i --no-ff HEAD^ && + test_must_fail env FAKE_LINES="reword 1" git rebase -i HEAD^ +' + test_expect_success 'rebase with a file named HEAD in worktree' ' rm -fr .git/hooks &&
t/test-lib-functions.sh +5 -1
@@ -155,6 +155,7 @@ test_pause () { test_commit () { notick= && signoff= && + noverify= && while test $# != 0 do case "$1" in
@@ -164,6 +165,9 @@ test_commit () { --signoff) signoff="$1" ;; + --no-verify) + noverify="$1" + ;; *) break ;;
@@ -177,7 +181,7 @@ test_commit () { then test_tick fi && - git commit $signoff -m "$1" && + git commit $signoff $noverify -m "$1" && git tag "${4:-$1}" }
Signature · 1 line
2.0.1
Re: [PATCH v1 03/19] rebase -i: reword executes pre-commit hook on interim commit
Quoted 10 lines · Fabian Ruch
Specify the git-commit option
--no-verifyto disable the pre-commit hook when editing the log message. Because--no-verifyalso skips the commit-msg hook, execute the hook from within git-rebase--interactive after the commit is created. Fortunately, the commit message is still available in$GIT_DIR/COMMIT_EDITMSGafter git-commit terminates. Caveat: In case the commit-msg hook finds the new log message ill-formatted, the user is only notified of the failed commit-msg hook but the log message is used for the commit anyway. git-commit ought to offer more fine-grained control over which hooks are executed.
Thanks for a nice explanation of the tradeoff. Have you looked at adding an option to git-commit? We already have --no-post-rewrite. I think you would just need:
builtin/commit.c +4 -1
@@ -102,6 +102,7 @@ static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;static int no_post_rewrite, allow_empty_message;static char *untracked_files_arg, *force_date, *ignore_submodule_arg;static char *sign_commit; +static int no_pre_commit;/* * The default commit message cleanup mode will remove the lines
@@ -661,7 +662,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix,/* This checks and barfs if author is badly specified */ determine_author_info (author_ident); -if (!no_verify &&run_commit_hook (use_editor, index_file,"pre-commit" ,NULL )) +if (!no_verify && !no_pre_commit && +run_commit_hook (use_editor, index_file,"pre-commit" ,NULL ))return 0 ;if (squash_message) {
@@ -1604,6 +1606,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) N_("terminate entries with NUL")),OPT_BOOL (0 ,"amend" , &amend,N_ ("amend previous commit" )),OPT_BOOL (0 ,"no-post-rewrite" , &no_post_rewrite,N_ ("bypass post-rewrite hook" )), +OPT_BOOL (0 ,"no-pre-commit" , &no_pre_commit,N_ ("bypass pre-commit hook" )), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, N_("mode"), N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, /* end commit contents options */
though I would also not be opposed to some more uniform hook selection mechanism (e.g., "--no-verify=pre-commit" or something).
-Peff
Re: [PATCH v1 03/19] rebase -i: reword executes pre-commit hook on interim commit
Hi,
Quoted 48 lines · Jeff King
Quoted 10 lines · Fabian Ruch
Specify the git-commit option
--no-verifyto disable the pre-commit hook when editing the log message. Because--no-verifyalso skips the commit-msg hook, execute the hook from within git-rebase--interactive after the commit is created. Fortunately, the commit message is still available in$GIT_DIR/COMMIT_EDITMSGafter git-commit terminates. Caveat: In case the commit-msg hook finds the new log message ill-formatted, the user is only notified of the failed commit-msg hook but the log message is used for the commit anyway. git-commit ought to offer more fine-grained control over which hooks are executed.Thanks for a nice explanation of the tradeoff. Have you looked at adding an option to git-commit? We already have --no-post-rewrite. I think you would just need:
builtin/commit.c +4
-1@@ -102,6 +102,7 @@ static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;static int no_post_rewrite, allow_empty_message;static char *untracked_files_arg, *force_date, *ignore_submodule_arg;static char *sign_commit; +static int no_pre_commit;/* * The default commit message cleanup mode will remove the lines @@ -661,7 +662,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix,/* This checks and barfs if author is badly specified */ determine_author_info (author_ident); -if (!no_verify &&run_commit_hook (use_editor, index_file,"pre-commit" ,NULL )) +if (!no_verify && !no_pre_commit && +run_commit_hook (use_editor, index_file,"pre-commit" ,NULL ))return 0 ;if (squash_message) {@@ -1604,6 +1606,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) N_("terminate entries with NUL")),OPT_BOOL (0 ,"amend" , &amend,N_ ("amend previous commit" )),OPT_BOOL (0 ,"no-post-rewrite" , &no_post_rewrite,N_ ("bypass post-rewrite hook" )), +OPT_BOOL (0 ,"no-pre-commit" , &no_pre_commit,N_ ("bypass pre-commit hook" )), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, N_("mode"), N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, /* end commit contents options */
Quoted 2 lines
though I would also not be opposed to some more uniform hook selection mechanism (e.g., "--no-verify=pre-commit" or something).
While the --no-verify= mechanism doesn't add a new option to the git-commit interface but lets one refine the --no-verify option, users might find it weird to have an argument to name disabled hooks but then not be able to disable all hooks that way.
To have that uniform hook selection without duplicating code, we might want to have something like --bypass-hook= right away. This would cover --no-post-rewrite as well and add support for disabling prepare-commit-msg and post-commit, whose execution cannot be controlled via the git-commit interface at the moment.
Since the hook selection wouldn't have to change, the options parsing code seems to be simpler (--bypass-hook= would have to support several occurrences with different arguments which could be implemented as an OPT_CALLBACK?) and git-commit decided to have --no- options for hook selection so far, I would lean towards your patch from above.
I know you didn't say anything otherwise, I just wanted to expand a little. You find an amended version of your patch below that documents --no-verify as a synonym for --no-pre-commit and --no-commit-msg, and adds tests.
Quoted 66 lines
builtin/commit.c +22
-4@@ -98,12 +98,27 @@ static char *edit_message, *use_message;static char *fixup_message, *squash_message;static int all, also, interactive, patch_interactive, only, amend, signoff;static int edit_flag =-1 ;/* unspecified */ -static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; +static int quiet, verbose, allow_empty, dry_run, renew_authorship;static int no_post_rewrite, allow_empty_message;static char *untracked_files_arg, *force_date, *ignore_submodule_arg;static char *sign_commit;/* +* The verify variable is interpreted as a bitmap of enabled commit +* verification hooks according to the legend below. +* +* By default, the pre-commit and commit-msg hooks are enabled. This +* is represented by both the PRE_COMMIT and COMMIT_MSG bits being +* set. +* +* The bitmap is changed through the command line options +* --no-verify, --no-pre-commit and --no-commit-msg. +*/ +#define PRE_COMMIT (1<<0) +#define COMMIT_MSG (1<<1) +static int verify =PRE_COMMIT |COMMIT_MSG ; + +/* * The default commit message cleanup mode will remove the lines * beginning with # (shell comments) and leading and trailing * whitespaces (empty lines or containing only whitespaces) @@ -667,7 +682,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix,/* This checks and barfs if author is badly specified */ determine_author_info (author_ident); -if (!no_verify &&run_commit_hook (use_editor, index_file,"pre-commit" ,NULL )) +if (verify &PRE_COMMIT && +run_commit_hook (use_editor, index_file,"pre-commit" ,NULL ))return 0 ;if (squash_message) {@@ -968,7 +984,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, } } -if (!no_verify && +if (verify &COMMIT_MSG &&run_commit_hook (use_editor, index_file,"commit-msg" ,git_path (commit_editmsg),NULL )) {return 0 ; }@@ -1597,7 +1613,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix)OPT_BOOL (0 ,"interactive" , &interactive,N_ ("interactively add files" )),OPT_BOOL ('p' ,"patch" , &patch_interactive,N_ ("interactively add changes" )),OPT_BOOL ('o' ,"only" , &only,N_ ("commit only specified files" )), -OPT_BOOL ('n' ,"no-verify" , &no_verify,N_ ("bypass pre-commit hook" )), +OPT_NEGBIT ('n' ,"no-verify" , &verify, +N_ ("synonym for --no-pre-commit --no-commit-msg" ), +PRE_COMMIT |COMMIT_MSG ),OPT_BOOL (0 ,"dry-run" , &dry_run,N_ ("show what would be committed" )),OPT_SET_INT (0 ,"short" , &status_format,N_ ("show status concisely" ), STATUS_FORMAT_SHORT),@@ -1610,6 +1628,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix)OPT_BOOL ('z' ,"null" , &s.null_termination, N_("terminate entries with NUL")),
builtin/commit.c int cmd_commit(int argc, const char **argv, const char *prefix) · line 1639
Quoted 10 lines
builtin/commit.c +6
-0OPT_BOOL (0 ,"amend" , &amend,N_ ("amend previous commit" )), +OPT_NEGBIT (0 ,"no-pre-commit" , &verify, +N_ ("bypass pre-commit hook" ), +PRE_COMMIT ), +OPT_NEGBIT (0 ,"no-commit-msg" , &verify, +N_ ("bypass commit-msg hook" ), +COMMIT_MSG ),OPT_BOOL (0 ,"no-post-rewrite" , &no_post_rewrite,N_ ("bypass post-rewrite hook" )), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, N_("mode"), N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, /* end commit contents options */
Since all of the hook options are motivated by internal usage from git-rebase, perhaps they should be configured as PARSE_OPT_HIDDEN. Any thoughts on this?
Fabian
Re: [PATCH v1 03/19] rebase -i: reword executes pre-commit hook on interim commit
Quoted 7 lines · Fabian Ruch
Quoted 2 lines
though I would also not be opposed to some more uniform hook selection mechanism (e.g., "--no-verify=pre-commit" or something).
While the --no-verify= mechanism doesn't add a new option to the git-commit interface but lets one refine the --no-verify option, users might find it weird to have an argument to name disabled hooks but then not be able to disable all hooks that way.
Right, I think that is only worth doing if we add it uniformly for all hooks. I wonder if it should be a "git" option, not one to specific commands. Like:
git --disable-hook=pre-commit commit ...
and then the run_hook code can just respect the disabled list.
I think a name like "--disable-hook" or your "--bypass-hook" is better than "--no-verify" here, too. Not all hooks are about verifying (I also think "disable" is better than "bypass" for that reason, too).
Quoted 5 lines
Since the hook selection wouldn't have to change, the options parsing code seems to be simpler (--bypass-hook= would have to support several occurrences with different arguments which could be implemented as an
OPT_CALLBACK?) and git-commit decided to have --no- options for hook selection so far, I would lean towards your patch from above.
You'd probably implement --disable-hook with OPT_STRING_LIST (or just do it by hand if it's the git.c option parser). And then the --no-post-rewrite arguments remain for historical compatibility, and can be implemented as synonyms for "--disable-hook=post-rewrite", etc.
I think people have also asked for the ability to override hooks, too, though I do not remember the exact details. Instead of --disable-hook, we could have an option for setting specific hooks (and setting them to nothing to disable would just be one possibility).
This is getting bigger in scope, though. I was trying not to derail you too much from your GSoC project, but see if we could just fix this one hacky corner easily.
Quoted 3 lines
Since all of the hook options are motivated by internal usage from git-rebase, perhaps they should be configured as
PARSE_OPT_HIDDEN. Any thoughts on this?
I could go either way. Just because they are motivated by git-rebase does not mean other callers might not find them useful (after all, git commands are often meant to be scripted). As long as we promise to support them in future versions as we do with normal options, I do not think there is any problem in advertising them.
That being said, "git commit -h" is already getting pretty long. It might be worth cutting some seldom-used options from that list just to make it more palatable to normal users.
-Peff
PATCH 4/19 rebase -i: teach do_pick the option --edit
In reply to Fabian Ruchdo_pick is the git-cherry-pick wrapper in git-rebase--interactive that is used to implement the to-do list command pick. To cater for the different pick behaviours (like reword), do_pick accepts several options not only from the git-cherry-pick but also the git-commit interface. Add the common option --edit to let the user edit the log message of the named commit.
Loop over $@ to parse the do_pick arguments. Assign the local variable edit if one of the options is --edit so that the remainder of do_pick can easily check whether the client code asked to edit the commit message. If one of the options is unknown, mention it on the console and die. Break the loop on the first non-option and do some sanity checking to ensure that there exactly two non-options, which are interpreted by the remainder as <commit> and <title> like before.
do_pick ought to act as a wrapper around cherry-pick. Unfortunately, it cannot just forward --edit to the cherry-pick command line. The assembled command line is executed within a command substitution for controlling the verbosity of rebase--interactive. Passing --edit would either hang the terminal or clutter the substituted command output with control sequences. Execute the reword code from do_next instead if the option --edit is specified.
--- git-rebase--interactive.sh | 52 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+)
git-rebase--interactive.sh +52 -0
@@ -461,7 +461,42 @@ record_in_rewritten() { esac } +# Apply the changes introduced by the given commit to the current head. +# +# do_pick [--edit] <commit> <title> +# +# Wrapper around git-cherry-pick. +# +# -e, --edit +# After picking <commit>, open an editor and let the user edit the +# commit message. The editor contents becomes the commit message of +# the new head. This creates a fresh commit. +# +# <commit> +# The commit to cherry-pick. +# +# <title> +# The commit message title of <commit>. Used for information +# purposes only. do_pick () { + edit= + while test $# -gt 0 + do + case "$1" in + -e|--edit) + edit=y + ;; + -*) + die "do_pick: unrecognized option -- $1" + ;; + *) + break + ;; + esac + shift + done + test $# -ne 2 && die "do_pick: wrong number of arguments" + if test "$(git rev-parse HEAD)" = "$squash_onto" then # Set the correct commit message and author info on the
@@ -483,6 +518,23 @@ do_pick () { pick_one $1 || die_with_patch $1 "Could not apply $1... $2" fi + + if test -n "$edit" + then + # TODO: Work around the fact that git-commit lets us + # disable either both the pre-commit and the commit-msg + # hook or none. Disable the pre-commit hook because the + # tree is left unchanged but run the commit-msg hook + # from here because the log message is altered. + git commit --allow-empty --amend --no-post-rewrite -n ${gpg_sign_opt:+"$gpg_sign_opt"} && + if test -x "$GIT_DIR"/hooks/commit-msg + then + "$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG + fi || { + warn "Could not amend commit after successfully picking $1... $2" + exit_with_patch $1 1 + } + fi } do_next () {
Signature · 1 line
2.0.1
PATCH 5/19 rebase -i: implement reword in terms of do_pick
In reply to Fabian RuchThe to-do list command reword replays a commit like pick but lets the user also edit the commit's log message. If one thinks of pick entries as scheduled cherry-pick command lines, then reword becomes an alias for the command line cherry-pick --edit. The porcelain rebase--interactive defines a function do_pick for processing the pick entries on to-do lists. Reimplement reword in terms of do_pick --edit.
If the user picks a commit using the to-do list line
reword fa1afe1 Some change
execute the command do_pick --edit fa1afe1 "Some change" which carries out exactly the same steps as the case arm for reword in do_next so far.
--- git-rebase--interactive.sh | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-)
git-rebase--interactive.sh +1 -14
@@ -555,20 +555,7 @@ do_next () { comment_for_reflog reword mark_action_done - do_pick $sha1 "$rest" - # TODO: Work around the fact that git-commit lets us - # disable either both the pre-commit and the commit-msg - # hook or none. Disable the pre-commit hook because the - # tree is left unchanged but run the commit-msg hook - # from here because the log message is altered. - git commit --allow-empty --amend --no-post-rewrite -n ${gpg_sign_opt:+"$gpg_sign_opt"} && - if test -x "$GIT_DIR"/hooks/commit-msg - then - "$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG - fi || { - warn "Could not amend commit after successfully picking $sha1... $rest" - exit_with_patch $sha1 1 - } + do_pick --edit $sha1 "$rest" record_in_rewritten $sha1 ;; edit|e)
Signature · 1 line
2.0.1
PATCH 6/19 rebase -i: allow replaying commits with empty log messages
In reply to Fabian Ruchgit-rebase--interactive handles empty log messages inconsistently between enabled and disabled fast-forwards. By default, commits with empty log messages are rebased successfully like in non-interactive mode. In contrast, the --no-ff option aborts the replay of such commits.
In line with not verifying rebased commits and behaving like git-rebase for pick lines, use the --allow-empty-message option to replay commits. Root commits are replayed by recreating them in do_pick using git-commit and commits with parents are replayed using git-cherry-pick in pick_one. Apply the option, understood by both git-commit and git-cherry-pick, at the respective sites.
In case of reword and squash/fixup continue to abort the rebase if the resulting commit would have no commit message. The rationale behind this default is that patches and their log messages should be verified at least once. For unchanged commits this is assumed to have happened according to the author's standards when she created the commits the first time. While the empty log message can always be kept in place by editing and resuming the aborted rebase, a debatable alternative could be to teach git-rebase--interactive the option --allow-empty-message for disabling complaints about empty log messages in changed commits. The to-do list command edit is handled just like pick for this matter, because git-rebase--interactive replays the named commit without changes before the rebase is interrupted and the user can make her changes to the replayed commit.
Add tests.
--- git-rebase--interactive.sh | 4 ++-- t/t3404-rebase-interactive.sh | 24 ++++++++++++++++++++++++ t/t3412-rebase-root.sh | 19 +++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-)
git-rebase--interactive.sh +2 -2
@@ -249,7 +249,7 @@ pick_one () { test -d "$rewritten" && pick_one_preserving_merges "$@" && return - output eval git cherry-pick \ + output eval git cherry-pick --allow-empty-message \ ${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} \ "$strategy_args" $empty_args $ff "$@" }
@@ -363,7 +363,7 @@ pick_one_preserving_merges () { echo "$sha1 $(git rev-parse HEAD^0)" >> "$rewritten_list" ;; *) - output eval git cherry-pick \ + output eval git cherry-pick --allow-empty-message \ ${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} \ "$strategy_args" "$@" || die_with_patch $sha1 "Could not pick $sha1"
t/t3404-rebase-interactive.sh +24 -0
@@ -1085,4 +1085,28 @@ test_expect_success 'short SHA-1 collide' ' ) ' +test_expect_success 'rebase commits with empty commit log messages' ' + git checkout -b no-msg-commit && + test_commit no-msg-commit-1 && + git commit --amend --allow-empty-message -m " " && + test_commit no-msg-commit-2 && + git commit --amend --allow-empty-message -m " " && + EDITOR=true git rebase -i HEAD^ && + EDITOR=true git rebase -i --no-ff HEAD^ +' + +test_expect_success 'reword commit with empty commit log message' ' + git checkout no-msg-commit && + test_when_finished reset_rebase && + set_fake_editor && + test_must_fail env FAKE_LINES="reword 1" git rebase -i HEAD^ +' + +test_expect_success 'squash commits with empty commit log messages' ' + git checkout no-msg-commit && + test_when_finished reset_rebase && + set_fake_editor && + test_must_fail env FAKE_LINES="1 squash 2" git rebase -i HEAD^^ +' + test_done
t/t3412-rebase-root.sh +19 -0
@@ -278,4 +278,23 @@ test_expect_success 'rebase -i -p --root with conflict (second part)' ' test_cmp expect-conflict-p out ' +test_expect_success 'rebase --root with empty root log message' ' + git checkout --orphan no-msg-root-commit && + test_commit no-msg-root-commit && + git commit --amend -m " " --allow-empty-message && + git rebase --root && + test_path_is_file no-msg-root-commit.t && + git rebase --root --no-ff && + test_path_is_file no-msg-root-commit.t +' + +test_expect_success 'rebase --root with empty child log message' ' + test_commit no-msg-child-commit && + git commit --amend -m " " --allow-empty-message && + git rebase --root && + test_path_is_file no-msg-child-commit.t && + git rebase --root --no-ff && + test_path_is_file no-msg-child-commit.t +' + test_done
Signature · 1 line
2.0.1
PATCH 7/19 rebase -i: log the replay of root commits
In reply to Fabian RuchThe command line used to recreate root commits specifies the option -q which suppresses the commit summary message. However, git-rebase--interactive tends to tell the user about the commits it creates in the final history, if she wishes (cf. command line option --verbose). The code parts handling non-root commits and squash commits all output commit summary messages. Do not make the replay of root commits an exception. Remove the option to make the report of the rebased history complete.
It is OK that the commit summary is still suppressed when git-commit is used to initialize the authorship of the sentinel commit because this additional commit is an implementation detail hidden from the final history. The removed -q option was probably introduced as a copy-and-paste error stemming from that part of the root commit handling code.
--- git-rebase--interactive.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
git-rebase--interactive.sh +1 -1
@@ -511,7 +511,7 @@ do_pick () { --no-post-rewrite -n -q -C $1 && pick_one -n $1 && git commit --allow-empty --allow-empty-message \ - --amend --no-post-rewrite -n -q -C $1 \ + --amend --no-post-rewrite -n -C $1 \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_with_patch $1 "Could not apply $1... $2" else
Signature · 1 line
2.0.1
Re: [PATCH v1 07/19] rebase -i: log the replay of root commits
Quoted 9 lines · Fabian Ruch
The command line used to recreate root commits specifies the option
-qwhich suppresses the commit summary message. However, git-rebase--interactive tends to tell the user about the commits it creates in the final history, if she wishes (cf. command line option--verbose). The code parts handling non-root commits and squash commits all output commit summary messages. Do not make the replay of root commits an exception. Remove the option to make the report of the rebased history complete.
Quoted 6 lines
It is OK that the commit summary is still suppressed when git-commit is used to initialize the authorship of the sentinel commit because this additional commit is an implementation detail hidden from the final history. The removed
-qoption was probably introduced as a copy-and-paste error stemming from that part of the root commit handling code.
I'm confused. This implies that we should be seeing summaries for other commits, but not root commits, and this patch is bring them into harmony. But if I have a repo like this:
git init -q repo &&
cd repo &&
for i in one two; do
echo $i >file &&
git add file &&
git commit -q -m $idone
then using stock git gives me this:
$ GIT_EDITOR=true git rebase -i --root 2>&1 | perl -pe 's/\r/\\r\n/g' Rebasing (1/2)\r Rebasing (2/2)\r Successfully rebased and updated refs/heads/master.
but with your patch, I get:
$ GIT_EDITOR=true git.compile rebase -i --root 2>&1 | perl -pe 's/\r/\\r\n/g' Rebasing (1/2)\r [detached HEAD 60834b3] one Date: Fri Aug 1 20:00:05 2014 -0400 1 file changed, 1 insertion(+) create mode 100644 file Rebasing (2/2)\r Successfully rebased and updated refs/heads/master.
Am I misunderstanding the purpose of the patch?
-Peff
Re: [PATCH v1 07/19] rebase -i: log the replay of root commits
Hi,
Quoted 47 lines · Jeff King
Quoted 9 lines · Fabian Ruch
The command line used to recreate root commits specifies the option
-qwhich suppresses the commit summary message. However, git-rebase--interactive tends to tell the user about the commits it creates in the final history, if she wishes (cf. command line option--verbose). The code parts handling non-root commits and squash commits all output commit summary messages. Do not make the replay of root commits an exception. Remove the option to make the report of the rebased history complete.Quoted 6 lines
It is OK that the commit summary is still suppressed when git-commit is used to initialize the authorship of the sentinel commit because this additional commit is an implementation detail hidden from the final history. The removed
-qoption was probably introduced as a copy-and-paste error stemming from that part of the root commit handling code.I'm confused. This implies that we should be seeing summaries for other commits, but not root commits, and this patch is bring them into harmony. But if I have a repo like this:
git init -q repo &&
cd repo &&
for i in one two; doecho $i >file && git add file && git commit -q -m $idone
then using stock git gives me this:
$ GIT_EDITOR=true git rebase -i --root 2>&1 | perl -pe 's/\r/\\r\n/g' Rebasing (1/2)\r Rebasing (2/2)\r Successfully rebased and updated refs/heads/master.but with your patch, I get:
$ GIT_EDITOR=true git.compile rebase -i --root 2>&1 | perl -pe 's/\r/\\r\n/g' Rebasing (1/2)\r [detached HEAD 60834b3] one Date: Fri Aug 1 20:00:05 2014 -0400 1 file changed, 1 insertion(+) create mode 100644 file Rebasing (2/2)\r Successfully rebased and updated refs/heads/master.
Quoted 1 line
Am I misunderstanding the purpose of the patch?
Thanks for laying out the differences in the user visible output. With stock git we are seeing summaries for other commits, but not root commits, _with the --verbose option_. It's the fault of my patch to show the summary even in non-verbose mode. This is now fixed by wrapping the relevant command in 'output', a shell function defined in git-rebase.sh as follows:
Quoted 4 lines
output=$("$@" 2>&1 )
status=$?
test $status != 0 && printf "%s\n" "$output"
return $status
The problem that git-rebase--interactive has is that the redirection of stdin to a variable (or a file) does not work reliably with commands that invoke the log message editor, that is 'reword' and 'squash' produce their output both in verbose and non-verbose mode. I wouldn't know how to fix this but
1) invoking $GIT_EDITOR from git-rebase--interactive.sh, but to make this right there should be a built-in command for shell scripts and an interface for git-commit that prepare the editor contents like git-commit does now, or
2) forcing $GIT_EDITOR and git-commit to print on distinct file descriptors, which would involve temporarily wrapping the $GIT_EDITOR call in a shell script that redirects stdin to some other file descriptor similar to what t/test-lib.sh does, or
Quoted 9 lines
cat >"$state_dir"/editor.sh <<EOF
#!/bin/sh
$(git var GIT_EDITOR) \$* >&3
EOF
chmod +x "$state_dir"/editor.sh
(
export GIT_EDITOR="$state_dir"/editor.sh
"$@" 3>&1 >"$state_dir"/output 2>&1
)3) passing the --quiet option in non-verbose mode and omitting it in verbose mode, which would cover the '$status != 0' above for if a command fails, it should indicate its error status despite being asked to be silent.
Options 2) and 3) seem attainable within this patch series and 3) sounds like the cleanest option but I'm uncertain if I'm missing something here. The only command line that is wrapped in 'output' and that doesn't support a --quiet option seems to be a 'warn' line which could simply be skipped in non-verbose mode. (Johannes Schindelin is cc'd as the original author of git-rebase--interactive.sh and 'output' in particular).
Fabian
Re: [PATCH v1 07/19] rebase -i: log the replay of root commits
Quoted 6 lines · Fabian Ruch
Thanks for laying out the differences in the user visible output. With stock git we are seeing summaries for other commits, but not root commits,
_withthe --verboseoption_. It's the fault of my patch to show the summary even in non-verbose mode. This is now fixed by wrapping the relevant command in 'output', a shell function defined ingit-rebase.shas follows:
Ah, OK. That makes a lot more sense, then.
Quoted 16 lines
Quoted 4 lines
output=$("$@" 2>&1 )
status=$?
test $status != 0 && printf "%s\n" "$output"
return $statusThe problem that git-rebase--interactive has is that the redirection of stdin to a variable (or a file) does not work reliably with commands that invoke the log message editor, that is 'reword' and 'squash' produce their output both in verbose and non-verbose mode. I wouldn't know how to fix this but
1) invoking $GIT_EDITOR from
git-rebase--interactive.sh, but to make this right there should be a built-in command for shell scripts and an interface for git-commit that prepare the editor contents like git-commit does now, or
Quoted 4 lines
2) forcing $GIT_EDITOR and git-commit to print on distinct file descriptors, which would involve temporarily wrapping the $GIT_EDITOR call in a shell script that redirects stdin to some other file descriptor similar to what
t/test-lib.shdoes, or
Hmm. In the test scripts, we send stdout and stderr for sub-commands to fds 3 and 4 respectively. And then we either point those at /dev/null or to >&1 and >&2, depending on whether $verbose mode was specified.
I don't think that will work here, though. You literally have one "git commit" command to run, and you want its stderr/stdout to go somewhere different than the $GIT_EDITOR it will invoke. Your (2) makes some sense to me. Something like:
GIT_EDITOR="$(shellquote "$(git var GIT_EDITOR)") >&3 2>&4" \
git commit ... 3>&1 4>&2 >wherever 2>&1Or we could just point it at /dev/tty, though I guess that may open another can of worms (systems without /dev/tty, what happens when you do not have a terminal, etc).
Quoted 4 lines
3) passing the --quiet option in non-verbose mode and omitting it in verbose mode, which would cover the '$status != 0' above for if a command fails, it should indicate its error status despite being asked to be silent.
Yeah, that is probably the cleanest option if it works. I would just worry that it is not as complete. It works for "git commit", but are there are other commands wrapped in the verbose output that would want the same treatment (that might not know about --quiet)? Your paragraph below says it would not be that big a deal, so as long as we don't plan to add anything in the future that could not handle the requirement, that may be enough.
-Peff
PATCH 8/19 rebase -i: root commits are replayed with an unnecessary option
In reply to Fabian RuchThe command line used to recreate root commits specifies the effectless option -C. It makes git-commit reuse commit message and authorship of the named commit. However, the commit being amended here, which is the sentinel commit, already carries the authorship and log message of the commit being replayed. Remove the option.
Since -C (in contrast to -c) does not invoke the editor and the --amend option invokes it by default, disable editor invocation again by specifying --no-edit.
--- git-rebase--interactive.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
git-rebase--interactive.sh +1 -1
@@ -511,7 +511,7 @@ do_pick () { --no-post-rewrite -n -q -C $1 && pick_one -n $1 && git commit --allow-empty --allow-empty-message \ - --amend --no-post-rewrite -n -C $1 \ + --amend --no-post-rewrite -n --no-edit \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_with_patch $1 "Could not apply $1... $2" else
Signature · 1 line
2.0.1
Re: [PATCH v1 08/19] rebase -i: root commits are replayed with an unnecessary option
Quoted 9 lines · Fabian Ruch
The command line used to recreate root commits specifies the effectless option
-C. It makes git-commit reuse commit message and authorship of the named commit. However, the commit being amended here, which is the sentinel commit, already carries the authorship and log message of the commit being replayed. Remove the option.Since
-C(in contrast to-c) does not invoke the editor and the--amendoption invokes it by default, disable editor invocation again by specifying--no-edit.
I found this description a little backwards. The "-C" does have an effect, as you noticed in the second paragraph.
I think the reasoning is more like:
The command line used to recreate root commits uses "-C" to suppress the commit editor. This is unnecessarily confusing, though, because that suppression is a secondary effect of the option. The main purpose of "-C" is to pull the metadata from another commit, but here we know that this is a noop, since we are amending a commit just created from the same data.
At the time, commit did not yet know "--no-edit", and this was a reasonable way to get the desired behavior. We can switch it to use "--no-edit" to make the intended effect more obvious.
-Peff
Re: [PATCH v1 08/19] rebase -i: root commits are replayed with an unnecessary option
Hi Jeff,
Quoted 23 lines · Jeff King
Quoted 9 lines · Fabian Ruch
The command line used to recreate root commits specifies the effectless option
-C. It makes git-commit reuse commit message and authorship of the named commit. However, the commit being amended here, which is the sentinel commit, already carries the authorship and log message of the commit being replayed. Remove the option.Since
-C(in contrast to-c) does not invoke the editor and the--amendoption invokes it by default, disable editor invocation again by specifying--no-edit.I found this description a little backwards. The "-C" does have an effect, as you noticed in the second paragraph.
I think the reasoning is more like:
The command line used to recreate root commits uses "-C" to suppress the commit editor. This is unnecessarily confusing, though, because that suppression is a secondary effect of the option. The main purpose of "-C" is to pull the metadata from another commit, but here we know that this is a noop, since we are amending a commit just created from the same data.
Quoted 3 lines
At the time, commit did not yet know "--no-edit", and this was a reasonable way to get the desired behavior. We can switch it to use "--no-edit" to make the intended effect more obvious.
Thanks again, I shamelessly copied your formulation but squeezed in an "undocumented" because --no-edit had just been implemented (commit ca1ba2010), though was then still missing from the git-commit manpage.
Fabian
PATCH 9/19 rebase -i: commit only once when rewriting picks
In reply to Fabian RuchThe options passed to do_pick determine whether the picked commit will be rewritten or not. If the commit gets rewritten, because the user requested to edit the commit message for instance, let pick_one merely apply the changes introduced by the commit and do not commit the resulting tree yet. If the commit is replayed as is, leave it to pick_one to recreate the commit (possibly by fast-forwarding the head). This makes it easier to combine git-commit options like --edit and --amend in do_pick because git-cherry-pick does not support --amend.
In the case of --edit, do not exit_with_patch but assign rewrite to pick the changes with -n. If the pick conflicts, no commit is created which we would have to amend when continuing the rebase. To complete the pick after the conflicts are resolved the user just resumes with git rebase --continue.
git-commit lets the user edit the commit log message by default. We do not want that for the rewriting git-commit command line because the default behaviour of git-rebase is exactly the opposite. Pass --no-edit when rewriting a picked commit. An explicit --edit passed to do_pick (for instance, when reword is executed) enables the editor launch again. Similarly, pass --allow-empty-message unless the log message is edited.
If rebase--interactive is used to rebase a complete branch onto some head, rebase creates a sentinel commit that requires special treatment by do_pick. Do not finalize the pick here either because its commit message can be altered as for any other pick. Since the orphaned root commit gets a temporary parent, it is always rewritten. Safely use the rewrite infrastructure of do_pick to create the final commit.
--- git-rebase--interactive.sh | 58 ++++++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 22 deletions(-)
git-rebase--interactive.sh +36 -22
@@ -63,7 +63,8 @@ msgnum="$state_dir"/msgnum author_script="$state_dir"/author-script # When an "edit" rebase command is being processed, the SHA1 of the -# commit to be edited is recorded in this file. When "git rebase +# commit to be edited is recorded in this file. The same happens when +# rewriting a commit fails, for instance "reword". When "git rebase # --continue" is executed, if there are any staged changes then they # will be amended to the HEAD commit, but only provided the HEAD # commit is still the commit to be edited. When any other rebase
@@ -479,12 +480,17 @@ record_in_rewritten() { # The commit message title of <commit>. Used for information # purposes only. do_pick () { - edit= + allow_empty_message=y + rewrite= + rewrite_amend= + rewrite_edit= while test $# -gt 0 do case "$1" in -e|--edit) - edit=y + rewrite=y + rewrite_edit=y + allow_empty_message= ;; -*) die "do_pick: unrecognized option -- $1"
@@ -499,6 +505,10 @@ do_pick () { if test "$(git rev-parse HEAD)" = "$squash_onto" then + rewrite=y + rewrite_amend=y + git rev-parse --verify HEAD >"$amend" + # Set the correct commit message and author info on the # sentinel root before cherry-picking the original changes # without committing (-n). Finally, update the sentinel again
@@ -509,31 +519,35 @@ do_pick () { # rebase --continue. git commit --allow-empty --allow-empty-message --amend \ --no-post-rewrite -n -q -C $1 && - pick_one -n $1 && - git commit --allow-empty --allow-empty-message \ - --amend --no-post-rewrite -n --no-edit \ - ${gpg_sign_opt:+"$gpg_sign_opt"} || + pick_one -n $1 || die_with_patch $1 "Could not apply $1... $2" else - pick_one $1 || + pick_one ${rewrite:+-n} $1 || die_with_patch $1 "Could not apply $1... $2" fi - if test -n "$edit" + if test -n "$rewrite" then - # TODO: Work around the fact that git-commit lets us - # disable either both the pre-commit and the commit-msg - # hook or none. Disable the pre-commit hook because the - # tree is left unchanged but run the commit-msg hook - # from here because the log message is altered. - git commit --allow-empty --amend --no-post-rewrite -n ${gpg_sign_opt:+"$gpg_sign_opt"} && - if test -x "$GIT_DIR"/hooks/commit-msg - then - "$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG - fi || { - warn "Could not amend commit after successfully picking $1... $2" - exit_with_patch $1 1 - } + git commit --allow-empty --no-post-rewrite -n --no-edit \ + ${allow_empty_message:+--allow-empty-message} \ + ${rewrite_amend:+--amend} \ + ${rewrite_edit:+--edit} \ + ${gpg_sign_opt:+"$gpg_sign_opt"} || + die_with_patch $1 "Could not rewrite commit after successfully picking $1... $2" + fi + + # TODO: Work around the fact that git-commit lets us + # disable either both the pre-commit and the commit-msg + # hook or none. Disable the pre-commit hook because the + # tree is left unchanged but run the commit-msg hook + # from here because the log message is altered. + if test -n "$rewrite_edit" + then + if test -x "$GIT_DIR"/hooks/commit-msg + then + "$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG + fi || + die_with_patch $1 "Could not rewrite commit after successfully picking $1... $2" fi }
Signature · 1 line
2.0.1
Re: [PATCH v1 09/19] rebase -i: commit only once when rewriting picks
Quoted 10 lines · Fabian Ruch
The options passed to
do_pickdetermine whether the picked commit will be rewritten or not. If the commit gets rewritten, because the user requested to edit the commit message for instance, letpick_onemerely apply the changes introduced by the commit and do not commit the resulting tree yet. If the commit is replayed as is, leave it topick_oneto recreate the commit (possibly by fast-forwarding the head). This makes it easier to combine git-commit options like--editand--amendindo_pickbecause git-cherry-pick does not support--amend.
Quoted 5 lines
In the case of
--edit, do notexit_with_patchbut assignrewriteto pick the changes with-n. If the pick conflicts, no commit is created which we would have to amend when continuing the rebase. To complete the pick after the conflicts are resolved the user just resumes withgit rebase --continue.
Hmm. So does this mean the user will actually see a different state during such a conflict?
E.g., if I have instructions like:
pick A
squash B
squash C
and there is a conflict picking C, then what state do I see? Right now I see a commit with the A+B squash prepared. But your description sounds to me like we would avoid the squash for "B", and the user would see a different state.
However, I couldn't trigger that behavior in a few experiments. Am I misunderstanding, or is there some case where the user-visible state will be different?
-Peff
Re: [PATCH v1 09/19] rebase -i: commit only once when rewriting picks
Hi Jeff,
Quoted 22 lines · Jeff King
Quoted 10 lines · Fabian Ruch
The options passed to
do_pickdetermine whether the picked commit will be rewritten or not. If the commit gets rewritten, because the user requested to edit the commit message for instance, letpick_onemerely apply the changes introduced by the commit and do not commit the resulting tree yet. If the commit is replayed as is, leave it topick_oneto recreate the commit (possibly by fast-forwarding the head). This makes it easier to combine git-commit options like--editand--amendindo_pickbecause git-cherry-pick does not support--amend.Quoted 5 lines
In the case of
--edit, do notexit_with_patchbut assignrewriteto pick the changes with-n. If the pick conflicts, no commit is created which we would have to amend when continuing the rebase. To complete the pick after the conflicts are resolved the user just resumes withgit rebase --continue.Hmm. So does this mean the user will actually see a different state during such a conflict?
E.g., if I have instructions like:
Quoted 8 lines
pick A
squash B
squash Cand there is a conflict picking C, then what state do I see? Right now I see a commit with the A+B squash prepared. But your description sounds to me like we would avoid the squash for "B", and the user would see a different state.
The squash state will not be different. squash sequences are still taken care of one line after the other: committing A, committing A+B, committing A+B+C. If there is a conflict picking C, HEAD will point to A+B and the index will record the conflicting changes.
Quoted 3 lines
However, I couldn't trigger that behavior in a few experiments. Am I misunderstanding, or is there some case where the user-visible state will be different?
The user-visible state will be different for rewords. For instance, let's consider
pick A
reword B.
The verbose log used to show two commits for B (with ff disabled), one after picking and one after editing. Now the log will show a single commit in connection with 'reword B' which might be less confusing.
Thanks for raising your eyebrows. I noticed now that the last paragraph is plainly wrong. The described amend situation did not arise "if the pick conflicted" but "if the edited commit did not verify". There will be no "after the conflicts are resolved" but the user can either commit manually and circumvent log message verification if she knows what she's doing, or provide a corrected log message in the editor launched by 'git rebase --continue'. The _incomplete_ 'git commit --amend' tip which used to be displayed after a failed verification hook could become unnecessary and this would possibly spare us including correct GPG sign options for instance.
However, this patch is mostly motivated by the unification of how commits are rewritten. Before, rewords and squashes went about this differently, now both fail with an uncommitted index if there are conflicts or the log message is ill-formatted.
The log message must be corrected and the following bug, which was noticed after PATCH v2, must be fixed.
cat >.git/hooks/commit-msg <<-EOF
#!/bin/sh
exit 1
EOF
chmod +x .git/hooks/commit-msg
test_must_fail env FAKE_LINES="reword 1" git rebase -i test_must_fail git rebase --continue
# the last command succeeds because --continue does not verify
Fabian
PATCH 10/19 rebase -i: do not die in do_pick
In reply to Fabian RuchSince do_pick might be executed in a sub-shell (a modified author environment for instance), calling die in do_pick has no effect but exiting the sub-shell with a failure exit status. The git-rebase--interactive script is not terminated. Moreover, if do_pick is called while a squash or fixup is in effect, die_with_patch will discard $squash_msg as commit message. Lastly, after a die in do_pick do_next has no chance to reschedule tasks that failed before changes could be applied.
Indicate an error in do_pick using return statements and properly kill the script at the call sites. Although possible in principle, the issued error messages are no more indicating whether do_pick failed while applying or while committing the changes. This reduces code complexity at the call site and does not matter from a user's point of view because a glance at the index reveals whether there are conflicts or not and in-depth troubleshooting is still possible using the --verbose option.
Remove the commit message title argument from do_pick's interface, which has become unused.
--- git-rebase--interactive.sh | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-)
git-rebase--interactive.sh +20 -17
@@ -464,7 +464,7 @@ record_in_rewritten() { # Apply the changes introduced by the given commit to the current head. # -# do_pick [--edit] <commit> <title> +# do_pick [--edit] <commit> # # Wrapper around git-cherry-pick. #
@@ -476,9 +476,11 @@ record_in_rewritten() { # <commit> # The commit to cherry-pick. # -# <title> -# The commit message title of <commit>. Used for information -# purposes only. +# The return value is 1 if applying the changes resulted in a conflict +# and 2 if the specified arguments were incorrect. If the changes could +# be applied successfully but creating the commit failed, a value +# greater than 2 is returned. No commit is created in either case and +# the index is left with the (conflicting) changes in place. do_pick () { allow_empty_message=y rewrite=
@@ -493,7 +495,8 @@ do_pick () { allow_empty_message= ;; -*) - die "do_pick: unrecognized option -- $1" + warn "do_pick: unrecognized option -- $1" + return 2 ;; *) break
@@ -501,7 +504,11 @@ do_pick () { esac shift done - test $# -ne 2 && die "do_pick: wrong number of arguments" + if test $# -ne 1 + then + warn "do_pick: wrong number of arguments" + return 2 + fi if test "$(git rev-parse HEAD)" = "$squash_onto" then
@@ -519,11 +526,9 @@ do_pick () { # rebase --continue. git commit --allow-empty --allow-empty-message --amend \ --no-post-rewrite -n -q -C $1 && - pick_one -n $1 || - die_with_patch $1 "Could not apply $1... $2" + pick_one -n $1 || return 1 else - pick_one ${rewrite:+-n} $1 || - die_with_patch $1 "Could not apply $1... $2" + pick_one ${rewrite:+-n} $1 || return 1 fi if test -n "$rewrite"
@@ -532,8 +537,7 @@ do_pick () { ${allow_empty_message:+--allow-empty-message} \ ${rewrite_amend:+--amend} \ ${rewrite_edit:+--edit} \ - ${gpg_sign_opt:+"$gpg_sign_opt"} || - die_with_patch $1 "Could not rewrite commit after successfully picking $1... $2" + ${gpg_sign_opt:+"$gpg_sign_opt"} || return 3 fi # TODO: Work around the fact that git-commit lets us
@@ -546,8 +550,7 @@ do_pick () { if test -x "$GIT_DIR"/hooks/commit-msg then "$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG - fi || - die_with_patch $1 "Could not rewrite commit after successfully picking $1... $2" + fi || return 3 fi }
@@ -562,21 +565,21 @@ do_next () { comment_for_reflog pick mark_action_done - do_pick $sha1 "$rest" + do_pick $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" record_in_rewritten $sha1 ;; reword|r) comment_for_reflog reword mark_action_done - do_pick --edit $sha1 "$rest" + do_pick --edit $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" record_in_rewritten $sha1 ;; edit|e) comment_for_reflog edit mark_action_done - do_pick $sha1 "$rest" + do_pick $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" warn "Stopped at $sha1... $rest" exit_with_patch $sha1 0 ;;
Signature · 1 line
2.0.1
PATCH 11/19 rebase -i: teach do_pick the option --amend
In reply to Fabian Ruchdo_pick is the git-cherry-pick wrapper in git-rebase--interactive that is used to implement the to-do list commands pick, reword and edit. To cater for the different pick behaviours (like squash), do_pick accepts several options not only from the git-cherry-pick but also the git-commit interface.
Add the option --amend from the git-commit interface to the options pool of do_pick. It creates a new commit for the changes introduced by the picked commit and the previous one. The previous commit is then replaced with the new commit. If no other options are specified, the log message of the previous commit is used.
Be careful when --amend is used to pick a root commit because HEAD might point to the sentinel commit but there is still nothing to amend. Be sure to initialize amend so that commits are squashed even when git-rebase--interactive is interrupted for resolving conflicts. It is not a mistake to do the initialization regardless of any conflicts because amend is always cleared before the next to-do item is processed.
--- git-rebase--interactive.sh | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-)
git-rebase--interactive.sh +17 -1
@@ -464,10 +464,16 @@ record_in_rewritten() { # Apply the changes introduced by the given commit to the current head. # -# do_pick [--edit] <commit> +# do_pick [--amend] [--edit] <commit> # # Wrapper around git-cherry-pick. # +# --amend +# After picking <commit>, replace the current head commit with a new +# commit that also introduces the changes of <commit>. +# +# _This is not a git-cherry-pick option._ +# # -e, --edit # After picking <commit>, open an editor and let the user edit the # commit message. The editor contents becomes the commit message of
@@ -489,6 +495,16 @@ do_pick () { while test $# -gt 0 do case "$1" in + --amend) + if test "$(git rev-parse HEAD)" = "$squash_onto" || ! git rev-parse --verify HEAD + then + warn "do_pick: nothing to amend" + return 2 + fi + rewrite=y + rewrite_amend=y + git rev-parse --verify HEAD >"$amend" + ;; -e|--edit) rewrite=y rewrite_edit=y
Signature · 1 line
2.0.1
PATCH 12/19 rebase -i: teach do_pick the option --file
In reply to Fabian Ruchdo_pick is the git-cherry-pick wrapper in git-rebase--interactive that is used to implement the to-do list command pick, reword and edit. To cater for the different pick behaviours (like squash), do_pick accepts several options not only from the git-cherry-pick but also the git-commit interface.
Add the option --file from the git-commit interface to the options pool of do_pick. It expects an argument itself which is interpreted as a file path and takes the commit message from the given file. If --file is passed to do_pick, assign the given file path to the local variable rewrite_message and relay the option
--file "$rewrite_message"
to the git-commit command line which creates the commit.
--- git-rebase--interactive.sh | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-)
git-rebase--interactive.sh +19 -1
@@ -464,7 +464,7 @@ record_in_rewritten() { # Apply the changes introduced by the given commit to the current head. # -# do_pick [--amend] [--edit] <commit> +# do_pick [--amend] [--file <file>] [--edit] <commit> # # Wrapper around git-cherry-pick. #
@@ -474,6 +474,12 @@ record_in_rewritten() { # # _This is not a git-cherry-pick option._ # +# -F <file>, --file <file> +# Take the commit message from the given file. This creates a fresh +# commit. +# +# _This is not a git-cherry-pick option._ +# # -e, --edit # After picking <commit>, open an editor and let the user edit the # commit message. The editor contents becomes the commit message of
@@ -492,6 +498,7 @@ do_pick () { rewrite= rewrite_amend= rewrite_edit= + rewrite_message= while test $# -gt 0 do case "$1" in
@@ -505,6 +512,16 @@ do_pick () { rewrite_amend=y git rev-parse --verify HEAD >"$amend" ;; + -F|--file) + if test $# -eq 0 + then + warn "do_pick: option --file specified but no <file> given" + return 2 + fi + rewrite=y + rewrite_message=$2 + shift + ;; -e|--edit) rewrite=y rewrite_edit=y
@@ -553,6 +570,7 @@ do_pick () { ${allow_empty_message:+--allow-empty-message} \ ${rewrite_amend:+--amend} \ ${rewrite_edit:+--edit} \ + ${rewrite_message:+--file "$rewrite_message"} \ ${gpg_sign_opt:+"$gpg_sign_opt"} || return 3 fi
Signature · 1 line
2.0.1
PATCH 13/19 rebase -i: prepare for squash in terms of do_pick --amend
In reply to Fabian RuchRewrite squash and fixup handling in do_next using the sequence
pick_one
commit
in order to test the correctness of a single do_squash or parameterised do_pick and make the subsequent patch reimplementing squash in terms of such a single function more readable.
Do not call rm -f "$GIT_DIR"/MERGE_MSG since it has no effect on the state after git-rebase--interactive terminates. The option -F makes git-commit ignore MERGE_MSG for the log message. If git-commit succeeds, MERGE_MSG is removed, and if it fails, MERGE_MSG is overwritten by the error sequence die_failed_squash. In summary, removing MERGE_MSG neither influences the squash commit message nor the file state after git-commit returns.
Moreover, pick_one ignores $GIT_DIR/SQUASH_MSG and does not touch $squash_msg so that it is correct to execute pick_one immediately before git-commit.
Might be squashed into the subsequent commit.
--- git-rebase--interactive.sh | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-)
git-rebase--interactive.sh +15 -6
@@ -636,15 +636,15 @@ do_next () { author_script_content=$(get_author_ident_from_commit HEAD) echo "$author_script_content" > "$author_script" eval "$author_script_content" - if ! pick_one -n $sha1 - then - git rev-parse --verify HEAD >"$amend" - die_failed_squash $sha1 "$rest" - fi case "$(peek_next_command)" in squash|s|fixup|f) # This is an intermediate commit; its message will only be # used in case of trouble. So use the long version: + if ! pick_one -n $sha1 + then + git rev-parse --verify HEAD >"$amend" + die_failed_squash $sha1 "Could not apply $sha1... $rest" + fi do_with_author output git commit --amend --no-verify -F "$squash_msg" \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest"
@@ -653,12 +653,21 @@ do_next () { # This is the final command of this squash/fixup group if test -f "$fixup_msg" then + if ! pick_one -n $sha1 + then + git rev-parse --verify HEAD >"$amend" + die_failed_squash $sha1 "Could not apply $sha1... $rest" + fi do_with_author git commit --amend --no-verify -F "$fixup_msg" \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest" else cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit - rm -f "$GIT_DIR"/MERGE_MSG + if ! pick_one -n $sha1 + then + git rev-parse --verify HEAD >"$amend" + die_failed_squash $sha1 "Could not apply $sha1... $rest" + fi do_with_author git commit --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest"
Signature · 1 line
2.0.1
PATCH 14/19 rebase -i: implement squash in terms of do_pick
In reply to Fabian RuchThe to-do list command squash and its close relative fixup replay the changes of a commit like pick but do not recreate the commit. Instead they replace the previous commit with a new commit that also introduces the changes of the squashed commit. This is roughly like cherry-picking without committing and using git-commit to amend the previous commit.
The to-do list
pick a Some changes
squash b Some more changes
gets translated into the sequence of git commands
git cherry-pick a
git cherry-pick -n b
git commit --amend
and if git-cherry-pick supported --amend this would look even more like the to-do list it is based on
git cherry-pick a
git cherry-pick --amend b.
Since do_pick takes care of pick entries and the above suggests squash as an alias for pick --amend, reimplement squash in terms of do_pick --amend. Introduce $squash_msg as the commit message via the --file option. When the last commit of a squash series is processed, the user is asked to review the log message. Pass --edit as additional option in this case. The only difference in the options passed to git-commit and do_pick is the omitted --no-verify. However, do_pick does not execute the verification hooks anyway because it solely replays commits and assumes that they have been verified before.
--- git-rebase--interactive.sh | 30 ++++++------------------------ 1 file changed, 6 insertions(+), 24 deletions(-)
git-rebase--interactive.sh +6 -24
@@ -640,37 +640,19 @@ do_next () { squash|s|fixup|f) # This is an intermediate commit; its message will only be # used in case of trouble. So use the long version: - if ! pick_one -n $sha1 - then - git rev-parse --verify HEAD >"$amend" - die_failed_squash $sha1 "Could not apply $sha1... $rest" - fi - do_with_author output git commit --amend --no-verify -F "$squash_msg" \ - ${gpg_sign_opt:+"$gpg_sign_opt"} || - die_failed_squash $sha1 "$rest" + do_with_author do_pick --amend -F "$squash_msg" $sha1 \ + || die_failed_squash $sha1 "Could not apply $sha1... $rest" ;; *) # This is the final command of this squash/fixup group if test -f "$fixup_msg" then - if ! pick_one -n $sha1 - then - git rev-parse --verify HEAD >"$amend" - die_failed_squash $sha1 "Could not apply $sha1... $rest" - fi - do_with_author git commit --amend --no-verify -F "$fixup_msg" \ - ${gpg_sign_opt:+"$gpg_sign_opt"} || - die_failed_squash $sha1 "$rest" + do_with_author do_pick --amend -F "$fixup_msg" $sha1 \ + || die_failed_squash $sha1 "Could not apply $sha1... $rest" else cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit - if ! pick_one -n $sha1 - then - git rev-parse --verify HEAD >"$amend" - die_failed_squash $sha1 "Could not apply $sha1... $rest" - fi - do_with_author git commit --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \ - ${gpg_sign_opt:+"$gpg_sign_opt"} || - die_failed_squash $sha1 "$rest" + do_with_author do_pick --amend -F "$GIT_DIR"/SQUASH_MSG -e $sha1 \ + || die_failed_squash $sha1 "Could not apply $sha1... $rest" fi rm -f "$squash_msg" "$fixup_msg" ;;
Signature · 1 line
2.0.1
PATCH 15/19 rebase -i: explicitly distinguish replay commands and exec tasks
In reply to Fabian RuchThere are two kinds of to-do list commands available. One kind replays a commit (pick, reword, edit, squash and fixup that is) and the other executes a shell command (exec). We will call the first kind replay commands.
The two kinds of tasks are scheduled using different line formats. Replay commands expect a commit hash argument following the command name and exec concatenates all arguments to assemble a command line.
Adhere to the distinction of formats by not trying to parse the sha1 field unless we are dealing with a replay command. Move the replay command handling code to a new function do_replay which assumes the first argument to be a commit hash and make no more such assumptions in do_next.
--- git-rebase--interactive.sh | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-)
git-rebase--interactive.sh +28 -14
@@ -588,13 +588,12 @@ do_pick () { fi } -do_next () { - rm -f "$msg" "$author_script" "$amend" || exit - read -r command sha1 rest < "$todo" +do_replay () { + command=$1 + sha1=$2 + rest=$3 + case "$command" in - "$comment_char"*|''|noop) - mark_action_done - ;; pick|p) comment_for_reflog pick
@@ -659,6 +658,28 @@ do_next () { esac record_in_rewritten $sha1 ;; + *) + read -r command <"$todo" + warn "Unknown command: $command" + fixtodo="Please fix this using 'git rebase --edit-todo'." + if git rev-parse --verify -q "$sha1" >/dev/null + then + die_with_patch $sha1 "$fixtodo" + else + die "$fixtodo" + fi + ;; + esac +} + +do_next () { + rm -f "$msg" "$author_script" "$amend" || exit + read -r command sha1 rest <"$todo" + + case "$command" in + "$comment_char"*|''|noop) + mark_action_done + ;; x|"exec") read -r command rest < "$todo" mark_action_done
@@ -698,14 +719,7 @@ do_next () { fi ;; *) - warn "Unknown command: $command $sha1 $rest" - fixtodo="Please fix this using 'git rebase --edit-todo'." - if git rev-parse --verify -q "$sha1" >/dev/null - then - die_with_patch $sha1 "$fixtodo" - else - die "$fixtodo" - fi + do_replay $command $sha1 "$rest" ;; esac test -s "$todo" && return
Signature · 1 line
2.0.1
PATCH 16/19 rebase -i: parse to-do list command line options
In reply to Fabian RuchRead in to-do list lines as
command args
instead of
command sha1 rest
so that to-do list command lines can specify additional arguments apart from the commit hash and the log message title, which become the non-options in args. Loop over args, put all options (an argument beginning with a dash) in opts, stop the loop on the first non-option and assign it to sha1. The loop does not know the options it parses so that options that take an argument themselves are not supported at the moment. Neither are options that contain spaces because the shell expansion of args in do_next interprets white space characters as argument separator, that is a command line like
pick --author "A U Thor" fa1afe1 Some change
is parsed as the pick command
pick --author
and the commit hash
"A
which obviously results in an unknown revision error. For the sake of completeness, in the example above the message title variable rest is assigned the string 'U Thor" fa1afe1 Some change' (without the single quotes).
Print an error message for unknown or unsupported command line options, which means an error for all specified options at the moment. Cleanly break the do_next loop by assigning the special value 'unknown' to the local variable command, which triggers the unknown command case in do_cmd.
The to-do list is also parsed when the commit hashes are translated between long and short format before and after the to-do list is edited. Apply the same procedure as in do_cmd with the exception that we only care about where the options stop and the commit hash begins. Do not reject any options when transforming the commit hashes.
--- git-rebase--interactive.sh | 49 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 8 deletions(-)
git-rebase--interactive.sh +41 -8
@@ -590,8 +590,26 @@ do_pick () { do_replay () { command=$1 - sha1=$2 - rest=$3 + shift + + opts= + while test $# -gt 0 + do + case "$1" in + -*) + warn "Unknown option: $1" + command=unknown + ;; + *) + break + ;; + esac + opts="$opts $(git rev-parse --sq-quote "$1")" + shift + done + sha1=$1 + shift + rest=$* case "$command" in pick|p)
@@ -674,7 +692,7 @@ do_replay () { do_next () { rm -f "$msg" "$author_script" "$amend" || exit - read -r command sha1 rest <"$todo" + read -r command args <"$todo" case "$command" in "$comment_char"*|''|noop)
@@ -719,7 +737,7 @@ do_next () { fi ;; *) - do_replay $command $sha1 "$rest" + do_replay $command $args ;; esac test -s "$todo" && return
@@ -799,19 +817,34 @@ skip_unnecessary_picks () { } transform_todo_ids () { - while read -r command rest + while read -r command args do case "$command" in "$comment_char"* | exec) # Be careful for oddball commands like 'exec' # that do not have a SHA-1 at the beginning of $rest. + newargs=\ $args ;; *) - sha1=$(git rev-parse --verify --quiet "$@" ${rest%% *}) && - rest="$sha1 ${rest#* }" + newargs= + sha1= + for arg in $args + do + case "$arg" in + -*) + newargs="$newargs $arg" + ;; + *) + test -z "$sha1" && + sha1=$(git rev-parse --verify --quiet "$@" $arg) && + arg=$sha1 + newargs="$newargs $arg" + ;; + esac + done ;; esac - printf '%s\n' "$command${rest:+ }$rest" + printf '%s\n' "$command$newargs" done <"$todo" >"$todo.new" && mv -f "$todo.new" "$todo" }
Signature · 1 line
2.0.1
PATCH 17/19 rebase -i: teach do_pick the option --reset-author
In reply to Fabian Ruchdo_pick is the git-cherry-pick wrapper in git-rebase--interactive that is used to implement many of the to-do list commands. Eventually, the complete do_pick interface will be exposed to the user in some form or another and those commands will become simple aliases for the do_pick options now used to implement them.
Add the git-commit option --reset-author to the options pool of do_pick. It rewrites the author date and name of the picked commit to match the committer date and name.
If --reset-author is passed to do_pick, set the rewrite flag and relay the option to the git-commit command line which creates the final commit. If --amend is not passed as well, the fresh authorship effect is achieved by the mere fact that we are creating a new commit.
--- git-rebase--interactive.sh | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-)
git-rebase--interactive.sh +22 -1
@@ -464,10 +464,18 @@ record_in_rewritten() { # Apply the changes introduced by the given commit to the current head. # -# do_pick [--amend] [--file <file>] [--edit] <commit> +# do_pick [--reset-author] [--amend] [--file <file>] [--edit] <commit> # # Wrapper around git-cherry-pick. # +# --reset-author +# Pretend the changes were made for the first time. Declare that the +# authorship of the resulting commit now belongs to the committer. +# This also renews the author timestamp. This creates a fresh +# commit. +# +# _This is not a git-cherry-pick option._ +# # --amend # After picking <commit>, replace the current head commit with a new # commit that also introduces the changes of <commit>.
@@ -502,6 +510,10 @@ do_pick () { while test $# -gt 0 do case "$1" in + --reset-author) + rewrite=y + rewrite_author=y + ;; --amend) if test "$(git rev-parse HEAD)" = "$squash_onto" || ! git rev-parse --verify HEAD then
@@ -564,6 +576,14 @@ do_pick () { pick_one ${rewrite:+-n} $1 || return 1 fi + if test -n "$rewrite_author" && test -z "$rewrite_amend" + then + # keep rewrite flag to create a new commit, rewrite + # without --reset-author though because it can only be + # used with -C, -c or --amend + rewrite_author= + fi + if test -n "$rewrite" then git commit --allow-empty --no-post-rewrite -n --no-edit \
@@ -571,6 +591,7 @@ do_pick () { ${rewrite_amend:+--amend} \ ${rewrite_edit:+--edit} \ ${rewrite_message:+--file "$rewrite_message"} \ + ${rewrite_author:+--reset-author} \ ${gpg_sign_opt:+"$gpg_sign_opt"} || return 3 fi
Signature · 1 line
2.0.1
PATCH 18/19 rebase -i: teach do_pick the option --signoff
In reply to Fabian Ruchdo_pick is the git-cherry-pick wrapper in git-rebase--interactive that is currently used to implement most of the to-do list commands and offers additional options that will eventually find their way onto to-do lists.
To extend the repertoire of available options, add the git-commit and git-cherry-pick option --signoff to the do_pick interface. It appends a Signed-off-by: line using the committer identity to the log message of the picked commit.
--- git-rebase--interactive.sh | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-)
git-rebase--interactive.sh +11 -1
@@ -464,10 +464,15 @@ record_in_rewritten() { # Apply the changes introduced by the given commit to the current head. # -# do_pick [--reset-author] [--amend] [--file <file>] [--edit] <commit> +# do_pick [--signoff] [--reset-author] [--amend] [--file <file>] +# [--edit] <commit> # # Wrapper around git-cherry-pick. # +# -s, --signoff +# Insert a Signed-off-by: line using the committer identity at the +# end of the commit log message. This creates a fresh commit. +# # --reset-author # Pretend the changes were made for the first time. Declare that the # authorship of the resulting commit now belongs to the committer.
@@ -510,6 +515,10 @@ do_pick () { while test $# -gt 0 do case "$1" in + -s|--signoff) + rewrite=y + rewrite_signoff=y + ;; --reset-author) rewrite=y rewrite_author=y
@@ -588,6 +597,7 @@ do_pick () { then git commit --allow-empty --no-post-rewrite -n --no-edit \ ${allow_empty_message:+--allow-empty-message} \ + ${rewrite_signoff:+--signoff} \ ${rewrite_amend:+--amend} \ ${rewrite_edit:+--edit} \ ${rewrite_message:+--file "$rewrite_message"} \
Signature · 1 line
2.0.1
PATCH 19/19 rebase -i: enable options --signoff, --reset-author for pick, reword
In reply to Fabian Ruchpick and reword are atomic to-do list commands in the sense that they open a new task which is closed after the respective command is completed. squash and fixup are not atomic. They create a new task which is not completed until the last squash or fixup is processed.
Lift the general unknown option blockade for the pick and reword commands. If do_cmd comes across one of the options --signoff and --reset-author while parsing a to-do entry and the scheduled command is either pick or reword, relay the option to do_pick.
The do_pick options --gpg-sign and --file are not yet supported because do_cmd cannot handle option arguments and options with spaces at the moment. It is true that edit is one of the atomic commands but it displays hash information when the rebase is stopped and some options rewrite the picked commit which alters that information. squash and fixup still do not accept user options as the interplay of --reset-author and the author script are yet to be determined.
--- git-rebase--interactive.sh | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-)
git-rebase--interactive.sh +16 -3
@@ -627,6 +627,16 @@ do_replay () { while test $# -gt 0 do case "$1" in + --signoff|--reset-author) + case "$command" in + pick|reword) + ;; + *) + warn "Unsupported option: $1" + command=unknown + ;; + esac + ;; -*) warn "Unknown option: $1" command=unknown
@@ -647,21 +657,24 @@ do_replay () { comment_for_reflog pick mark_action_done - do_pick $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" + eval do_pick $opts $sha1 \ + || die_with_patch $sha1 "Could not apply $sha1... $rest" record_in_rewritten $sha1 ;; reword|r) comment_for_reflog reword mark_action_done - do_pick --edit $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" + eval do_pick --edit $opts $sha1 \ + || die_with_patch $sha1 "Could not apply $sha1... $rest" record_in_rewritten $sha1 ;; edit|e) comment_for_reflog edit mark_action_done - do_pick $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" + eval do_pick $opts $sha1 \ + || die_with_patch $sha1 "Could not apply $sha1... $rest" warn "Stopped at $sha1... $rest" exit_with_patch $sha1 0 ;;
Signature · 1 line
2.0.1
Re: [PATCH v1 00/19] Enable options --signoff, --reset-author for pick, reword
In reply to Fabian RuchQuoted 4 lines · Fabian Ruch
this is a reroll of the patch series that enables rudimentary support of line options for git-rebase's to-do list commands and reimplements the well-known commands
rewordandsquashin terms of a parameteriseddo_pick.
I just finished reading over the whole series (which is my first real exposure to it). With the exception of the comments I already sent, it looks pretty reasonable to me.
Thanks for splitting it and writing good commit messages; that made it relatively easy to follow what was going on.
-Peff
Re: [PATCH v1 00/19] Enable options --signoff, --reset-author for pick, reword
Hi Peff,
Quoted 6 lines · Jeff King
Quoted 4 lines · Fabian Ruch
this is a reroll of the patch series that enables rudimentary support of line options for git-rebase's to-do list commands and reimplements the well-known commands
rewordandsquashin terms of a parameteriseddo_pick.
Quoted 6 lines
I just finished reading over the whole series (which is my first real exposure to it). With the exception of the comments I already sent, it looks pretty reasonable to me.
Thanks for splitting it and writing good commit messages; that made it relatively easy to follow what was going on.
Thanks a lot for taking the time.
Your review revealed more shortcomings of the patch series. Now that the replay of root commits is logged, the log is dumped on the console even in non-verbose mode. And, I must admit that the fact that --no-edit hasn't been a (documented) feature of git-commit for all time didn't struck me at all as a possible reason for using -C, which is a little embarrassing.
I will include more details in separate replies to your comments later.
Fabian
v2 cover Enable options --signoff, --reset-author for pick, reword
In reply to Fabian RuchHi List,
this is the second reroll of the patch series fixing corner-case bugs regarding empty commits, commits with no log message and root commits, providing a uniform implementation of the to-do list commands using the do_pick interface.
This reroll includes the following list of changes to PATCH v1 from last week, mostly induced by Jeff's comments. However, I didn't get around the most recent discussion from tonight yet.
- new tests: allow replaying commits with empty log messages
- coverage of all to-do list commands
- new patch: allow squashing empty commits without complaints
- do not complain about an empty squash commit unless it is the final one and --keep-empty is not specified on the command line
- new tests: allow rewording empty commits without complaints
- coverage of all to-do list commands
- new patch: hide interactive command messages in verbose mode
- make it possible to launch an editor inside 'output'
- new patch: allow disabling pre-commit and commit-msg separately
- add options --no-pre-commit and --no-commit-msg to git-commit
- redefine --no-verify as synonym for the above two
- new patch: squash skips commit-msg hook
- run commit-msg hook for reworded _and_ squashed commits
- a change to 'test_commit' options and 'fake_editor' debug output
Thanks for your time and reviews,
Fabian
Fabian Ruch (23):
rebase -i: allow replaying commits with empty log messages rebase -i: allow squashing empty commits without complaints rebase -i: allow rewording empty commits without complaints rebase -i: hide interactive command messages in verbose mode rebase -i: failed reword prints redundant error message
commit: allow disabling pre-commit and commit-msg separately rebase -i: squash skips commit-msg hook
rebase -i: reword executes pre-commit hook on interim commit rebase -i: teach do_pick the option --edit
rebase -i: implement reword in terms of do_pick
rebase -i: log the replay of root commits
rebase -i: root commits are replayed with an unnecessary option rebase -i: commit only once when rewriting picks
rebase -i: do not die in do_pick
rebase -i: teach do_pick the option --amend
rebase -i: teach do_pick the option --file
rebase -i: prepare for squash in terms of do_pick --amend rebase -i: implement squash in terms of do_pick
rebase -i: explicitly distinguish replay commands and exec tasks rebase -i: parse to-do list command line options
rebase -i: teach do_pick the option --reset-author
rebase -i: teach do_pick the option --signoff
rebase -i: enable options --signoff, --reset-author for pick, reword
Documentation/git-commit.txt | 8 +- builtin/commit.c | 32 ++++- git-rebase--interactive.sh | 288 ++++++++++++++++++++++++++++++++++-------- git-rebase.sh | 12 +- t/lib-rebase.sh | 8 +- t/t3404-rebase-interactive.sh | 234 ++++++++++++++++++++++++++++++++-- t/t3406-rebase-message.sh | 18 +++ t/t3412-rebase-root.sh | 16 +++ t/t7503-pre-commit-hook.sh | 65 ++++++++-- t/t7504-commit-msg-hook.sh | 85 ++++++++++--- t/test-lib-functions.sh | 23 +++- 11 files changed, 680 insertions(+), 109 deletions(-)
Signature · 1 line
2.0.1
PATCH v2 1/23 rebase -i: allow replaying commits with empty log messages
git-rebase--interactive handles empty log messages inconsistently between enabled and disabled fast-forwards. By default, commits with empty log messages are rebased successfully like in non-interactive mode. In contrast, the --no-ff option aborts the replay of such commits.
In line with not verifying rebased commits and behaving like git-rebase for pick lines, use the --allow-empty-message option to replay commits. Root commits are replayed by recreating them in do_pick using git-commit and all other commits are replayed using git-cherry-pick in pick_one. Apply the option, understood by both git-commit and git-cherry-pick, at the respective sites.
In case of reword and squash, continue to abort the rebase if the _resulting_ commit would have no commit message. The rationale behind this default is that patches and their log messages should be verified at least once. For unchanged commits this is assumed to have happened according to the author's standards when she created the commits for the first time. While the empty log message can always be kept in place by editing and resuming the aborted rebase, a debatable alternative could be to teach git-rebase--interactive the option --allow-empty-message for disabling complaints about empty log messages even in changed commits.
The fixup case is different again because it throws away the second commit's log message and uses the first log message for the changed commit. Do not abort the rebase if that message is empty either since it is assumed to have been verified already.
The remaining to-do list command edit is handled just like pick for this matter, because git-rebase--interactive replays the named commit without changes before the rebase is interrupted and the user can make her changes to the replayed commit.
Add tests. In particular, design the squash-specific test case such that it involves interim commits and fixup steps. Interim commits should not trigger failures themselves and fixup steps should not let git-rebase--interactive forget that it is still dealing with a squash result.
--- git-rebase--interactive.sh | 10 ++++++---- t/t3404-rebase-interactive.sh | 38 ++++++++++++++++++++++++++++++++++++++ t/t3412-rebase-root.sh | 16 ++++++++++++++++ 3 files changed, 60 insertions(+), 4 deletions(-)
git-rebase--interactive.sh +6 -4
@@ -249,7 +249,7 @@ pick_one () { test -d "$rewritten" && pick_one_preserving_merges "$@" && return - output eval git cherry-pick \ + output eval git cherry-pick --allow-empty-message \ ${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} \ "$strategy_args" $empty_args $ff "$@" }
@@ -363,7 +363,7 @@ pick_one_preserving_merges () { echo "$sha1 $(git rev-parse HEAD^0)" >> "$rewritten_list" ;; *) - output eval git cherry-pick \ + output eval git cherry-pick --allow-empty-message \ ${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} \ "$strategy_args" "$@" || die_with_patch $sha1 "Could not pick $sha1"
@@ -549,7 +549,8 @@ do_next () { squash|s|fixup|f) # This is an intermediate commit; its message will only be # used in case of trouble. So use the long version: - do_with_author output git commit --amend --no-verify -F "$squash_msg" \ + do_with_author output git commit --allow-empty-message \ + --amend --no-verify -F "$squash_msg" \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest" ;;
@@ -557,7 +558,8 @@ do_next () { # This is the final command of this squash/fixup group if test -f "$fixup_msg" then - do_with_author git commit --amend --no-verify -F "$fixup_msg" \ + do_with_author git commit --allow-empty-message \ + --amend --no-verify -F "$fixup_msg" \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest" else
t/t3404-rebase-interactive.sh +38 -0
@@ -1039,4 +1039,42 @@ test_expect_success 'short SHA-1 collide' ' ) ' +test_expect_success 'setup commits with empty commit log messages' ' + git checkout -b empty-log-messages master && + test_commit no-msg-commit-1 && + git commit --amend --allow-empty-message -F - </dev/null && + test_commit no-msg-commit-2 && + git commit --amend --allow-empty-message -F - </dev/null && + test_commit no-msg-commit-3 && + git commit --amend --allow-empty-message -F - </dev/null +' + +test_expect_success 'rebase commits with empty commit log messages' ' + git checkout -b rebase-empty-log-messages empty-log-messages && + set_fake_editor && + test_expect_code 0 env FAKE_LINES="1" git rebase -i master && + test_expect_code 0 env FAKE_LINES="1" git rebase -i --no-ff master +' + +test_expect_success 'reword commits with empty commit log messages' ' + git checkout -b reword-empty-log-messages empty-log-messages && + test_when_finished reset_rebase && + set_fake_editor && + test_must_fail env FAKE_LINES="reword 1" git rebase -i master +' + +test_expect_success 'squash commits with empty commit log messages' ' + git checkout -b squash-empty-log-messages empty-log-messages && + set_fake_editor && + test_must_fail env FAKE_LINES="1 squash 2 fixup 3" git rebase -i master && + git commit --allow-empty-message --amend && + git rebase --continue +' + +test_expect_success 'fixup commits with empty commit log messages' ' + git checkout -b fixup-empty-log-messages empty-log-messages && + set_fake_editor && + env FAKE_LINES="1 fixup 2" git rebase -i master +' + test_done
t/t3412-rebase-root.sh +16 -0
@@ -278,4 +278,20 @@ test_expect_success 'rebase -i -p --root with conflict (second part)' ' test_cmp expect-conflict-p out ' +test_expect_success 'rebase --root root with empty log message' ' + git checkout --orphan empty-log-messages-root master && + test_commit no-msg-root-commit && + git commit --amend --allow-empty-message -F - </dev/null && + test_expect_code 0 git rebase --root && + test_expect_code 0 git rebase --root --no-ff +' + +test_expect_success 'rebase --root commits with empty log messages' ' + git checkout -b empty-log-messages master && + test_commit no-msg-commit && + git commit --amend --allow-empty-message -F - </dev/null && + test_expect_code 0 git rebase --root && + test_expect_code 0 git rebase --root --no-ff +' + test_done
Signature · 1 line
2.0.1
PATCH v2 2/23 rebase -i: allow squashing empty commits without complaints
In reply to Fabian RuchThe to-do list commands squash and fixup apply the changes introduced by the named commit to the tree but instead of creating a new commit on top of the current head it replaces the previous commit with a new commit that records the updated tree. If the result is an empty commit git-rebase stops with the error message
You asked to amend the most recent commit, but doing so would make it empty. You can repeat your command with --allow-empty, or you can remove the commit entirely with "git reset HEAD^".
This message is not very helpful because neither does git-rebase support an option --allow-empty nor does the messages say how to resume the rebase. Firstly, change the error message to
The squash result is empty and --keep-empty was not specified.
You can remove the squash commit now with
git reset HEAD^
Once you are down, run
git rebase --continue
If the user wishes to squash a sequence of commits into one commit, f. i.
pick A
squash Revert "A"
squash A'
, it does not matter for the end result that the first squash result, or any sub-sequence in general, is going to be empty. The squash message is not affected at all by which commits are created and only the commit created by the last line in the sequence will end up in the final history. Secondly, print the error message only if the whole squash sequence produced an empty commit.
Lastly, since an empty squash commit is not a failure to rewrite the history as planned, issue the message above as a mere warning and interrupt the rebase with the return value zero. The interruption should be considered as a notification with the chance to undo it on the spot. Specifying the --keep-empty option tells git-rebase to keep empty squash commits in the rebased history without notification.
Add tests.
---
Hi,
Peter Krefting is cc'd as the author of the bug report "Confusing error message in rebase when commit becomes empty" discussed on the mailing list in June. Phil Hord and Jeff King both participated in the problem discussion which ended with two proposals by Jeff.
Quoted 6 lines · Jeff King
1. Always keep such empty commits. A user who is surprised by them being empty can then revisit them. Or drop them by doing another rebase without --keep-empty.
2. Notice ourselves that the end-result of the whole squash is an empty commit, and stop to let the user deal with it.
This patch chooses the second alternative. Either way seems OK. The crucial consensus of the discussion was to silently throw away empty interim commits.
Fabian
git-rebase--interactive.sh | 20 +++++++++++--- t/t3404-rebase-interactive.sh | 62 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 3 deletions(-)
git-rebase--interactive.sh +17 -3
@@ -549,7 +549,7 @@ do_next () { squash|s|fixup|f) # This is an intermediate commit; its message will only be # used in case of trouble. So use the long version: - do_with_author output git commit --allow-empty-message \ + do_with_author output git commit --allow-empty-message --allow-empty \ --amend --no-verify -F "$squash_msg" \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest"
@@ -558,18 +558,32 @@ do_next () { # This is the final command of this squash/fixup group if test -f "$fixup_msg" then - do_with_author git commit --allow-empty-message \ + do_with_author git commit --allow-empty-message --allow-empty \ --amend --no-verify -F "$fixup_msg" \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest" else cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit rm -f "$GIT_DIR"/MERGE_MSG - do_with_author git commit --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \ + do_with_author git commit --allow-empty --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest" fi rm -f "$squash_msg" "$fixup_msg" + if test -z "$keep_empty" && is_empty_commit HEAD + then + echo "$sha1" >"$state_dir"/stopped-sha + warn "The squash result is empty and --keep-empty was not specified." + warn + warn "You can remove the squash commit now with" + warn + warn " git reset HEAD^" + warn + warn "Once you are down, run" + warn + warn " git rebase --continue" + exit 0 + fi ;; esac record_in_rewritten $sha1
t/t3404-rebase-interactive.sh +62 -0
68 lines not on this page · raw message
Signature · 1 line
2.0.1
Re: [PATCH v2 02/23] rebase -i: allow squashing empty commits without complaints
Fabian Ruch:
Quoted 4 lines
Quoted 2 lines
2. Notice ourselves that the end-result of the whole squash is an empty commit, and stop to let the user deal with it.
This patch chooses the second alternative. Either way seems OK. The crucial consensus of the discussion was to silently throw away empty interim commits.
Yes, the important part is that giving good advice is better than giving bad advice. Thank you for taking your time to fix this.
I haven't reviewed the changes themselves, but I am happy with the underlying idea.
Signature · 1 line
\\// Peter - http://www.softwolves.pp.se/
Re: [PATCH v2 02/23] rebase -i: allow squashing empty commits without complaints
In reply to Fabian RuchQuoted 14 lines · Fabian Ruch
The to-do list commands
squashandfixupapply the changes introduced by the named commit to the tree but instead of creating a new commit on top of the current head it replaces the previous commit with a new commit that records the updated tree. If the result is an empty commit git-rebase stops with the error messageYou asked to amend the most recent commit, but doing so would make it empty. You can repeat your command with --allow-empty, or you can remove the commit entirely with "git reset HEAD^".
This message is not very helpful because neither does git-rebase support an option
--allow-emptynor does the messages say how to resume the rebase. Firstly, change the error message to
Quoted 7 lines
The squash result is empty and --keep-empty was not specified.
You can remove the squash commit now with
git reset HEAD^
Once you are down, run
I guess you meant: s/down/done
Same issue with the actually message in the code (below).
Quoted 85 lines
git rebase --continueIf the user wishes to squash a sequence of commits into one commit, f. i.
pick A
squash Revert "A"
squash A', it does not matter for the end result that the first squash result, or any sub-sequence in general, is going to be empty. The squash message is not affected at all by which commits are created and only the commit created by the last line in the sequence will end up in the final history. Secondly, print the error message only if the whole squash sequence produced an empty commit.
Lastly, since an empty squash commit is not a failure to rewrite the history as planned, issue the message above as a mere warning and interrupt the rebase with the return value zero. The interruption should be considered as a notification with the chance to undo it on the spot. Specifying the
--keep-emptyoption tells git-rebase to keep empty squash commits in the rebased history without notification.Add tests.
Reported-by Peter Krefting <[email protected]>Signed-off-by Fabian Ruch <[email protected]>---
Hi,Peter Krefting is cc'd as the author of the bug report "Confusing error message in rebase when commit becomes empty" discussed on the mailing list in June. Phil Hord and Jeff King both participated in the problem discussion which ended with two proposals by Jeff.
Quoted 6 lines · Jeff King
1. Always keep such empty commits. A user who is surprised by them being empty can then revisit them. Or drop them by doing another rebase without --keep-empty.
2. Notice ourselves that the end-result of the whole squash is an empty commit, and stop to let the user deal with it.
This patch chooses the second alternative. Either way seems OK. The crucial consensus of the discussion was to silently throw away empty interim commits.
Fabian
git-rebase--interactive.sh | 20 +++++++++++--- t/t3404-rebase-interactive.sh | 62 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 3 deletions(-)git-rebase--interactive.sh +3
-3@@ -549,7 +549,7 @@ do_next () { squash|s|fixup|f) # This is an intermediate commit; its message will only be # used in case of trouble. So use the long version: - do_with_author output git commit --allow-empty-message \ + do_with_author output git commit --allow-empty-message --allow-empty \ --amend --no-verify -F "$squash_msg" \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest"@@ -558,18 +558,32 @@ do_next () { # This is the final command of this squash/fixup group if test -f "$fixup_msg" then - do_with_author git commit --allow-empty-message \ + do_with_author git commit --allow-empty-message --allow-empty \ --amend --no-verify -F "$fixup_msg" \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest" else cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit rm -f "$GIT_DIR"/MERGE_MSG - do_with_author git commit --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \ + do_with_author git commit --allow-empty --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest" fi rm -f "$squash_msg" "$fixup_msg"
git-rebase--interactive.sh do_next () { · line 582
Quoted 10 lines
git-rebase--interactive.sh +10
-0+ if test -z "$keep_empty" && is_empty_commit HEAD + then + echo "$sha1" >"$state_dir"/stopped-sha + warn "The squash result is empty and --keep-empty was not specified." + warn + warn "You can remove the squash commit now with" + warn + warn " git reset HEAD^" + warn + warn "Once you are down, run"
s/down/done/
Quoted 87 lines
git-rebase--interactive.sh +4
-0+ warn + warn " git rebase --continue" + exit 0 + fi ;; esac record_in_rewritten $sha1t/t3404-rebase-interactive.sh +62
-1@@ -237,6 +237,68 @@ test_expect_success 'retain authorship' ' git show HEAD | grep "^Author: Twerp Snog" ' +test_expect_success 'setup squash/fixup reverted and fixed feature' ' + git checkout -b reverted-feature master && + test_commit feature && + git revert feature && + git checkout -b fixed-feature reverted-feature && + test_commit featurev2 +' + +test_expect_success 'fixup fixed feature (empty interim commit)' ' + git checkout -b fixup-fixed-feature fixed-feature && + set_fake_editor && + FAKE_LINES="1 fixup 2 fixup 3" git rebase -i master && + git log --oneline master.. >actual && + test_line_count = 1 actual && + git diff --exit-code featurev2 +' + +test_expect_success 'squash fixed feature (empty interim commit)' ' + git checkout -b squash-fixed-feature fixed-feature && + set_fake_editor && + FAKE_LINES="1 squash 2 squash 3" git rebase -i master && + git log --oneline master.. >actual && + test_line_count = 1 actual && + git diff --exit-code featurev2 +' + +test_expect_success 'fixup reverted feature (empty final commit)' ' + git checkout -b fixup-reverted-feature reverted-feature && + set_fake_editor && + FAKE_LINES="1 fixup 2" git rebase -i master && + git reset HEAD^ && + git rebase --continue && + test_cmp_rev master HEAD +' + +test_expect_success 'squash reverted feature (empty final commit)' ' + git checkout -b squash-reverted-feature reverted-feature && + set_fake_editor && + FAKE_LINES="1 squash 2" git rebase -i master && + git reset HEAD^ && + git rebase --continue && + test_cmp_rev master HEAD +' + +test_expect_success 'fixup reverted feature (empty final commit with --keep-empty)' ' + git checkout -b fixup-keep-reverted-feature reverted-feature && + set_fake_editor && + FAKE_LINES="1 fixup 2" git rebase -i --keep-empty master && + git log --oneline master.. >actual && + test_line_count = 1 actual && + git diff --exit-code master +' + +test_expect_success 'squash reverted feature (empty final commit with --keep-empty)' ' + git checkout -b squash-keep-reverted-feature reverted-feature && + set_fake_editor && + FAKE_LINES="1 squash 2" git rebase -i --keep-empty master && + git log --oneline master.. >actual && + test_line_count = 1 actual && + git diff --exit-code master +' + test_expect_success 'squash' ' git reset --hard twerp && echo B > file7 && --2.0.1
--
To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Re: [PATCH v2 02/23] rebase -i: allow squashing empty commits without complaints
Hi Eric,
Quoted 19 lines · Eric Sunshine
Quoted 18 lines · Fabian Ruch
The to-do list commands
squashandfixupapply the changes introduced by the named commit to the tree but instead of creating a new commit on top of the current head it replaces the previous commit with a new commit that records the updated tree. If the result is an empty commit git-rebase stops with the error messageYou asked to amend the most recent commit, but doing so would make it empty. You can repeat your command with --allow-empty, or you can remove the commit entirely with "git reset HEAD^".
This message is not very helpful because neither does git-rebase support an option
--allow-emptynor does the messages say how to resume the rebase. Firstly, change the error message toThe squash result is empty and --keep-empty was not specified.
You can remove the squash commit now with
Quoted 7 lines
Quoted 3 lines
git reset HEAD^
Once you are down, run
I guess you meant: s/down/done
Same issue with the actually message in the code (below).
Fixed.
Quoted 89 lines
Quoted 89 lines
git rebase --continueIf the user wishes to squash a sequence of commits into one commit, f. i.
pick A
squash Revert "A"
squash A', it does not matter for the end result that the first squash result, or any sub-sequence in general, is going to be empty. The squash message is not affected at all by which commits are created and only the commit created by the last line in the sequence will end up in the final history. Secondly, print the error message only if the whole squash sequence produced an empty commit.
Lastly, since an empty squash commit is not a failure to rewrite the history as planned, issue the message above as a mere warning and interrupt the rebase with the return value zero. The interruption should be considered as a notification with the chance to undo it on the spot. Specifying the
--keep-emptyoption tells git-rebase to keep empty squash commits in the rebased history without notification.Add tests.
Reported-by Peter Krefting <[email protected]>Signed-off-by Fabian Ruch <[email protected]>---
Hi,Peter Krefting is cc'd as the author of the bug report "Confusing error message in rebase when commit becomes empty" discussed on the mailing list in June. Phil Hord and Jeff King both participated in the problem discussion which ended with two proposals by Jeff.
Quoted 6 lines · Jeff King
1. Always keep such empty commits. A user who is surprised by them being empty can then revisit them. Or drop them by doing another rebase without --keep-empty.
2. Notice ourselves that the end-result of the whole squash is an empty commit, and stop to let the user deal with it.
This patch chooses the second alternative. Either way seems OK. The crucial consensus of the discussion was to silently throw away empty interim commits.
Fabian
git-rebase--interactive.sh | 20 +++++++++++--- t/t3404-rebase-interactive.sh | 62 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 3 deletions(-)git-rebase--interactive.sh +7
-3@@ -549,7 +549,7 @@ do_next () { squash|s|fixup|f) # This is an intermediate commit; its message will only be # used in case of trouble. So use the long version: - do_with_author output git commit --allow-empty-message \ + do_with_author output git commit --allow-empty-message --allow-empty \ --amend --no-verify -F "$squash_msg" \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest"@@ -558,18 +558,32 @@ do_next () { # This is the final command of this squash/fixup group if test -f "$fixup_msg" then - do_with_author git commit --allow-empty-message \ + do_with_author git commit --allow-empty-message --allow-empty \ --amend --no-verify -F "$fixup_msg" \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest" else cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit rm -f "$GIT_DIR"/MERGE_MSG - do_with_author git commit --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \ + do_with_author git commit --allow-empty --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest" fi rm -f "$squash_msg" "$fixup_msg" + if test -z "$keep_empty" && is_empty_commit HEAD + then + echo "$sha1" >"$state_dir"/stopped-sha + warn "The squash result is empty and --keep-empty was not specified."
Quoted 8 lines
git-rebase--interactive.sh do_next () { · line 582
Quoted 6 lines
git-rebase--interactive.sh +6
-0+ warn + warn "You can remove the squash commit now with" + warn + warn " git reset HEAD^" + warn + warn "Once you are down, run"s/down/done/
Thanks for the thorough reading.
Fabian
Re: [PATCH v2 02/23] rebase -i: allow squashing empty commits without complaints
In reply to Fabian RuchThanks for working on this.
Quoted 14 lines · Fabian Ruch
The to-do list commands
squashandfixupapply the changes introduced by the named commit to the tree but instead of creating a new commit on top of the current head it replaces the previous commit with a new commit that records the updated tree. If the result is an empty commit git-rebase stops with the error messageYou asked to amend the most recent commit, but doing so would make it empty. You can repeat your command with --allow-empty, or you can remove the commit entirely with "git reset HEAD^".
This message is not very helpful because neither does git-rebase support an option
--allow-emptynor does the messages say how to resume the rebase. Firstly, change the error message to
Quoted 7 lines
The squash result is empty and --keep-empty was not specified.
You can remove the squash commit now with
git reset HEAD^
Once you are down, run
s/down/done
Quoted 3 lines
git rebase --continueI like the direction of this rewording, but it seems to hide the instructions for the user who wants to keep the empty commit.
I'm not sure how to improve it. Maybe this:
The squash result is empty and --keep-empty was not specified.
You may remove the squash commit now with
git reset HEAD^
or keep it by doing nothing extra.
Continue the rebase with
git rebase --continue
Quoted 39 lines
If the user wishes to squash a sequence of commits into one commit, f. i.
pick A
squash Revert "A"
squash A', it does not matter for the end result that the first squash result, or any sub-sequence in general, is going to be empty. The squash message is not affected at all by which commits are created and only the commit created by the last line in the sequence will end up in the final history. Secondly, print the error message only if the whole squash sequence produced an empty commit.
Lastly, since an empty squash commit is not a failure to rewrite the history as planned, issue the message above as a mere warning and interrupt the rebase with the return value zero. The interruption should be considered as a notification with the chance to undo it on the spot. Specifying the
--keep-emptyoption tells git-rebase to keep empty squash commits in the rebased history without notification.Add tests.
Reported-by Peter Krefting <[email protected]>Signed-off-by Fabian Ruch <[email protected]>---
Hi,Peter Krefting is cc'd as the author of the bug report "Confusing error message in rebase when commit becomes empty" discussed on the mailing list in June. Phil Hord and Jeff King both participated in the problem discussion which ended with two proposals by Jeff.
Quoted 4 lines · Jeff King
1. Always keep such empty commits. A user who is surprised by them being empty can then revisit them. Or drop them by doing another rebase without --keep-empty.
Quoted 6 lines
Quoted 2 lines
2. Notice ourselves that the end-result of the whole squash is an empty commit, and stop to let the user deal with it.
This patch chooses the second alternative. Either way seems OK. The crucial consensus of the discussion was to silently throw away empty interim commits.
This patch does _not_ silently throw away empty commits. I wonder if such behavior could be triggered with something like --no-keep-empty. Maybe that belongs in another patch.
Quoted 38 lines
Fabian
git-rebase--interactive.sh | 20 +++++++++++--- t/t3404-rebase-interactive.sh | 62 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 3 deletions(-)git-rebase--interactive.sh +3
-3@@ -549,7 +549,7 @@ do_next () { squash|s|fixup|f) # This is an intermediate commit; its message will only be # used in case of trouble. So use the long version: - do_with_author output git commit --allow-empty-message \ + do_with_author output git commit --allow-empty-message --allow-empty \ --amend --no-verify -F "$squash_msg" \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest"@@ -558,18 +558,32 @@ do_next () { # This is the final command of this squash/fixup group if test -f "$fixup_msg" then - do_with_author git commit --allow-empty-message \ + do_with_author git commit --allow-empty-message --allow-empty \ --amend --no-verify -F "$fixup_msg" \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest" else cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit rm -f "$GIT_DIR"/MERGE_MSG - do_with_author git commit --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \ + do_with_author git commit --allow-empty --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest" fi rm -f "$squash_msg" "$fixup_msg"
git-rebase--interactive.sh do_next () { · line 582
Quoted 10 lines
git-rebase--interactive.sh +10
-0+ if test -z "$keep_empty" && is_empty_commit HEAD + then + echo "$sha1" >"$state_dir"/stopped-sha + warn "The squash result is empty and --keep-empty was not specified." + warn + warn "You can remove the squash commit now with" + warn + warn " git reset HEAD^" + warn + warn "Once you are down, run"
s/down/done
Quoted 83 lines
git-rebase--interactive.sh +4
-0+ warn + warn " git rebase --continue" + exit 0 + fi ;; esac record_in_rewritten $sha1t/t3404-rebase-interactive.sh +62
-1@@ -237,6 +237,68 @@ test_expect_success 'retain authorship' ' git show HEAD | grep "^Author: Twerp Snog" ' +test_expect_success 'setup squash/fixup reverted and fixed feature' ' + git checkout -b reverted-feature master && + test_commit feature && + git revert feature && + git checkout -b fixed-feature reverted-feature && + test_commit featurev2 +' + +test_expect_success 'fixup fixed feature (empty interim commit)' ' + git checkout -b fixup-fixed-feature fixed-feature && + set_fake_editor && + FAKE_LINES="1 fixup 2 fixup 3" git rebase -i master && + git log --oneline master.. >actual && + test_line_count = 1 actual && + git diff --exit-code featurev2 +' + +test_expect_success 'squash fixed feature (empty interim commit)' ' + git checkout -b squash-fixed-feature fixed-feature && + set_fake_editor && + FAKE_LINES="1 squash 2 squash 3" git rebase -i master && + git log --oneline master.. >actual && + test_line_count = 1 actual && + git diff --exit-code featurev2 +' + +test_expect_success 'fixup reverted feature (empty final commit)' ' + git checkout -b fixup-reverted-feature reverted-feature && + set_fake_editor && + FAKE_LINES="1 fixup 2" git rebase -i master && + git reset HEAD^ && + git rebase --continue && + test_cmp_rev master HEAD +' + +test_expect_success 'squash reverted feature (empty final commit)' ' + git checkout -b squash-reverted-feature reverted-feature && + set_fake_editor && + FAKE_LINES="1 squash 2" git rebase -i master && + git reset HEAD^ && + git rebase --continue && + test_cmp_rev master HEAD +' + +test_expect_success 'fixup reverted feature (empty final commit with --keep-empty)' ' + git checkout -b fixup-keep-reverted-feature reverted-feature && + set_fake_editor && + FAKE_LINES="1 fixup 2" git rebase -i --keep-empty master && + git log --oneline master.. >actual && + test_line_count = 1 actual && + git diff --exit-code master +' + +test_expect_success 'squash reverted feature (empty final commit with --keep-empty)' ' + git checkout -b squash-keep-reverted-feature reverted-feature && + set_fake_editor && + FAKE_LINES="1 squash 2" git rebase -i --keep-empty master && + git log --oneline master.. >actual && + test_line_count = 1 actual && + git diff --exit-code master +' + test_expect_success 'squash' ' git reset --hard twerp && echo B > file7 && --2.0.1
PATCH v2 3/23 rebase -i: allow rewording empty commits without complaints
In reply to Fabian RuchThe to-do list command reword replays a commit like pick but lets the user also edit the commit's log message. If --keep-empty is passed as option to git-rebase--interactive, empty commits ought to be replayed without complaints. However, if the users chooses to reword an empty commit by changing the respective to-do list entry from
pick fa1afe1 Empty commit
to
reword fa1afe1 Empty commit
, then git-rebase--interactive suddenly fails to replay the empty commit. This is especially counterintuitive because reword is thought of as a pick that alters the log message in some way but nothing more and the unchanged to-do list entry would not fail.
Handle reword by cherry-picking the named commit and editing the log message using
git commit --allow-empty --amend
instead of
git commit --amend.
Add test.
--- git-rebase--interactive.sh | 2 +- t/t3404-rebase-interactive.sh | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-)
git-rebase--interactive.sh +1 -1
@@ -504,7 +504,7 @@ do_next () { mark_action_done do_pick $sha1 "$rest" - git commit --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} || { + git commit --allow-empty --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} || { warn "Could not amend commit after successfully picking $sha1... $rest" warn "This is most likely due to an empty commit message, or the pre-commit hook" warn "failed. If the pre-commit hook failed, you may need to resolve the issue before"
t/t3404-rebase-interactive.sh +24 -0
@@ -75,6 +75,30 @@ test_expect_success 'rebase --keep-empty' ' test_line_count = 6 actual ' +test_expect_success 'rebase --keep-empty (reword)' ' + git checkout -b reword-emptybranch emptybranch && + set_fake_editor && + FAKE_LINES="1 reword 2" git rebase --keep-empty -i HEAD~2 && + git log --oneline >actual && + test_line_count = 6 actual +' + +test_expect_success 'rebase --keep-empty (fixup)' ' + git checkout -b fixup-emptybranch emptybranch && + set_fake_editor && + FAKE_LINES="1 fixup 2" git rebase --keep-empty -i HEAD~2 && + git log --oneline >actual && + test_line_count = 5 actual +' + +test_expect_success 'rebase --keep-empty (squash)' ' + git checkout -b squash-emptybranch emptybranch && + set_fake_editor && + FAKE_LINES="1 squash 2" git rebase --keep-empty -i HEAD~2 && + git log --oneline >actual && + test_line_count = 5 actual +' + test_expect_success 'rebase -i with the exec command' ' git checkout master && (
Signature · 1 line
2.0.1
PATCH v2 4/23 rebase -i: hide interactive command messages in verbose mode
In reply to Fabian Ruchgit-rebase--interactive prints summary messages of the commits it creates in the final history only if the --verbose option is specified by the user and suppresses them otherwise. This behaviour is implemented by wrapping git-commit calls in a shell function named output which redirects stderr to stdout, captures stdout in a shell variable and ignores its contents unless the command exits with an error status.
The command lines used to implement the to-do list commands reword and squash print diagnostic messages even in non-verbose mode. The reason for this inconsistency is that both commands launch the log message editor which usually requires a working terminal attached to stdin. Temporarily redirect the editor output to a third file descriptor in order to ship it around the capture stream. Wrap the respective git-commit command lines in output.
fake_editor prints the to-do list before and after applying the FAKE_LINES rewrite rules to it. Redirect this debug output to stderr so that it does not interfere with the git-rebase status output. Add test.
--- git-rebase--interactive.sh | 9 +++++---- git-rebase.sh | 12 ++++++++++-- t/lib-rebase.sh | 8 ++++---- t/t3404-rebase-interactive.sh | 18 ++++++------------ t/t3406-rebase-message.sh | 18 ++++++++++++++++++ 5 files changed, 43 insertions(+), 22 deletions(-)
git-rebase--interactive.sh +5 -4
@@ -504,7 +504,7 @@ do_next () { mark_action_done do_pick $sha1 "$rest" - git commit --allow-empty --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} || { + output git commit --allow-empty --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} || { warn "Could not amend commit after successfully picking $sha1... $rest" warn "This is most likely due to an empty commit message, or the pre-commit hook" warn "failed. If the pre-commit hook failed, you may need to resolve the issue before"
@@ -558,14 +558,14 @@ do_next () { # This is the final command of this squash/fixup group if test -f "$fixup_msg" then - do_with_author git commit --allow-empty-message --allow-empty \ + do_with_author output git commit --allow-empty-message --allow-empty \ --amend --no-verify -F "$fixup_msg" \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest" else cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit rm -f "$GIT_DIR"/MERGE_MSG - do_with_author git commit --allow-empty --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \ + do_with_author output git commit --allow-empty --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest" fi
@@ -923,6 +923,8 @@ EOF ;; esac +mkdir -p "$state_dir" || die "Could not create temporary $state_dir" + git var GIT_COMMITTER_IDENT >/dev/null || die "You need to set your committer info first"
@@ -938,7 +940,6 @@ then fi orig_head=$(git rev-parse --verify HEAD) || die "No HEAD?" -mkdir -p "$state_dir" || die "Could not create temporary $state_dir" : > "$state_dir"/interactive || die "Could not mark as interactive" write_basic_state
git-rebase.sh +10 -2
@@ -131,9 +131,17 @@ write_basic_state () { output () { case "$verbose" in '') - output=$("$@" 2>&1 ) + cat >"$state_dir"/editor.sh <<EOF +#!/bin/sh +$(git var GIT_EDITOR) "\$@" >&3 +EOF + chmod +x "$state_dir"/editor.sh + ( + export GIT_EDITOR=\""$state_dir"/editor.sh\" + "$@" 3>&1 >"$state_dir"/output 2>&1 + ) status=$? - test $status != 0 && printf "%s\n" "$output" + test $status != 0 && cat "$state_dir"/output return $status ;; *)
t/lib-rebase.sh +4 -4
@@ -41,8 +41,8 @@ set_fake_editor () { test -z "$FAKE_LINES" && exit grep -v '^#' < "$1" > "$1".tmp rm -f "$1" - echo 'rebase -i script before editing:' - cat "$1".tmp + echo 'rebase -i script before editing:' >&2 + cat "$1".tmp >&2 action=pick for line in $FAKE_LINES; do case $line in
@@ -59,8 +59,8 @@ set_fake_editor () { action=pick;; esac done - echo 'rebase -i script after editing:' - cat "$1" + echo 'rebase -i script after editing:' >&2 + cat "$1" >&2 EOF test_set_editor "$(pwd)/fake-editor.sh"
t/t3404-rebase-interactive.sh +6 -12
@@ -873,9 +873,8 @@ test_expect_success 'running "git rebase -i --exec git show HEAD"' ' ( FAKE_LINES="1 exec_git_show_HEAD 2 exec_git_show_HEAD" && export FAKE_LINES && - git rebase -i HEAD~2 >expect + git rebase -i HEAD~2 >expected ) && - sed -e "1,9d" expect >expected && test_cmp expected actual '
@@ -887,9 +886,8 @@ test_expect_success 'running "git rebase --exec git show HEAD -i"' ' ( FAKE_LINES="1 exec_git_show_HEAD 2 exec_git_show_HEAD" && export FAKE_LINES && - git rebase -i HEAD~2 >expect + git rebase -i HEAD~2 >expected ) && - sed -e "1,9d" expect >expected && test_cmp expected actual '
@@ -901,9 +899,8 @@ test_expect_success 'running "git rebase -ix git show HEAD"' ' ( FAKE_LINES="1 exec_git_show_HEAD 2 exec_git_show_HEAD" && export FAKE_LINES && - git rebase -i HEAD~2 >expect + git rebase -i HEAD~2 >expected ) && - sed -e "1,9d" expect >expected && test_cmp expected actual '
@@ -915,9 +912,8 @@ test_expect_success 'rebase -ix with several <CMD>' ' ( FAKE_LINES="1 exec_git_show_HEAD;_pwd 2 exec_git_show_HEAD;_pwd" && export FAKE_LINES && - git rebase -i HEAD~2 >expect + git rebase -i HEAD~2 >expected ) && - sed -e "1,9d" expect >expected && test_cmp expected actual '
@@ -930,9 +926,8 @@ test_expect_success 'rebase -ix with several instances of --exec' ' FAKE_LINES="1 exec_git_show_HEAD exec_pwd 2 exec_git_show_HEAD exec_pwd" && export FAKE_LINES && - git rebase -i HEAD~2 >expect + git rebase -i HEAD~2 >expected ) && - sed -e "1,11d" expect >expected && test_cmp expected actual '
@@ -956,9 +951,8 @@ test_expect_success 'rebase -ix with --autosquash' ' git checkout -b autosquash_expected && FAKE_LINES="1 fixup 3 fixup 4 exec_git_show_HEAD 2 exec_git_show_HEAD" && export FAKE_LINES && - git rebase -i HEAD~4 >expect + git rebase -i HEAD~4 >expected ) && - sed -e "1,13d" expect >expected && test_cmp expected actual '
t/t3406-rebase-message.sh +18 -0
@@ -4,6 +4,8 @@ test_description='messages from rebase operation' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-rebase.sh + test_expect_success 'setup' ' test_commit O fileO && test_commit X fileX &&
@@ -84,4 +86,20 @@ test_expect_success 'rebase --onto outputs the invalid ref' ' test_i18ngrep "invalid-ref" err ' +test_expect_success 'commit summary is suppressed in non-verbose mode' ' + git checkout --detach Y && + cat >expected.out <<-EOF && + Rebasing (1/5)\r + Rebasing (2/5)\r + Rebasing (3/5)\r + Rebasing (4/5)\r + Rebasing (5/5)\r + EOF + set_fake_editor && + FAKE_LINES="reword 1 fixup 2 fixup 3 4 squash 5" \ + git rebase -i --root >actual.out.tmp && + sed -e "s/\r/\\\\r\n/g" <actual.out.tmp >actual.out && + test_cmp expected.out actual.out +' + test_done
Signature · 1 line
2.0.1
Re: [PATCH v2 04/23] rebase -i: hide interactive command messages in verbose mode
Quoted 10 lines · Fabian Ruch
git-rebase--interactive.sh +2
-0@@ -923,6 +923,8 @@ EOF ;; esac +mkdir -p "$state_dir" || die "Could not create temporary $state_dir" + git var GIT_COMMITTER_IDENT >/dev/null || die "You need to set your committer info first"
git-rebase--interactive.sh then · line 945
Quoted 7 lines
git-rebase--interactive.sh +0
-1@@ -938,7 +940,6 @@ then fi orig_head=$(git rev-parse --verify HEAD) || die "No HEAD?" -mkdir -p "$state_dir" || die "Could not create temporary $state_dir" : > "$state_dir"/interactive || die "Could not mark as interactive" write_basic_state
Why this change? I can't figure out how it relates to the output change.
t/t3404-rebase-interactive.sh test_expect_success 'running "git rebase -i --exec git show HEAD"' ' · line 879
Quoted 10 lines
t/t3404-rebase-interactive.sh +1
-2@@ -873,9 +873,8 @@ test_expect_success 'running "git rebase -i --exec git show HEAD"' ' ( FAKE_LINES="1 exec_git_show_HEAD 2 exec_git_show_HEAD" && export FAKE_LINES && - git rebase -i HEAD~2 >expect + git rebase -i HEAD~2 >expected ) && - sed -e "1,9d" expect >expected && test_cmp expected actual '
Getting rid of these magic removals is a very nice change, thank you.
Signature · 2 lines
Thomas Rast [email protected]
Re: [PATCH v2 04/23] rebase -i: hide interactive command messages in verbose mode
Hi Thomas,
Quoted 13 lines · Thomas Rast
Quoted 12 lines · Fabian Ruch
git-rebase--interactive.sh +2
-0@@ -923,6 +923,8 @@ EOF ;; esac +mkdir -p "$state_dir" || die "Could not create temporary $state_dir" + git var GIT_COMMITTER_IDENT >/dev/null || die "You need to set your committer info first"@@ -938,7 +940,6 @@ then fi
Quoted 8 lines
git-rebase--interactive.sh then · line 945
Quoted 5 lines
git-rebase--interactive.sh +0
-1orig_head=$(git rev-parse --verify HEAD) || die "No HEAD?" -mkdir -p "$state_dir" || die "Could not create temporary $state_dir" : > "$state_dir"/interactive || die "Could not mark as interactive" write_basic_stateWhy this change? I can't figure out how it relates to the output change.
Creating the state directory a few steps earlier into 'git_rebase__interactive' is necessary because the changed definition of 'output' needs it for 'editor.sh'. This change was triggered by a failing test case that used the <branch> argument with git-rebase. The 'git checkout <branch>', which is executed if 'switch_to' is set to <branch>, is wrapped into an 'output' line and 'output' failed because it wasn't able to create 'editor.sh'.
The state directory (of git-rebase--interactive!) is now created directly after the case expression that handles --continue, --skip and --edit-todo. They all assume the existence of the state directory and either jump into 'do_rest' or 'exit' immediately, that is creating the directory earlier would make the options handling code somewhat incorrect and would not change anything for the start sequence of git-rebase--interactive.
The patch message now reads as follows (with the reference to 7725cb5 in the second paragraph and the complete third paragraph added):
Quoted 26 lines
rebase -i: hide interactive command messages in verbose mode
git-rebase--interactive prints summary messages of the commits it creates in the final history only if the
--verboseoption is specified by the user and suppresses them otherwise. This behaviour is implemented by wrapping git-commit calls in a shell function namedoutputwhich redirects stderr to stdout, captures stdout in a shell variable and ignores its contents unless the command exits with an error status.The command lines used to implement the to-do list commands
rewordandsquashprint diagnostic messages even in non-verbose mode. The reason for this inconsistency is that both commands launch the log message editor which usually requires a working terminal attached to stdin. Wrapping therewordandsquashcommand lines inoutputwould seemingly freeze the terminal (see commit 7725cb5, "rebase -i:
fix reword when using a terminal editor"). Temporarily redirect the editor output to a third file descriptor in order to ship it around the capture stream. Wrap the remaining git-commit command lines in the newoutput.In order to temporarily redirect the editor output, the new definition of
outputcreates a script in the state directory to be used asGIT_EDITOR. Make sure the state directory exists beforeoutputis called for the first time.
Quoted 4 lines
fake_editorprints the to-do list before and after applying theFAKE_LINESrewrite rules to it. Redirect this debug output to stderr so that it does not interfere with the git-rebase status output. Add test.
Fabian
Re: [PATCH v2 04/23] rebase -i: hide interactive command messages in verbose mode
Quoted 32 lines · Fabian Ruch
Hi Thomas,
Quoted 13 lines · Thomas Rast
Quoted 12 lines · Fabian Ruch
git-rebase--interactive.sh +2
-0@@ -923,6 +923,8 @@ EOF ;; esac +mkdir -p "$state_dir" || die "Could not create temporary $state_dir" + git var GIT_COMMITTER_IDENT >/dev/null || die "You need to set your committer info first"@@ -938,7 +940,6 @@ then fiQuoted 8 lines
git-rebase--interactive.sh then · line 945
Quoted 5 lines
git-rebase--interactive.sh +0
-1orig_head=$(git rev-parse --verify HEAD) || die "No HEAD?" -mkdir -p "$state_dir" || die "Could not create temporary $state_dir" : > "$state_dir"/interactive || die "Could not mark as interactive" write_basic_stateWhy this change? I can't figure out how it relates to the output change.
Creating the state directory a few steps earlier into '
git_rebase__interactive' is necessary because the changed definition of 'output' needs it for 'editor.sh'. This change was triggered by a failing test case that used the <branch> argument with git-rebase. The 'git checkout <branch>', which is executed if 'switch_to' is set to <branch>, is wrapped into an 'output' line and 'output' failed because it wasn't able to create 'editor.sh'.
[...]
Quoted 4 lines
Quoted 4 lines
In order to temporarily redirect the editor output, the new definition of
outputcreates a script in the state directory to be used asGIT_EDITOR. Make sure the state directory exists beforeoutputis called for the first time.
Ah, makes sense. Thanks for the explanations!
Signature · 2 lines
Thomas Rast [email protected]
PATCH v2 5/23 rebase -i: failed reword prints redundant error message
In reply to Fabian RuchThe to-do list command reword replays a commit like pick but lets the user also edit the commit's log message. If the edited log message is empty or is found ill-formatted by one of the commit hooks, git-rebase--interactive prints three error messages to the console.
1. The git-commit output, which contains all the output from hook scripts.
2. A rebase diagnosis saying at which task on the to-do list it got stuck.
3. Generic presumptions about what could have triggered the error.
The third message contains redundant information and does not add any enlightenment either, which makes the output unnecessarily longish and different from the other command's output. For instance, this is what the output looks like if the log message is empty (contains duplicate Signed-off-by lines).
(1.) Aborting commit due to empty commit message. (Duplicate Signed-off-by lines.) (2.) Could not amend commit after successfully picking fa1afe1... Some change (3.) This is most likely due to an empty commit message, or the pre-commit hook
failed. If the pre-commit hook failed, you may need to resolve the issue before you are able to reword the commit.
Discard the third message.
It is true that a failed hook script might not output any diagnosis but then the generic message is not of much help either. Since this lack of information affects the built-in git commands for commit, merge and cherry-pick first, the solution would be to keep track of the failed hooks in their output so that the user knows which of her hooks require improvement.
--- git-rebase--interactive.sh | 3 --- 1 file changed, 3 deletions(-)
git-rebase--interactive.sh +0 -3
@@ -506,9 +506,6 @@ do_next () { do_pick $sha1 "$rest" output git commit --allow-empty --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} || { warn "Could not amend commit after successfully picking $sha1... $rest" - warn "This is most likely due to an empty commit message, or the pre-commit hook" - warn "failed. If the pre-commit hook failed, you may need to resolve the issue before" - warn "you are able to reword the commit." exit_with_patch $sha1 1 } record_in_rewritten $sha1
Signature · 1 line
2.0.1
PATCH v2 6/23 commit: allow disabling pre-commit and commit-msg separately
In reply to Fabian RuchIntroduce the git-commit command line options --no-pre-commit and --no-commit-msg to disable the pre-commit and commit-msg hooks, respectively. Make --no-verify a synonym for specifying both at the same time.
This change is motivated by an internal usage of git-commit in git-rebase--interactive to disable pre-commit while keeping commit-msg enabled when rewording a commit.
Make test_commit forward unknown options to git-commit instead of teaching it all possible options. In order to support leading double dashes in <message>, stop interpreting test_commit arguments following a -- argument as options. This wasn't a problem before because the first unknown option would be used as <message>.
Allow disabling tag creation to avoid name clashes when using test_commit with the same arguments several times from the same test suite. By default, test_commit tags successful commits using git-tag for easy reference. The --notag option skips this step.
Add tests.
--- Documentation/git-commit.txt | 8 ++++- builtin/commit.c | 32 ++++++++++++++--- t/t7503-pre-commit-hook.sh | 65 ++++++++++++++++++++++++++++----- t/t7504-commit-msg-hook.sh | 85 ++++++++++++++++++++++++++++++++++---------- t/test-lib-functions.sh | 23 ++++++++---- 5 files changed, 176 insertions(+), 37 deletions(-)
Documentation/git-commit.txt +7 -1
@@ -158,7 +158,7 @@ OPTIONS -n:: --no-verify:: - This option bypasses the pre-commit and commit-msg hooks. + A synonym for `--no-pre-commit --no-commit-msg`. See also linkgit:githooks[5]. --allow-empty::
@@ -238,6 +238,12 @@ You should understand the implications of rewriting history if you amend a commit that has already been published. (See the "RECOVERING FROM UPSTREAM REBASE" section in linkgit:git-rebase[1].) +--no-pre-commit:: + This option bypasses the pre-commit hook. + +--no-commit-msg:: + This option bypasses the commit-msg hook. + --no-post-rewrite:: Bypass the post-rewrite hook.
builtin/commit.c +28 -4
@@ -98,12 +98,27 @@ static char *edit_message, *use_message;static char *fixup_message, *squash_message;static int all, also, interactive, patch_interactive, only, amend, signoff;static int edit_flag =-1 ;/* unspecified */ -static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; +static int quiet, verbose, allow_empty, dry_run, renew_authorship;static int no_post_rewrite, allow_empty_message;static char *untracked_files_arg, *force_date, *ignore_submodule_arg;static char *sign_commit;/* +* The verify variable is interpreted as a bitmap of enabled commit +* verification hooks according to the legend below. +* +* By default, the pre-commit and commit-msg hooks are enabled. This +* is represented by both the PRE_COMMIT and COMMIT_MSG bits being +* set. +* +* The bitmap is changed through the command line options +* --no-verify, --no-pre-commit and --no-commit-msg. +*/ +#define PRE_COMMIT (1<<0) +#define COMMIT_MSG (1<<1) +static int verify =PRE_COMMIT |COMMIT_MSG ; + +/* * The default commit message cleanup mode will remove the lines * beginning with # (shell comments) and leading and trailing * whitespaces (empty lines or containing only whitespaces)
@@ -661,7 +676,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix,/* This checks and barfs if author is badly specified */ determine_author_info (author_ident); -if (!no_verify &&run_commit_hook (use_editor, index_file,"pre-commit" ,NULL )) +if (verify &PRE_COMMIT && +run_commit_hook (use_editor, index_file,"pre-commit" ,NULL ))return 0 ;if (squash_message) {
@@ -962,7 +978,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, } } -if (!no_verify && +if (verify &COMMIT_MSG &&run_commit_hook (use_editor, index_file,"commit-msg" ,git_path (commit_editmsg),NULL )) {return 0 ; }
@@ -1590,7 +1606,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix)OPT_BOOL (0 ,"interactive" , &interactive,N_ ("interactively add files" )),OPT_BOOL ('p' ,"patch" , &patch_interactive,N_ ("interactively add changes" )),OPT_BOOL ('o' ,"only" , &only,N_ ("commit only specified files" )), -OPT_BOOL ('n' ,"no-verify" , &no_verify,N_ ("bypass pre-commit hook" )), +OPT_NEGBIT ('n' ,"no-verify" , &verify, +N_ ("synonym for --no-pre-commit --no-commit-msg" ), +PRE_COMMIT |COMMIT_MSG ),OPT_BOOL (0 ,"dry-run" , &dry_run,N_ ("show what would be committed" )),OPT_SET_INT (0 ,"short" , &status_format,N_ ("show status concisely" ), STATUS_FORMAT_SHORT),
@@ -1603,6 +1621,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix)OPT_BOOL ('z' ,"null" , &s.null_termination,N_ ("terminate entries with NUL" )),OPT_BOOL (0 ,"amend" , &amend,N_ ("amend previous commit" )), +OPT_NEGBIT (0 ,"no-pre-commit" , &verify, +N_ ("bypass pre-commit hook" ), +PRE_COMMIT ), +OPT_NEGBIT (0 ,"no-commit-msg" , &verify, +N_ ("bypass commit-msg hook" ), +COMMIT_MSG ),OPT_BOOL (0 ,"no-post-rewrite" , &no_post_rewrite,N_ ("bypass post-rewrite hook" )), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, N_("mode"), N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, /* end commit contents options */
t/t7503-pre-commit-hook.sh +57 -8
105 lines not on this page · raw message
t/t7504-commit-msg-hook.sh +67 -18
164 lines not on this page · raw message
t/test-lib-functions.sh +17 -6
@@ -154,20 +154,28 @@ test_pause () { test_commit () { notick= && - signoff= && + notag= && + commit_opts= && while test $# != 0 do case "$1" in --notick) notick=yes ;; - --signoff) - signoff="$1" + --notag) + notag=yes + ;; + --) + shift && + break + ;; + -*) + commit_opts="$commit_opts $1" ;; *) break ;; - esac + esac && shift done && file=${2:-"$1.t"} &&
@@ -177,8 +185,11 @@ test_commit () { then test_tick fi && - git commit $signoff -m "$1" && - git tag "${4:-$1}" + git commit $commit_opts -m "$1" && + if test -z "$notag" + then + git tag "${4:-$1}" + fi } # Call test_merge with the arguments "<message> <commit>", where <commit>
Signature · 1 line
2.0.1
PATCH v2 7/23 rebase -i: squash skips commit-msg hook
In reply to Fabian RuchUsing the to-do list command squash the user can specify two or more commits and git-rebase creates one commit that introduces all their changes combined. The authorship for the created commit is taken from the first commit specified and the user can edit the log message. There is a variant of squash available named fixup which also takes the first log message without asking for user input.
While it is reasonable to not verify replayed changes twice or rejecting some other author's changes in her name, it is insufficient to not verify the user input used as log message in the case of squash. Specify the git-commit option --commit-msg when committing the squash result to execute the commit-msg hook and verify the new log message. For the same reasons the pre-commit hook is disabled in all replay modes, the commit-msg hook gets disabled in fixup mode.
Add tests. In addition to the existing test checking that the pre-commit hook is disabled when simply picking a commit, provide a test checking that the commit-msg hook is disabled as well.
--- git-rebase--interactive.sh | 2 +- t/t3404-rebase-interactive.sh | 78 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-)
git-rebase--interactive.sh +1 -1
@@ -562,7 +562,7 @@ do_next () { else cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit rm -f "$GIT_DIR"/MERGE_MSG - do_with_author output git commit --allow-empty --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \ + do_with_author output git commit --allow-empty --amend --no-pre-commit -F "$GIT_DIR"/SQUASH_MSG -e \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest" fi
t/t3404-rebase-interactive.sh +78 -0
84 lines not on this page · raw message
Signature · 1 line
2.0.1
PATCH v2 8/23 rebase -i: reword executes pre-commit hook on interim commit
In reply to Fabian RuchThe to-do list command reword replays a commit like pick but lets the user also edit the commit's log message. This happens in two steps. Firstly, the named commit is cherry-picked. Secondly, the commit created in the first step is amended using an unchanged index to edit the log message. The pre-commit hook is meant to verify the changes introduced by a commit (for instance, catching inserted trailing white space). Since the committed tree is not changed between the two steps and we do not verify rebased patches, do not execute the pre-commit hook in the second step.
Specify the git-commit option --no-pre-commit to disable the pre-commit hook when editing the log message. The commit-msg hook will still be executed to verify the edited commit log message. As before, if the hook finds the new log message ill-formatted, the rebase will be interrupted with the unchanged commit replayed and the new log message in $GIT_DIR/COMMIT_EDITMSG.
Add tests.
--- git-rebase--interactive.sh | 2 +- t/t3404-rebase-interactive.sh | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-)
git-rebase--interactive.sh +1 -1
@@ -504,7 +504,7 @@ do_next () { mark_action_done do_pick $sha1 "$rest" - output git commit --allow-empty --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} || { + output git commit --allow-empty --amend --no-post-rewrite --no-pre-commit ${gpg_sign_opt:+"$gpg_sign_opt"} || { warn "Could not amend commit after successfully picking $sha1... $rest" exit_with_patch $sha1 1 }
t/t3404-rebase-interactive.sh +14 -0
@@ -672,6 +672,13 @@ test_expect_success 'setup failing pre-commit' ' test_commit --no-verify pre-commit-violated-2 ' +test_expect_success 'reword a commit violating pre-commit' ' + git checkout -b reword-violating-pre-commit violating-pre-commit && + test_when_finished reset_rebase && + set_fake_editor && + env FAKE_LINES="reword 1" git rebase -i master +' + test_expect_success 'squash a commit violating pre-commit' ' git checkout -b squash-violating-pre-commit violating-pre-commit && test_when_finished reset_rebase &&
@@ -715,6 +722,13 @@ test_expect_success 'rebase a commit violating commit-msg' ' FAKE_LINES="1" git rebase -i master ' +test_expect_success 'reword a commit violating commit-msg' ' + git checkout -b reword-violating-commit-msg violating-commit-msg && + test_when_finished reset_rebase && + set_fake_editor && + test_must_fail env FAKE_LINES="reword 1" git rebase -i master +' + test_expect_success 'squash a commit violating commit-msg' ' git checkout -b squash-violating-commit-msg violating-commit-msg && set_fake_editor &&
Signature · 1 line
2.0.1
Re: [PATCH v2 08/23] rebase -i: reword executes pre-commit hook on interim commit
Quoted 1 line · Fabian Ruch
Subject: Re: [PATCH v2 08/23] rebase -i: reword executes pre-commit hook on interim commit
I think the change makes sense, but can you reword the subjects that it describes the state after the commit (i.e. what you are doing), instead of before the commit?
Signature · 2 lines
Thomas Rast [email protected]
Re: [PATCH v2 08/23] rebase -i: reword executes pre-commit hook on interim commit
Hi Thomas,
Quoted 6 lines · Thomas Rast
Quoted 1 line · Fabian Ruch
Subject: Re: [PATCH v2 08/23] rebase -i: reword executes pre-commit hook on interim commit
I think the change makes sense, but can you reword the subjects that it describes the state after the commit (i.e. what you are doing), instead of before the commit?
The log message subject now reads as follows:
Quoted 1 line
rebase -i: do not verify reworded patches using pre-commit
Fabian
Re: [PATCH v2 08/23] rebase -i: reword executes pre-commit hook on interim commit
Quoted 4 lines · Fabian Ruch
Hi Thomas,
Quoted 1 line · Thomas Rast
Fabian Ruch <[email protected]> writes:
Quoted 10 lines
Quoted 6 lines
Quoted 2 lines
Subject: Re: [PATCH v2 08/23] rebase -i: reword executes pre-commit hook on interim commit
I think the change makes sense, but can you reword the subjects that it describes the state after the commit (i.e. what you are doing), instead of before the commit?
The log message subject now reads as follows:
Quoted 1 line
rebase -i: do not verify reworded patches using pre-commit
Excellent wording, thanks!
Signature · 2 lines
Thomas Rast [email protected]
PATCH v2 9/23 rebase -i: teach do_pick the option --edit
In reply to Fabian Ruchdo_pick is the git-cherry-pick wrapper in git-rebase--interactive that is used to implement the to-do list command pick. To cater for the different pick behaviours (like reword), do_pick accepts several options not only from the git-cherry-pick but also the git-commit interface. Add the common option --edit to let the user edit the log message of the named commit.
Loop over $@ to parse the do_pick arguments. Assign the local variable edit if one of the options is --edit so that the remainder of do_pick can easily check whether the client code asked to edit the commit message. If one of the options is unknown, mention it on the console and die. Break the loop on the first non-option and do some sanity checking to ensure that there exactly two non-options, which are interpreted by the remainder as <commit> and <title> like before.
do_pick ought to act as a wrapper around cherry-pick. Unfortunately, it cannot just forward --edit to the cherry-pick command line. The assembled command line is executed within a command substitution for controlling the verbosity of rebase--interactive. Passing --edit would either hang the terminal or clutter the substituted command output with control sequences. Execute the reword code from do_next instead if the option --edit is specified.
--- git-rebase--interactive.sh | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+)
git-rebase--interactive.sh +43 -0
@@ -461,7 +461,42 @@ record_in_rewritten() { esac } +# Apply the changes introduced by the given commit to the current head. +# +# do_pick [--edit] <commit> <title> +# +# Wrapper around git-cherry-pick. +# +# -e, --edit +# After picking <commit>, open an editor and let the user edit the +# commit message. The editor contents becomes the commit message of +# the new head. This creates a fresh commit. +# +# <commit> +# The commit to cherry-pick. +# +# <title> +# The commit message title of <commit>. Used for information +# purposes only. do_pick () { + edit= + while test $# -gt 0 + do + case "$1" in + -e|--edit) + edit=y + ;; + -*) + die "do_pick: unrecognized option -- $1" + ;; + *) + break + ;; + esac + shift + done + test $# -ne 2 && die "do_pick: wrong number of arguments" + if test "$(git rev-parse HEAD)" = "$squash_onto" then # Set the correct commit message and author info on the
@@ -483,6 +518,14 @@ do_pick () { pick_one $1 || die_with_patch $1 "Could not apply $1... $2" fi + + if test -n "$edit" + then + output git commit --allow-empty --amend --no-post-rewrite --no-pre-commit ${gpg_sign_opt:+"$gpg_sign_opt"} || { + warn "Could not amend commit after successfully picking $1... $2" + exit_with_patch $1 1 + } + fi } do_next () {
Signature · 1 line
2.0.1
PATCH v2 10/23 rebase -i: implement reword in terms of do_pick
In reply to Fabian RuchThe to-do list command reword replays a commit like pick but lets the user also edit the commit's log message. If one thinks of pick entries as scheduled cherry-pick command lines, then reword becomes an alias for the command line cherry-pick --edit. The porcelain rebase--interactive defines a function do_pick for processing the pick entries on to-do lists. Reimplement reword in terms of do_pick --edit.
If the user picks a commit using the to-do list line
reword fa1afe1 Some change
execute the command do_pick --edit fa1afe1 "Some change" which carries out exactly the same steps as the case arm for reword in do_next so far.
--- git-rebase--interactive.sh | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-)
git-rebase--interactive.sh +1 -5
@@ -546,11 +546,7 @@ do_next () { comment_for_reflog reword mark_action_done - do_pick $sha1 "$rest" - output git commit --allow-empty --amend --no-post-rewrite --no-pre-commit ${gpg_sign_opt:+"$gpg_sign_opt"} || { - warn "Could not amend commit after successfully picking $sha1... $rest" - exit_with_patch $sha1 1 - } + do_pick --edit $sha1 "$rest" record_in_rewritten $sha1 ;; edit|e)
Signature · 1 line
2.0.1
PATCH v2 11/23 rebase -i: log the replay of root commits
In reply to Fabian RuchThe command line used to recreate root commits specifies the option -q which suppresses the commit summary message. However, git-rebase--interactive tends to tell the user about the commits it creates in the final history, if she wishes (cf. command line option --verbose). The code parts handling non-root commits and squash commits all output commit summary messages. Do not make the replay of root commits an exception. Remove the option to make the report of the rebased history complete. Do not forget to wrap the git-commit command line in output so that the summary is shown if git-rebase is called with the --verbose option but suppressed otherwise.
It is OK that the commit summary is still suppressed when git-commit is used to initialize the authorship of the sentinel commit because this additional commit is an implementation detail hidden from the final history. The removed -q option was probably introduced as a copy-and-paste error stemming from that part of the root commit handling code.
--- git-rebase--interactive.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
git-rebase--interactive.sh +2 -2
@@ -510,8 +510,8 @@ do_pick () { git commit --allow-empty --allow-empty-message --amend \ --no-post-rewrite -n -q -C $1 && pick_one -n $1 && - git commit --allow-empty --allow-empty-message \ - --amend --no-post-rewrite -n -q -C $1 \ + output git commit --allow-empty --allow-empty-message \ + --amend --no-post-rewrite -n -C $1 \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_with_patch $1 "Could not apply $1... $2" else
Signature · 1 line
2.0.1
PATCH v2 12/23 rebase -i: root commits are replayed with an unnecessary option
In reply to Fabian RuchThe command line used to recreate root commits uses -C to suppress the log message editor. This is unnecessarily confusing, though, because that suppression is a secondary effect of the option. The main purpose of -C is to pull the metadata from another commit, but here we know that this is a noop, since we are amending a commit just created from the same data.
At the time -C was introduced, git-commit did not yet have a documented --no-edit, and this was a reasonable way to get the desired behavior. Switch it to use --no-edit to make the intended effect more obvious.
--- git-rebase--interactive.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
git-rebase--interactive.sh +1 -1
@@ -511,7 +511,7 @@ do_pick () { --no-post-rewrite -n -q -C $1 && pick_one -n $1 && output git commit --allow-empty --allow-empty-message \ - --amend --no-post-rewrite -n -C $1 \ + --amend --no-post-rewrite -n --no-edit \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_with_patch $1 "Could not apply $1... $2" else
Signature · 1 line
2.0.1
PATCH v2 13/23 rebase -i: commit only once when rewriting picks
In reply to Fabian RuchThe options passed to do_pick determine whether the picked commit will be rewritten or not. If the commit gets rewritten, because the user requested to edit the commit message for instance, let pick_one merely apply the changes introduced by the commit and do not commit the resulting tree yet. If the commit is replayed as is, leave it to pick_one to recreate the commit (possibly by fast-forwarding the head). This makes it easier to combine git-commit options like --edit and --amend in do_pick because git-cherry-pick does not support --amend.
In the case of --edit, do not exit_with_patch but assign rewrite to pick the changes with -n. If the pick conflicts, no commit is created which we would have to amend when continuing the rebase. To complete the pick after the conflicts are resolved the user just resumes with git rebase --continue.
git-commit lets the user edit the commit log message by default. We do not want that for the rewriting git-commit command line because the default behaviour of git-rebase is exactly the opposite. Pass --no-edit when rewriting a picked commit. An explicit --edit passed to do_pick (for instance, when reword is executed) enables the editor launch again. Similarly, pass --allow-empty-message unless the log message is edited.
If rebase--interactive is used to rebase a complete branch onto some head, rebase creates a sentinel commit that requires special treatment by do_pick. Do not finalize the pick here either because its commit message can be altered as for any other pick. Since the orphaned root commit gets a temporary parent, it is always rewritten. Safely use the rewrite infrastructure of do_pick to create the final commit.
--- git-rebase--interactive.sh | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-)
git-rebase--interactive.sh +22 -13
@@ -63,7 +63,8 @@ msgnum="$state_dir"/msgnum author_script="$state_dir"/author-script # When an "edit" rebase command is being processed, the SHA1 of the -# commit to be edited is recorded in this file. When "git rebase +# commit to be edited is recorded in this file. The same happens when +# rewriting a commit fails, for instance "reword". When "git rebase # --continue" is executed, if there are any staged changes then they # will be amended to the HEAD commit, but only provided the HEAD # commit is still the commit to be edited. When any other rebase
@@ -479,12 +480,17 @@ record_in_rewritten() { # The commit message title of <commit>. Used for information # purposes only. do_pick () { - edit= + allow_empty_message=y + rewrite= + rewrite_amend= + rewrite_edit= while test $# -gt 0 do case "$1" in -e|--edit) - edit=y + rewrite=y + rewrite_edit=y + allow_empty_message= ;; -*) die "do_pick: unrecognized option -- $1"
@@ -499,6 +505,10 @@ do_pick () { if test "$(git rev-parse HEAD)" = "$squash_onto" then + rewrite=y + rewrite_amend=y + git rev-parse --verify HEAD >"$amend" + # Set the correct commit message and author info on the # sentinel root before cherry-picking the original changes # without committing (-n). Finally, update the sentinel again
@@ -509,22 +519,21 @@ do_pick () { # rebase --continue. git commit --allow-empty --allow-empty-message --amend \ --no-post-rewrite -n -q -C $1 && - pick_one -n $1 && - output git commit --allow-empty --allow-empty-message \ - --amend --no-post-rewrite -n --no-edit \ - ${gpg_sign_opt:+"$gpg_sign_opt"} || + pick_one -n $1 || die_with_patch $1 "Could not apply $1... $2" else - pick_one $1 || + pick_one ${rewrite:+-n} $1 || die_with_patch $1 "Could not apply $1... $2" fi - if test -n "$edit" + if test -n "$rewrite" then - output git commit --allow-empty --amend --no-post-rewrite --no-pre-commit ${gpg_sign_opt:+"$gpg_sign_opt"} || { - warn "Could not amend commit after successfully picking $1... $2" - exit_with_patch $1 1 - } + output git commit --allow-empty --no-post-rewrite -n --no-edit \ + ${allow_empty_message:+--allow-empty-message} \ + ${rewrite_amend:+--amend} \ + ${rewrite_edit:+--edit --commit-msg} \ + ${gpg_sign_opt:+"$gpg_sign_opt"} || + die_with_patch $1 "Could not rewrite commit after successfully picking $1... $2" fi }
Signature · 1 line
2.0.1
PATCH v2 14/23 rebase -i: do not die in do_pick
In reply to Fabian RuchSince do_pick might be executed in a sub-shell (a modified author environment for instance), calling die in do_pick has no effect but exiting the sub-shell with a failure exit status. The git-rebase--interactive script is not terminated. Moreover, if do_pick is called while a squash or fixup is in effect, die_with_patch will discard $squash_msg as commit message. Lastly, after a die in do_pick do_next has no chance to reschedule tasks that failed before changes could be applied.
Indicate an error in do_pick using return statements and properly kill the script at the call sites. Although possible in principle, the issued error messages are no more indicating whether do_pick failed while applying or while committing the changes. This reduces code complexity at the call site and does not matter from a user's point of view because a glance at the index reveals whether there are conflicts or not and in-depth troubleshooting is still possible using the --verbose option.
Remove the commit message title argument from do_pick's interface, which has become unused.
--- git-rebase--interactive.sh | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-)
git-rebase--interactive.sh +19 -15
@@ -464,7 +464,7 @@ record_in_rewritten() { # Apply the changes introduced by the given commit to the current head. # -# do_pick [--edit] <commit> <title> +# do_pick [--edit] <commit> # # Wrapper around git-cherry-pick. #
@@ -476,9 +476,11 @@ record_in_rewritten() { # <commit> # The commit to cherry-pick. # -# <title> -# The commit message title of <commit>. Used for information -# purposes only. +# The return value is 1 if applying the changes resulted in a conflict +# and 2 if the specified arguments were incorrect. If the changes could +# be applied successfully but creating the commit failed, a value +# greater than 2 is returned. No commit is created in either case and +# the index is left with the (conflicting) changes in place. do_pick () { allow_empty_message=y rewrite=
@@ -493,7 +495,8 @@ do_pick () { allow_empty_message= ;; -*) - die "do_pick: unrecognized option -- $1" + warn "do_pick: unrecognized option -- $1" + return 2 ;; *) break
@@ -501,7 +504,11 @@ do_pick () { esac shift done - test $# -ne 2 && die "do_pick: wrong number of arguments" + if test $# -ne 1 + then + warn "do_pick: wrong number of arguments" + return 2 + fi if test "$(git rev-parse HEAD)" = "$squash_onto" then
@@ -519,11 +526,9 @@ do_pick () { # rebase --continue. git commit --allow-empty --allow-empty-message --amend \ --no-post-rewrite -n -q -C $1 && - pick_one -n $1 || - die_with_patch $1 "Could not apply $1... $2" + pick_one -n $1 || return 1 else - pick_one ${rewrite:+-n} $1 || - die_with_patch $1 "Could not apply $1... $2" + pick_one ${rewrite:+-n} $1 || return 1 fi if test -n "$rewrite"
@@ -532,8 +537,7 @@ do_pick () { ${allow_empty_message:+--allow-empty-message} \ ${rewrite_amend:+--amend} \ ${rewrite_edit:+--edit --commit-msg} \ - ${gpg_sign_opt:+"$gpg_sign_opt"} || - die_with_patch $1 "Could not rewrite commit after successfully picking $1... $2" + ${gpg_sign_opt:+"$gpg_sign_opt"} || return 3 fi }
@@ -548,21 +552,21 @@ do_next () { comment_for_reflog pick mark_action_done - do_pick $sha1 "$rest" + do_pick $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" record_in_rewritten $sha1 ;; reword|r) comment_for_reflog reword mark_action_done - do_pick --edit $sha1 "$rest" + do_pick --edit $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" record_in_rewritten $sha1 ;; edit|e) comment_for_reflog edit mark_action_done - do_pick $sha1 "$rest" + do_pick $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" warn "Stopped at $sha1... $rest" exit_with_patch $sha1 0 ;;
Signature · 1 line
2.0.1
PATCH v2 15/23 rebase -i: teach do_pick the option --amend
In reply to Fabian Ruchdo_pick is the git-cherry-pick wrapper in git-rebase--interactive that is used to implement the to-do list commands pick, reword and edit. To cater for the different pick behaviours (like squash), do_pick accepts several options not only from the git-cherry-pick but also the git-commit interface.
Add the option --amend from the git-commit interface to the options pool of do_pick. It creates a new commit for the changes introduced by the picked commit and the previous one. The previous commit is then replaced with the new commit. If no other options are specified, the log message of the previous commit is used.
Be careful when --amend is used to pick a root commit because HEAD might point to the sentinel commit but there is still nothing to amend. Be sure to initialize amend so that commits are squashed even when git-rebase--interactive is interrupted for resolving conflicts. It is not a mistake to do the initialization regardless of any conflicts because amend is always cleared before the next to-do item is processed.
--- git-rebase--interactive.sh | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-)
git-rebase--interactive.sh +17 -1
@@ -464,10 +464,16 @@ record_in_rewritten() { # Apply the changes introduced by the given commit to the current head. # -# do_pick [--edit] <commit> +# do_pick [--amend] [--edit] <commit> # # Wrapper around git-cherry-pick. # +# --amend +# After picking <commit>, replace the current head commit with a new +# commit that also introduces the changes of <commit>. +# +# _This is not a git-cherry-pick option._ +# # -e, --edit # After picking <commit>, open an editor and let the user edit the # commit message. The editor contents becomes the commit message of
@@ -489,6 +495,16 @@ do_pick () { while test $# -gt 0 do case "$1" in + --amend) + if test "$(git rev-parse HEAD)" = "$squash_onto" || ! git rev-parse -q --verify HEAD >/dev/null + then + warn "do_pick: nothing to amend" + return 2 + fi + rewrite=y + rewrite_amend=y + git rev-parse --verify HEAD >"$amend" + ;; -e|--edit) rewrite=y rewrite_edit=y
Signature · 1 line
2.0.1
PATCH v2 16/23 rebase -i: teach do_pick the option --file
In reply to Fabian Ruchdo_pick is the git-cherry-pick wrapper in git-rebase--interactive that is used to implement the to-do list command pick, reword and edit. To cater for the different pick behaviours (like squash), do_pick accepts several options not only from the git-cherry-pick but also the git-commit interface.
Add the option --file from the git-commit interface to the options pool of do_pick. It expects an argument itself which is interpreted as a file path and takes the commit message from the given file. If --file is passed to do_pick, assign the given file path to the local variable rewrite_message and relay the option
--file "$rewrite_message"
to the git-commit command line which creates the commit.
--- git-rebase--interactive.sh | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-)
git-rebase--interactive.sh +19 -1
@@ -464,7 +464,7 @@ record_in_rewritten() { # Apply the changes introduced by the given commit to the current head. # -# do_pick [--amend] [--edit] <commit> +# do_pick [--amend] [--file <file>] [--edit] <commit> # # Wrapper around git-cherry-pick. #
@@ -474,6 +474,12 @@ record_in_rewritten() { # # _This is not a git-cherry-pick option._ # +# -F <file>, --file <file> +# Take the commit message from the given file. This creates a fresh +# commit. +# +# _This is not a git-cherry-pick option._ +# # -e, --edit # After picking <commit>, open an editor and let the user edit the # commit message. The editor contents becomes the commit message of
@@ -492,6 +498,7 @@ do_pick () { rewrite= rewrite_amend= rewrite_edit= + rewrite_message= while test $# -gt 0 do case "$1" in
@@ -505,6 +512,16 @@ do_pick () { rewrite_amend=y git rev-parse --verify HEAD >"$amend" ;; + -F|--file) + if test $# -eq 0 + then + warn "do_pick: option --file specified but no <file> given" + return 2 + fi + rewrite=y + rewrite_message=$2 + shift + ;; -e|--edit) rewrite=y rewrite_edit=y
@@ -553,6 +570,7 @@ do_pick () { ${allow_empty_message:+--allow-empty-message} \ ${rewrite_amend:+--amend} \ ${rewrite_edit:+--edit --commit-msg} \ + ${rewrite_message:+--file "$rewrite_message"} \ ${gpg_sign_opt:+"$gpg_sign_opt"} || return 3 fi }
Signature · 1 line
2.0.1
PATCH v2 17/23 rebase -i: prepare for squash in terms of do_pick --amend
In reply to Fabian RuchRewrite squash and fixup handling in do_next using the sequence
pick_one
commit
in order to test the correctness of a single do_squash or parameterised do_pick and make the subsequent patch reimplementing squash in terms of such a single function more readable.
Do not call rm -f "$GIT_DIR"/MERGE_MSG since it has no effect on the state after git-rebase--interactive terminates. The option -F makes git-commit ignore MERGE_MSG for the log message. If git-commit succeeds, MERGE_MSG is removed, and if it fails, MERGE_MSG is overwritten by the error sequence die_failed_squash. In summary, removing MERGE_MSG neither influences the squash commit message nor the file state after git-commit returns.
Moreover, pick_one ignores $GIT_DIR/SQUASH_MSG and does not touch $squash_msg so that it is correct to execute pick_one immediately before git-commit.
Might be squashed into the subsequent commit.
--- git-rebase--interactive.sh | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-)
git-rebase--interactive.sh +15 -6
@@ -623,15 +623,15 @@ do_next () { author_script_content=$(get_author_ident_from_commit HEAD) echo "$author_script_content" > "$author_script" eval "$author_script_content" - if ! pick_one -n $sha1 - then - git rev-parse --verify HEAD >"$amend" - die_failed_squash $sha1 "$rest" - fi case "$(peek_next_command)" in squash|s|fixup|f) # This is an intermediate commit; its message will only be # used in case of trouble. So use the long version: + if ! pick_one -n $sha1 + then + git rev-parse --verify HEAD >"$amend" + die_failed_squash $sha1 "Could not apply $sha1... $rest" + fi do_with_author output git commit --allow-empty-message --allow-empty \ --amend --no-verify -F "$squash_msg" \ ${gpg_sign_opt:+"$gpg_sign_opt"} ||
@@ -641,13 +641,22 @@ do_next () { # This is the final command of this squash/fixup group if test -f "$fixup_msg" then + if ! pick_one -n $sha1 + then + git rev-parse --verify HEAD >"$amend" + die_failed_squash $sha1 "Could not apply $sha1... $rest" + fi do_with_author output git commit --allow-empty-message --allow-empty \ --amend --no-verify -F "$fixup_msg" \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest" else cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit - rm -f "$GIT_DIR"/MERGE_MSG + if ! pick_one -n $sha1 + then + git rev-parse --verify HEAD >"$amend" + die_failed_squash $sha1 "Could not apply $sha1... $rest" + fi do_with_author output git commit --allow-empty --amend --no-pre-commit -F "$GIT_DIR"/SQUASH_MSG -e \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest"
Signature · 1 line
2.0.1
PATCH v2 18/23 rebase -i: implement squash in terms of do_pick
In reply to Fabian RuchThe to-do list command squash and its close relative fixup replay the changes of a commit like pick but do not recreate the commit. Instead they replace the previous commit with a new commit that also introduces the changes of the squashed commit. This is roughly like cherry-picking without committing and using git-commit to amend the previous commit.
The to-do list
pick a Some changes
squash b Some more changes
gets translated into the sequence of git commands
git cherry-pick a
git cherry-pick -n b
git commit --amend
and if git-cherry-pick supported --amend this would look even more like the to-do list it is based on
git cherry-pick a
git cherry-pick --amend b.
Since do_pick takes care of pick entries and the above suggests squash as an alias for pick --amend, reimplement squash in terms of do_pick --amend. Introduce $squash_msg as the commit message via the --file option. When the last commit of a squash series is processed, the user is asked to review the log message. Pass --edit as additional option in this case. The only difference in the options passed to git-commit and do_pick is the omitted --no-verify. However, do_pick does not execute the verification hooks anyway because it solely replays commits and assumes that they have been verified before.
--- git-rebase--interactive.sh | 32 ++++++-------------------------- 1 file changed, 6 insertions(+), 26 deletions(-)
git-rebase--interactive.sh +6 -26
@@ -627,39 +627,19 @@ do_next () { squash|s|fixup|f) # This is an intermediate commit; its message will only be # used in case of trouble. So use the long version: - if ! pick_one -n $sha1 - then - git rev-parse --verify HEAD >"$amend" - die_failed_squash $sha1 "Could not apply $sha1... $rest" - fi - do_with_author output git commit --allow-empty-message --allow-empty \ - --amend --no-verify -F "$squash_msg" \ - ${gpg_sign_opt:+"$gpg_sign_opt"} || - die_failed_squash $sha1 "$rest" + do_with_author do_pick --amend -F "$squash_msg" $sha1 \ + || die_failed_squash $sha1 "$rest" ;; *) # This is the final command of this squash/fixup group if test -f "$fixup_msg" then - if ! pick_one -n $sha1 - then - git rev-parse --verify HEAD >"$amend" - die_failed_squash $sha1 "Could not apply $sha1... $rest" - fi - do_with_author output git commit --allow-empty-message --allow-empty \ - --amend --no-verify -F "$fixup_msg" \ - ${gpg_sign_opt:+"$gpg_sign_opt"} || - die_failed_squash $sha1 "$rest" + do_with_author do_pick --amend -F "$fixup_msg" $sha1 \ + || die_failed_squash $sha1 "$rest" else cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit - if ! pick_one -n $sha1 - then - git rev-parse --verify HEAD >"$amend" - die_failed_squash $sha1 "Could not apply $sha1... $rest" - fi - do_with_author output git commit --allow-empty --amend --no-pre-commit -F "$GIT_DIR"/SQUASH_MSG -e \ - ${gpg_sign_opt:+"$gpg_sign_opt"} || - die_failed_squash $sha1 "$rest" + do_with_author do_pick --amend -F "$GIT_DIR"/SQUASH_MSG -e $sha1 \ + || die_failed_squash $sha1 "$rest" fi rm -f "$squash_msg" "$fixup_msg" if test -z "$keep_empty" && is_empty_commit HEAD
Signature · 1 line
2.0.1
PATCH v2 19/23 rebase -i: explicitly distinguish replay commands and exec tasks
In reply to Fabian RuchThere are two kinds of to-do list commands available. One kind replays a commit (pick, reword, edit, squash and fixup that is) and the other executes a shell command (exec). We will call the first kind replay commands.
The two kinds of tasks are scheduled using different line formats. Replay commands expect a commit hash argument following the command name and exec concatenates all arguments to assemble a command line.
Adhere to the distinction of formats by not trying to parse the sha1 field unless we are dealing with a replay command. Move the replay command handling code to a new function do_replay which assumes the first argument to be a commit hash and make no more such assumptions in do_next.
--- git-rebase--interactive.sh | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-)
git-rebase--interactive.sh +28 -14
@@ -575,13 +575,12 @@ do_pick () { fi } -do_next () { - rm -f "$msg" "$author_script" "$amend" || exit - read -r command sha1 rest < "$todo" +do_replay () { + command=$1 + sha1=$2 + rest=$3 + case "$command" in - "$comment_char"*|''|noop) - mark_action_done - ;; pick|p) comment_for_reflog pick
@@ -660,6 +659,28 @@ do_next () { esac record_in_rewritten $sha1 ;; + *) + read -r command <"$todo" + warn "Unknown command: $command" + fixtodo="Please fix this using 'git rebase --edit-todo'." + if git rev-parse --verify -q "$sha1" >/dev/null + then + die_with_patch $sha1 "$fixtodo" + else + die "$fixtodo" + fi + ;; + esac +} + +do_next () { + rm -f "$msg" "$author_script" "$amend" || exit + read -r command sha1 rest <"$todo" + + case "$command" in + "$comment_char"*|''|noop) + mark_action_done + ;; x|"exec") read -r command rest < "$todo" mark_action_done
@@ -699,14 +720,7 @@ do_next () { fi ;; *) - warn "Unknown command: $command $sha1 $rest" - fixtodo="Please fix this using 'git rebase --edit-todo'." - if git rev-parse --verify -q "$sha1" >/dev/null - then - die_with_patch $sha1 "$fixtodo" - else - die "$fixtodo" - fi + do_replay $command $sha1 "$rest" ;; esac test -s "$todo" && return
Signature · 1 line
2.0.1
PATCH v2 20/23 rebase -i: parse to-do list command line options
In reply to Fabian RuchRead in to-do list lines as
command args
instead of
command sha1 rest
so that to-do list command lines can specify additional arguments apart from the commit hash and the log message title, which become the non-options in args. Loop over args, put all options (an argument beginning with a dash) in opts, stop the loop on the first non-option and assign it to sha1. The loop does not know the options it parses so that options that take an argument themselves are not supported at the moment. Neither are options that contain spaces because the shell expansion of args in do_next interprets white space characters as argument separator, that is a command line like
pick --author "A U Thor" fa1afe1 Some change
is parsed as the pick command
pick --author
and the commit hash
"A
which obviously results in an unknown revision error. For the sake of completeness, in the example above the message title variable rest is assigned the string 'U Thor" fa1afe1 Some change' (without the single quotes).
Print an error message for unknown or unsupported command line options, which means an error for all specified options at the moment. Cleanly break the do_next loop by assigning the special value 'unknown' to the local variable command, which triggers the unknown command case in do_cmd.
The to-do list is also parsed when the commit hashes are translated between long and short format before and after the to-do list is edited. Apply the same procedure as in do_cmd with the exception that we only care about where the options stop and the commit hash begins. Do not reject any options when transforming the commit hashes.
--- git-rebase--interactive.sh | 49 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 8 deletions(-)
git-rebase--interactive.sh +41 -8
@@ -577,8 +577,26 @@ do_pick () { do_replay () { command=$1 - sha1=$2 - rest=$3 + shift + + opts= + while test $# -gt 0 + do + case "$1" in + -*) + warn "Unknown option: $1" + command=unknown + ;; + *) + break + ;; + esac + opts="$opts $(git rev-parse --sq-quote "$1")" + shift + done + sha1=$1 + shift + rest=$* case "$command" in pick|p)
@@ -675,7 +693,7 @@ do_replay () { do_next () { rm -f "$msg" "$author_script" "$amend" || exit - read -r command sha1 rest <"$todo" + read -r command args <"$todo" case "$command" in "$comment_char"*|''|noop)
@@ -720,7 +738,7 @@ do_next () { fi ;; *) - do_replay $command $sha1 "$rest" + do_replay $command $args ;; esac test -s "$todo" && return
@@ -800,19 +818,34 @@ skip_unnecessary_picks () { } transform_todo_ids () { - while read -r command rest + while read -r command args do case "$command" in "$comment_char"* | exec) # Be careful for oddball commands like 'exec' # that do not have a SHA-1 at the beginning of $rest. + newargs=\ $args ;; *) - sha1=$(git rev-parse --verify --quiet "$@" ${rest%% *}) && - rest="$sha1 ${rest#* }" + newargs= + sha1= + for arg in $args + do + case "$arg" in + -*) + newargs="$newargs $arg" + ;; + *) + test -z "$sha1" && + sha1=$(git rev-parse --verify --quiet "$@" $arg) && + arg=$sha1 + newargs="$newargs $arg" + ;; + esac + done ;; esac - printf '%s\n' "$command${rest:+ }$rest" + printf '%s\n' "$command$newargs" done <"$todo" >"$todo.new" && mv -f "$todo.new" "$todo" }
Signature · 1 line
2.0.1
Re: [PATCH v2 20/23] rebase -i: parse to-do list command line options
Fabian Ruch <[email protected]> writes:
[...]
Quoted 11 lines
are not supported at the moment. Neither are options that contain spaces because the shell expansion of
argsindo_nextinterprets white space characters as argument separator, that is a command line likepick --author "A U Thor" fa1afe1 Some change
is parsed as the pick command
pick --author
Quoted 8 lines
and the commit hash
"Awhich obviously results in an unknown revision error. For the sake of completeness, in the example above the message title variable
restis assigned the string 'U Thor" fa1afe1 Some change' (without the single quotes).
You could probably trim down the non-example a bit and instead give an example :-)
Quoted 3 lines
Print an error message for unknown or unsupported command line options, which means an error for all specified options at the moment.
Can you add a test that verifies we catch an obvious unknown option (such as --unknown-option)?
Quoted 3 lines
Cleanly break the
do_nextloop by assigning the special value 'unknown' to the local variablecommand, which triggers the unknown command case indo_cmd.
[...]
Quoted 7 lines
do_replay() {git-rebase--interactive.sh +3
-2command=$1 - sha1=$2 - rest=$3 + shift + + opts=
git-rebase--interactive.sh do_pick () { · line 592
Quoted 10 lines
git-rebase--interactive.sh +10
-0+ while test $# -gt 0 + do + case "$1" in + -*) + warn "Unknown option: $1" + command=unknown + ;; + *) + break + ;;
This seems a rather hacky solution to me. Doesn't this now print
warning: Unknown option: --unknown-option warning: Unknown command: pick --unknown-option
?
It shouldn't claim the command is unknown if the command itself was valid.
Also, you speak of do_cmd above, but the unknown command handling seems to be part of do_replay?
Signature · 2 lines
Thomas Rast [email protected]
Re: [PATCH v2 20/23] rebase -i: parse to-do list command line options
Hi Thomas,
an updated patch is attached below.
Quoted 16 lines · Thomas Rast writes:> Fabian Ruch <[email protected]> writes:
[...]
Quoted 15 lines
are not supported at the moment. Neither are options that contain spaces because the shell expansion of
argsindo_nextinterprets white space characters as argument separator, that is a command line likepick --author "A U Thor" fa1afe1 Some change
is parsed as the pick command
pick --authorand the commit hash
"A
Quoted 7 lines
Quoted 4 lines
which obviously results in an unknown revision error. For the sake of completeness, in the example above the message title variable
restis assigned the string 'U Thor" fa1afe1 Some change' (without the single quotes).You could probably trim down the non-example a bit and instead give an example :-)
Done. The example "reword --signoff" is substituted for the non-example and used for an error message example as well. I hope that is not confusing.
Quoted 6 lines
Quoted 3 lines
Print an error message for unknown or unsupported command line options, which means an error for all specified options at the moment.
Can you add a test that verifies we catch an obvious unknown option (such as --unknown-option)?
Done. The test checks that git-rebase--interactive fails to execute 'pick --unknown-option' and that the rebase can be resumed after removing the --unknown-option part.
Quoted 24 lines
Quoted 3 lines
Cleanly break the
do_nextloop by assigning the special value 'unknown' to the local variablecommand, which triggers the unknown command case indo_cmd.[...]
Quoted 7 lines
do_replay() {(quoted diff) +3
-2command=$1 - sha1=$2 - rest=$3 + shift + + opts=Review
Quoted 10 lines
(quoted diff) +10
-0+ while test $# -gt 0 + do + case "$1" in + -*) + warn "Unknown option: $1" + command=unknown + ;; + *) + break + ;;This seems a rather hacky solution to me. Doesn't this now print
Quoted 7 lines
warning: Unknown option: --unknown-option warning: Unknown command: pick --unknown-option?
It shouldn't claim the command is unknown if the command itself was valid.
OK. "do_replay" now first checks for unknown commands and exits immediately if that is the case, ignoring any options specified.
Quoted 2 lines
Also, you speak of
do_cmdabove, but the unknown command handling seems to be part ofdo_replay?
Fixed.
Fabian
-- >8 --
Subject: rebase -i: parse to-do list command line options
Read in to-do list lines as
command args
instead of
command sha1 rest
so that to-do list command lines can specify additional arguments apart from the commit hash and the log message title, which become the non-options in args. Loop over args, put all options (an argument beginning with a dash) in opts, stop the loop on the first non-option and assign it to sha1. For instance, the to-do list
reword --signoff fa1afe1 Some change
is parsed as command=reword, opts= '--signoff' (including the single quotes and the space for evaluation and concatenation of options), sha1=fa1afel and rest=Some change. The loop does not know the options it parses so that options that take an argument themselves are not supported at the moment. Neither are options that contain spaces because the shell expansion of args in do_next interprets white space characters as argument separator.
Print an error message for unknown or unsupported command line options, which means an error for all specified options at the moment. Cleanly break the do_next loop by assigning a reason to the local variable malformed, which triggers the unknown command code in do_replay. Move the error code to the beginning of do_replay so that unknown commands are reported before bad options are as only the first error we come across is reported. For instance, the to-do list from above produces the error
Unknown 'reword' option: --signoff Please fix this using 'git rebase --edit-todo'.
The to-do list is also parsed when the commit hashes are translated between long and short format before and after the to-do list is edited. Apply the same procedure as in do_replay with the exception that we only care about where the options stop and the commit hash begins. Do not reject any options when transforming the commit hashes.
Enable the specification of to-do list command line options in FAKE_LINES the same way this is accomplished for command lines passed to exec. Define a new fake_editor.sh that edits a static to-do list instead of the one passed as argument when invoked by git-rebase. Add a test case that checks that unknown options are refused and can be corrected using --edit-todo.
--- git-rebase--interactive.sh | 80 ++++++++++++++++++++++++++++++++---------- t/lib-rebase.sh | 20 +++++++++-- t/t3427-rebase-line-options.sh | 26 ++++++++++++++ 3 files changed, 105 insertions(+), 21 deletions(-) create mode 100755 t/t3427-rebase-line-options.sh
git-rebase--interactive.sh +61 -19
@@ -576,9 +576,47 @@ do_pick () { } do_replay () { + malformed= command=$1 - sha1=$2 - rest=$3 + shift + case "$command" in + pick|reword|edit|squash|fixup) + ;; + *) + read -r command <"$todo" + malformed="Unknown command: $command" + ;; + esac + + opts= + while test $# -gt 0 && test -z "$malformed" + do + case "$1" in + -*) + malformed="Unknown '$command' option: $1" + ;; + *) + break + ;; + esac + opts="$opts $(git rev-parse --sq-quote "$1")" + shift + done + sha1=$1 + shift + rest=$* + + if test -n "$malformed" + then + warn "$malformed" + fixtodo="Please fix this using 'git rebase --edit-todo'." + if git rev-parse --verify -q "$sha1" >/dev/null + then + die_with_patch $sha1 "$fixtodo" + else + die "$fixtodo" + fi + fi case "$command" in pick|p)
@@ -659,23 +697,12 @@ do_replay () { esac record_in_rewritten $sha1 ;; - *) - read -r command <"$todo" - warn "Unknown command: $command" - fixtodo="Please fix this using 'git rebase --edit-todo'." - if git rev-parse --verify -q "$sha1" >/dev/null - then - die_with_patch $sha1 "$fixtodo" - else - die "$fixtodo" - fi - ;; esac } do_next () { rm -f "$msg" "$author_script" "$amend" || exit - read -r command sha1 rest <"$todo" + read -r command args <"$todo" case "$command" in "$comment_char"*|''|noop)
@@ -720,7 +747,7 @@ do_next () { fi ;; *) - do_replay $command $sha1 "$rest" + do_replay $command $args ;; esac test -s "$todo" && return
@@ -800,19 +827,34 @@ skip_unnecessary_picks () { } transform_todo_ids () { - while read -r command rest + while read -r command args do case "$command" in "$comment_char"* | exec) # Be careful for oddball commands like 'exec' # that do not have a SHA-1 at the beginning of $rest. + newargs=\ $args ;; *) - sha1=$(git rev-parse --verify --quiet "$@" ${rest%% *}) && - rest="$sha1 ${rest#* }" + newargs= + sha1= + for arg in $args + do + case "$arg" in + -*) + newargs="$newargs $arg" + ;; + *) + test -z "$sha1" && + sha1=$(git rev-parse --verify --quiet "$@" $arg) && + arg=$sha1 + newargs="$newargs $arg" + ;; + esac + done ;; esac - printf '%s\n' "$command${rest:+ }$rest" + printf '%s\n' "$command$newargs" done <"$todo" >"$todo.new" && mv -f "$todo.new" "$todo" }
t/lib-rebase.sh +18 -2
@@ -46,8 +46,8 @@ set_fake_editor () { action=pick for line in $FAKE_LINES; do case $line in - squash|fixup|edit|reword) - action="$line";; + pick*|squash*|fixup*|edit*|reword*) + action=$(echo "$line" | sed 's/_/ /g');; exec*) echo "$line" | sed 's/_/ /g' >> "$1";; "#")
@@ -80,6 +80,22 @@ set_cat_todo_editor () { test_set_editor "$(pwd)/fake-editor.sh" } +# set_fixed_todo_editor takes a file path as argument and installs an +# editor script that, firstly, overwrites the file path argument with +# the one specified during installation and, secondly, calls +# fake-editor.sh for changing the contents as usual. This comes in +# handy if it is easier to change some fixed file instead of the one +# that will be passed when the editor is being invoked. + +set_fixed_todo_editor () { + set_fake_editor + write_script fake-editor-wrapper.sh <<-EOF + cp "$1" "\$1" + "$(pwd)"/fake-editor.sh "\$1" + EOF + test_set_editor "$(pwd)/fake-editor-wrapper.sh" +} + # checks that the revisions in "$2" represent a linear range with the # subjects in "$1" test_linear_range () {
t/t3427-rebase-line-options.sh new file +26 -0
@@ -0,0 +1,26 @@ +#!/bin/sh + +test_description='git rebase -i with line options' + +. ./test-lib.sh + +. "$TEST_DIRECTORY"/lib-rebase.sh + +test_expect_success 'Set up repository' ' + test_commit Initial && + test_commit Commit1 && + test_commit Commit2 +' + +test_expect_success 'Unknown option' ' + git checkout -b unknown-option master && + set_cat_todo_editor && + test_must_fail git rebase -i HEAD^ >todo && + set_fake_editor && + test_must_fail env FAKE_LINES="1 pick_--unknown-option 2" git rebase -i HEAD~2 && + set_fixed_todo_editor "$(pwd)"/todo && + git rebase --edit-todo && + git rebase --continue +' + +test_done
Signature · 1 line
2.0.1
PATCH v2 21/23 rebase -i: teach do_pick the option --reset-author
In reply to Fabian Ruchdo_pick is the git-cherry-pick wrapper in git-rebase--interactive that is used to implement many of the to-do list commands. Eventually, the complete do_pick interface will be exposed to the user in some form or another and those commands will become simple aliases for the do_pick options now used to implement them.
Add the git-commit option --reset-author to the options pool of do_pick. It rewrites the author date and name of the picked commit to match the committer date and name.
If --reset-author is passed to do_pick, set the rewrite flag and relay the option to the git-commit command line which creates the final commit. If --amend is not passed as well, the fresh authorship effect is achieved by the mere fact that we are creating a new commit.
--- git-rebase--interactive.sh | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-)
git-rebase--interactive.sh +22 -1
@@ -464,10 +464,18 @@ record_in_rewritten() { # Apply the changes introduced by the given commit to the current head. # -# do_pick [--amend] [--file <file>] [--edit] <commit> +# do_pick [--reset-author] [--amend] [--file <file>] [--edit] <commit> # # Wrapper around git-cherry-pick. # +# --reset-author +# Pretend the changes were made for the first time. Declare that the +# authorship of the resulting commit now belongs to the committer. +# This also renews the author timestamp. This creates a fresh +# commit. +# +# _This is not a git-cherry-pick option._ +# # --amend # After picking <commit>, replace the current head commit with a new # commit that also introduces the changes of <commit>.
@@ -502,6 +510,10 @@ do_pick () { while test $# -gt 0 do case "$1" in + --reset-author) + rewrite=y + rewrite_author=y + ;; --amend) if test "$(git rev-parse HEAD)" = "$squash_onto" || ! git rev-parse -q --verify HEAD >/dev/null then
@@ -564,6 +576,14 @@ do_pick () { pick_one ${rewrite:+-n} $1 || return 1 fi + if test -n "$rewrite_author" && test -z "$rewrite_amend" + then + # keep rewrite flag to create a new commit, rewrite + # without --reset-author though because it can only be + # used with -C, -c or --amend + rewrite_author= + fi + if test -n "$rewrite" then output git commit --allow-empty --no-post-rewrite -n --no-edit \
@@ -571,6 +591,7 @@ do_pick () { ${rewrite_amend:+--amend} \ ${rewrite_edit:+--edit --commit-msg} \ ${rewrite_message:+--file "$rewrite_message"} \ + ${rewrite_author:+--reset-author} \ ${gpg_sign_opt:+"$gpg_sign_opt"} || return 3 fi }
Signature · 1 line
2.0.1
PATCH v2 22/23 rebase -i: teach do_pick the option --signoff
In reply to Fabian Ruchdo_pick is the git-cherry-pick wrapper in git-rebase--interactive that is currently used to implement most of the to-do list commands and offers additional options that will eventually find their way onto to-do lists.
To extend the repertoire of available options, add the git-commit and git-cherry-pick option --signoff to the do_pick interface. It appends a Signed-off-by: line using the committer identity to the log message of the picked commit.
--- git-rebase--interactive.sh | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-)
git-rebase--interactive.sh +11 -1
@@ -464,10 +464,15 @@ record_in_rewritten() { # Apply the changes introduced by the given commit to the current head. # -# do_pick [--reset-author] [--amend] [--file <file>] [--edit] <commit> +# do_pick [--signoff] [--reset-author] [--amend] [--file <file>] +# [--edit] <commit> # # Wrapper around git-cherry-pick. # +# -s, --signoff +# Insert a Signed-off-by: line using the committer identity at the +# end of the commit log message. This creates a fresh commit. +# # --reset-author # Pretend the changes were made for the first time. Declare that the # authorship of the resulting commit now belongs to the committer.
@@ -510,6 +515,10 @@ do_pick () { while test $# -gt 0 do case "$1" in + -s|--signoff) + rewrite=y + rewrite_signoff=y + ;; --reset-author) rewrite=y rewrite_author=y
@@ -588,6 +597,7 @@ do_pick () { then output git commit --allow-empty --no-post-rewrite -n --no-edit \ ${allow_empty_message:+--allow-empty-message} \ + ${rewrite_signoff:+--signoff} \ ${rewrite_amend:+--amend} \ ${rewrite_edit:+--edit --commit-msg} \ ${rewrite_message:+--file "$rewrite_message"} \
Signature · 1 line
2.0.1
PATCH v2 23/23 rebase -i: enable options --signoff, --reset-author for pick, reword
In reply to Fabian Ruchpick and reword are atomic to-do list commands in the sense that they open a new task which is closed after the respective command is completed. squash and fixup are not atomic. They create a new task which is not completed until the last squash or fixup is processed.
Lift the general unknown option blockade for the pick and reword commands. If do_cmd comes across one of the options --signoff and --reset-author while parsing a to-do entry and the scheduled command is either pick or reword, relay the option to do_pick.
The do_pick options --gpg-sign and --file are not yet supported because do_cmd cannot handle option arguments and options with spaces at the moment. It is true that edit is one of the atomic commands but it displays hash information when the rebase is stopped and some options rewrite the picked commit which alters that information. squash and fixup still do not accept user options as the interplay of --reset-author and the author script are yet to be determined.
--- git-rebase--interactive.sh | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-)
git-rebase--interactive.sh +16 -3
@@ -614,6 +614,16 @@ do_replay () { while test $# -gt 0 do case "$1" in + --signoff|--reset-author) + case "$command" in + pick|reword) + ;; + *) + warn "Unsupported option: $1" + command=unknown + ;; + esac + ;; -*) warn "Unknown option: $1" command=unknown
@@ -634,21 +644,24 @@ do_replay () { comment_for_reflog pick mark_action_done - do_pick $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" + eval do_pick $opts $sha1 \ + || die_with_patch $sha1 "Could not apply $sha1... $rest" record_in_rewritten $sha1 ;; reword|r) comment_for_reflog reword mark_action_done - do_pick --edit $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" + eval do_pick --edit $opts $sha1 \ + || die_with_patch $sha1 "Could not apply $sha1... $rest" record_in_rewritten $sha1 ;; edit|e) comment_for_reflog edit mark_action_done - do_pick $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" + eval do_pick $opts $sha1 \ + || die_with_patch $sha1 "Could not apply $sha1... $rest" warn "Stopped at $sha1... $rest" exit_with_patch $sha1 0 ;;
Signature · 1 line
2.0.1
Re: [PATCH v2 23/23] rebase -i: enable options --signoff, --reset-author for pick, reword
git-rebase--interactive.sh do_replay () { · line 648
Quoted 7 lines · Fabian Ruch
git-rebase--interactive.sh +2
-1@@ -634,21 +644,24 @@ do_replay () { comment_for_reflog pick mark_action_done - do_pick $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" + eval do_pick $opts $sha1 \ + || die_with_patch $sha1 "Could not apply $sha1... $rest"
You had me a little puzzled at the switch to 'eval' here. That is necessary to match the quoting added in 20/23, not for any change in this commit. This commit is simply the first one to trigger this. Also, are you sure $sha1 does not require quoting through an eval?
Please add tests to this patch.
Signature · 2 lines
Thomas Rast [email protected]
Re: [PATCH v2 23/23] rebase -i: enable options --signoff, --reset-author for pick, reword
Hi Thomas,
Quoted 4 lines · Thomas Rast
Quoted 3 lines · Fabian Ruch
(quoted diff) +0
-0@@ -634,21 +644,24 @@ do_replay () { comment_for_reflog pick
Quoted 8 lines
Review do_replay () { · line 648
Quoted 4 lines
(quoted diff) +2
-1mark_action_done - do_pick $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" + eval do_pick $opts $sha1 \ + || die_with_patch $sha1 "Could not apply $sha1... $rest"You had me a little puzzled at the switch to 'eval' here. That is necessary to match the quoting added in 20/23, not for any change in this commit. This commit is simply the first one to trigger this.
This patch switches to 'eval' here because it is the first time 'opts' occurs. However, I agree that it might be confusing that 'opts' wasn't already added to the 'do_pick' lines by 20/23. By "trigger" you mean that this commit is the first to actually fill 'opts' with contents? I will move these changes to 20/23 then.
Quoted 1 line
Also, are you sure $sha1 does not require quoting through an eval?
At least if we can assume that it is really a SHA-1 object name. As such it does not contain characters interpreted by the shell, like backslashes, quotes or whitespace.
Quoted 1 line
Please add tests to this patch.
The ones I had in mind are attached below the scissors line. The current reroll fails the authorship checks and the 'git rebase --continue' test cases. As the necessary changes would obfuscate this sub-thread, they will be included in the next reroll.
Fabian
-- >8 --
t/t3427-rebase-line-options.sh +156 -1
@@ -6,10 +6,32 @@ test_description='git rebase -i with line options' . "$TEST_DIRECTORY"/lib-rebase.sh +commit_message () { + git cat-file commit "$1" | sed '1,/^$/d' +} + +commit_authorship () { + git cat-file commit "$1" | sed -n '/^$/q;/^author /p' +} + +authorship () { + echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" +} + +test_diff_file () { + if cmp "$1" "$2" >/dev/null + then + echo "'$1' and '$2' are the same" + return 1 + fi +} + test_expect_success 'Set up repository' ' test_commit Initial && test_commit Commit1 && - test_commit Commit2 + test_commit Commit2 && + git checkout -b branch Commit1 && + test_commit Commit2_ Commit2.t ' test_expect_success 'Unknown option' '
@@ -23,4 +45,137 @@ test_expect_success 'Unknown option' ' git rebase --continue ' +test_msg_author () { + set_fake_editor && + FAKE_LINES="1 $1 2" git rebase -i HEAD~2 && + commit_message HEAD >actual.msg && + commit_authorship HEAD >actual.author && + test_cmp expected.msg actual.msg && + test_cmp expected.author actual.author +} + +test_msg_author_misspelled () { + set_cat_todo_editor && + test_must_fail git rebase -i HEAD^ >todo && + set_fake_editor && + test_must_fail env FAKE_LINES="1 $1-misspelled 2" git rebase -i HEAD~2 && + set_fixed_todo_editor "$(pwd)"/todo && + FAKE_LINES="$1 1" git rebase --edit-todo && + git rebase --continue && + commit_message HEAD >actual.msg && + commit_authorship HEAD >actual.author && + test_cmp expected.msg actual.msg && + test_cmp expected.author actual.author +} + +test_msg_author_conflicted () { + set_fake_editor && + test_must_fail env FAKE_LINES="$1 1" git rebase -i master && + git checkout --theirs Commit2.t && + git add Commit2.t && + git rebase --continue && + commit_message HEAD >actual.msg && + commit_authorship HEAD >actual.author && + test_cmp expected.msg actual.msg && + test_cmp expected.author actual.author +} + +test_expect_success 'Misspelled pick --signoff' ' + git checkout -b misspelled-pick--signoff master && + cat >expected.msg <<-EOF && + $(commit_message HEAD) + + Signed-off-by: C O Mitter <[email protected]> + EOF + commit_authorship HEAD >expected.author && + test_msg_author_misspelled pick_--signoff +' + +test_expect_success 'Conflicted pick --signoff' ' + git checkout -b conflicted-pick--signoff branch && + cat >expected.msg <<-EOF && + $(commit_message HEAD) + + Signed-off-by: C O Mitter <[email protected]> + EOF + commit_authorship HEAD >expected.author && + test_msg_author_conflicted pick_--signoff +' + +test_expect_success 'pick --signoff' ' + git checkout -b pick--signoff master && + cat >expected.msg <<-EOF && + $(commit_message HEAD) + + Signed-off-by: C O Mitter <[email protected]> + EOF + commit_authorship HEAD >expected.author && + test_msg_author pick_--signoff +' + +test_expect_success 'reword --signoff' ' + git checkout -b reword--signoff master && + cat >expected.msg <<-EOF && + $(commit_message HEAD) + + Signed-off-by: C O Mitter <[email protected]> + EOF + commit_authorship HEAD >expected.author && + test_msg_author reword_--signoff +' + +test_expect_success 'Misspelled pick --reset-author' ' + git checkout -b misspelled-pick--reset-author master && + commit_message HEAD >expected.msg && + test_tick && + authorship >expected.author && + commit_authorship HEAD >original.author && + test_diff_file expected.author original.author && + test_msg_author_misspelled pick_--reset-author +' + +test_expect_success 'Conflicted pick --reset-author' ' + git checkout -b conflicted-pick--reset-author branch && + commit_message HEAD >expected.msg && + test_tick && + authorship >expected.author && + commit_authorship HEAD >original.author && + test_diff_file expected.author original.author && + test_msg_author_conflicted pick_--reset-author +' + +test_expect_success 'pick --reset-author' ' + git checkout -b pick--reset-author master && + commit_message HEAD >expected.msg && + test_tick && + authorship >expected.author && + commit_authorship HEAD >original.author && + test_diff_file expected.author original.author && + test_msg_author pick_--reset-author +' + +test_expect_success 'pick --reset-author --signoff' ' + git checkout -b pick--reset-author--signoff master && + cat >expected.msg <<-EOF && + $(commit_message HEAD) + + Signed-off-by: C O Mitter <[email protected]> + EOF + test_tick && + authorship >expected.author && + commit_authorship HEAD >original.author && + test_diff_file expected.author original.author && + test_msg_author pick_--reset-author_--signoff +' + +test_expect_success 'reword --reset-author' ' + git checkout -b reword--reset-author master && + commit_message HEAD >expected.msg && + test_tick && + authorship >expected.author && + commit_authorship HEAD >original.author && + test_diff_file expected.author original.author && + test_msg_author reword_--reset-author +' + test_done
Re: [PATCH v2 23/23] rebase -i: enable options --signoff, --reset-author for pick, reword
In reply to Fabian RuchQuoted 4 lines · Fabian Ruch
pick and reword are atomic to-do list commands in the sense that they open a new task which is closed after the respective command is completed. squash and fixup are not atomic. They create a new task which is not completed until the last squash or fixup is processed.
I don't understand the distinction that you are attempting to draw between "atomic" and "non-atomic" commands. For example, in the following command list:
pick 1111111
squash 2222222
fixup 3333333the "pick" command doesn't seem very atomic, because the *end* result of the three commands is a single commit that is affected by all three commands. Furthermore, if we change the example to
pick 1111111
squash --reset-author 2222222
fixup --signoff 3333333then isn't it clear that the user's intention was to apply both options, "--reset-author" and "--signoff", to the resulting commit? In other words, it seems to me that any options on such a chain of lines should be collected and applied to the final commit as a whole.
Quoted 4 lines
Lift the general unknown option blockade for the pick and reword commands. If
do_cmdcomes across one of the options--signoffand--reset-authorwhile parsing a to-do entry and the scheduled command is eitherpickorreword, relay the option todo_pick.
The new user-exposed options should be documented in the git-rebase(1) manpage and probably also in the help text that is appended to every "rebase -i" todo list.
Quoted 9 lines
The
do_pickoptions--gpg-signand--fileare not yet supported becausedo_cmdcannot handle option arguments and options with spaces at the moment. It is true that edit is one of the atomic commands but it displays hash information when the rebase is stopped and some options rewrite the picked commit which alters that information. squash and fixup still do not accept user options as the interplay of--reset-authorand the author script are yet to be determined.
[...]
Michael
Signature · 2 lines
Michael Haggerty [email protected]
Re: [PATCH v2 23/23] rebase -i: enable options --signoff, --reset-author for pick, reword
Hi,
Quoted 9 lines · Michael Haggerty
Quoted 4 lines · Fabian Ruch
Lift the general unknown option blockade for the pick and reword commands. If
do_cmdcomes across one of the options--signoffand--reset-authorwhile parsing a to-do entry and the scheduled command is eitherpickorreword, relay the option todo_pick.The new user-exposed options should be documented in the git-rebase(1) manpage and probably also in the help text that is appended to every "rebase -i" todo list.
The next reroll will add the following paragraph to the git-rebase man page right after the introduction of the 'reword' command in the section "INTERACTIVE MODE":
Quoted 7 lines
The commands "pick" and "reword" understand some well-known options. To add a Signed-off-by line at the end of the commit message, pass the
--signoffoption. The authorship can be renewed by specifying the--reset-authoroption. For instance, before you decide to publish a heavily edited commit you might want to reset the authorship and add your signature. You can do so on a per line basis:
Quoted 5 lines
-------------------------------------------
pick deadbee The oneline of this commit
pick --reset-author --signoff fa1afe1 The oneline of the next commit
...
-------------------------------------------
By saying "heavily edited commit" I tried to describe a commit that has been amended, reworded and reordered in such a way that the actual author information has become meaningless.
The help text at the end of every to-do list would look like this:
Quoted 8 lines
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
Quoted 7 lines
Options:
[pick | reword] --signoff = add a Signed-off-by line [pick | reword] --reset-author = renew authorshipThese lines can be re-ordered; they are executed from top to bottom.
If you remove a line here THAT COMMIT WILL BE LOST.
New about this is the "Options" headline.
Fabian
Re: [PATCH v2 23/23] rebase -i: enable options --signoff, --reset-author for pick, reword
In reply to Michael HaggertyHi Michael,
Quoted 10 lines · Michael Haggerty
Quoted 4 lines · Fabian Ruch
pick and reword are atomic to-do list commands in the sense that they open a new task which is closed after the respective command is completed. squash and fixup are not atomic. They create a new task which is not completed until the last squash or fixup is processed.
I don't understand the distinction that you are attempting to draw between "atomic" and "non-atomic" commands. For example, in the following command list:
Quoted 7 lines
pick 1111111 squash 2222222 fixup 3333333the "pick" command doesn't seem very atomic, because the *end* result of the three commands is a single commit that is affected by all three commands.
Right, when I wrote the commit message I was thinking in abstract terms so I implicitly thought of your example as a (single) squash/fixup command. Now it has become obvious that I wasn't very thorough with the implementation part. The git-rebase implementation is oblivious to the context when it processes 'pick' lines and your example shows how 'pick' lines can be part of squash/fixup command context. In conclusion, I intended to keep options disabled for squash/fixup commands but failed to do so because I neglected that a 'pick' line can initiate a squash/fixup command.
Quoted 8 lines
Furthermore, if we change the example to
pick 1111111 squash --reset-author 2222222 fixup --signoff 3333333then isn't it clear that the user's intention was to apply both options, "--reset-author" and "--signoff", to the resulting commit?
This seems to suggest an interpretation of todo lists similar to what I was thinking of when writing the commit message, that is one in which pick is not oblivious to the neighbouring commands. It might be a problem that it forbids the (admittedly improbable) use case where --reset-author is used to rewrite the authorship to something recent and fixup to have an even more recent committership.
To reconcile this kind of vertical interpretation with the horizontal specification of options one could introduce a todo list command taking the list of commits to be squashed as an argument. However, that seems to make it difficult to obtain the squash behaviour for some commits and the fixup behaviour for others that are part of the same chain.
The alternative interpretation of todo lists as simplified batch scripts for git commands would allow the intended behaviour (--reset-author and --signoff applied to the resulting commit), not restrict the user relatively to what she can already do on the command line and give actually different meanings to the syntactically different todo lists
pick 1111111
squash --reset-author 2222222
fixup --signoff 3333333and
pick 1111111
squash --signoff 2222222
fixup --reset-author 3333333, which would be treated identically by an implementation that collects the options. The current meaning of squash/fixup seems to be valid in the batch interpretation.
Quoted 3 lines
In other
words, it seems to me that any options on such a chain of lines should be collected and applied to the final commit as a whole.
To summarise, I think line options might be confusing if we interpret
pick 1111111
squash --reset-author 2222222
fixup --signoff 3333333as
combine the changes of 1111111, 2222222, 3333333 concatenate the messages of 1111111 and 2222222 edit the message
reset the authorship to the committership add a Signed-off-by: line
and not as
pick 1111111
pick -n 2222222
commit amend --reset-author -m $squash_msg
pick -n 3333333
commit amend --signoff --edit.
Thanks for pointing me at these issues. "Atomic" and "non-atomic" are really poorly-chosen terminology and the squash-initiating 'pick' might not be implemented correctly.
Fabian
v3 cover Enable options --signoff, --reset-author for pick, reword, edit
In reply to Fabian RuchHi,
this is the third reroll of the patch series that makes the well-known commit options --signoff and --reset-author available to be used with the to-do list commands pick, reword and edit.
What follows is a short changelog since the second reroll from almost two weeks ago. The changes were mostly made in response to Thomas' review.
- Merely a commit message comment: The editor.sh wrapper script is still necessary to use output with interactive to-do list commands because the --quiet option does not force all commands to be totally silent.
- git-rebase--interactive.sh now uses the quoting functionality of git-rev-parse when assigning the editor.sh path to the GIT_EDITOR environment variable, in case the path includes double quotes itself.
- The tests dealing with squash commits that violate either commit-msg or pre-commit now test squash and fixup sequences with more than two commits. This is important since the final commit is created on a different code path than the others.
- The fake_editor.sh change that prints the debug output on stderr instead of stdout is now a separate patch so that it can be referred to directly. The change might be considered a hack and needs revision.
- Since whitespace and shell quoting are not supported by line options, the opts variable does not need to be evaluated using eval anymore.
- Malformed to-do command lines either trigger an "unknown command" or an "unknown option" error message but not both.
- A test case that specifies that git rebase --continue only commits staged changes if the user has not committed on top of the last successfully replayed commit in the meantime. The rationale behind this is that git rebase --continue commits using the author information of the commit it tried to replay last. While it perfectly makes sense to assume that this information is correct when HEAD has not changed, it is probable that it is incorrect when the user has created commits herself because the rebase process was interrupted for conflict resolution only and one must know internals to intentionally make git rebase --continue commit changes on top of user-created commits using original author information.
- do_pick did not preserve the authorship of the original commit when it was asked to rewrite the commit, for instance using reword. This was fixed by applying the authorship of the named commit using do_with_author. That doesn't interfere with squash commits because do_with_author is a no-op when used with git commit --amend.
- git rebase --continue did not apply the line options after conflict resolution. This was added by remembering line options the way git-rebase--interactive.sh reminds itself of amending commits.
- The test suite t3427-rebase-line-options.sh is responsible for the specification of to-do lists that use line options.
- The two line options available are now documented both in the git-rebase man page and the to-do list help text.
Thanks for your time,
Fabian
Fabian Ruch (27):
rebase -i: allow replaying commits with empty log messages rebase -i: allow squashing empty commits without complaints rebase -i: allow rewording empty commits without complaints
fake_editor: leave standard output unchanged
rebase -i: hide interactive command messages in verbose mode rebase -i: discard redundant message when rewording fails
commit: allow disabling pre-commit and commit-msg separately rebase -i: verify squash messages using commit-msg rebase -i: do not verify reworded patches using pre-commit rebase -i: teach do_pick the option --edit
rebase -i: implement reword in terms of do_pick
rebase -i: log the replay of root commits
rebase -i: do not use -C when --no-edit is sufficient rebase -i: commit only once when rewriting picks
rebase -i: do not die in do_pick
rebase -i: teach do_pick the option --amend
rebase -i: teach do_pick the option --file
rebase -i: remove no-op do_with_author git commit --amend rebase -i: prepare for squash in terms of do_pick --amend rebase -i: implement squash in terms of do_pick
rebase -i: explicitly distinguish replay commands and exec tasks rebase -i: parse to-do list command line options
rebase -i: teach do_pick the option --reset-author rebase -i: teach do_pick the option --signoff
rebase -i: do not overwrite user author information rebase -i: refuse to commit when resuming with updated head rebase -i: enable --signoff, --reset-author for pick, reword, edit
Documentation/git-commit.txt | 8 +- Documentation/git-rebase.txt | 13 ++ builtin/commit.c | 32 +++- git-rebase--interactive.sh | 370 ++++++++++++++++++++++++++++++++--------- git-rebase.sh | 13 +- t/lib-rebase.sh | 28 +++- t/t3404-rebase-interactive.sh | 290 ++++++++++++++++++++++++++++++-- t/t3406-rebase-message.sh | 18 ++ t/t3412-rebase-root.sh | 16 ++ t/t3427-rebase-line-options.sh | 216 ++++++++++++++++++++++++ t/t7503-pre-commit-hook.sh | 65 +++++++- t/t7504-commit-msg-hook.sh | 85 ++++++++-- t/test-lib-functions.sh | 23 ++- 13 files changed, 1044 insertions(+), 133 deletions(-) create mode 100755 t/t3427-rebase-line-options.sh
Signature · 1 line
2.0.1
PATCH v3 1/27 rebase -i: allow replaying commits with empty log messages
git-rebase--interactive handles empty log messages inconsistently between enabled and disabled fast-forwards. By default, commits with empty log messages are rebased successfully like in non-interactive mode. In contrast, the --no-ff option aborts the replay of such commits.
In line with not verifying rebased commits and behaving like git-rebase for pick lines, use the --allow-empty-message option to replay commits. Root commits are replayed by recreating them in do_pick using git-commit and all other commits are replayed using git-cherry-pick in pick_one. Apply the option, understood by both git-commit and git-cherry-pick, at the respective sites.
In case of reword and squash, continue to abort the rebase if the _resulting_ commit would have no commit message. The rationale behind this default is that patches and their log messages should be verified at least once. For unchanged commits this is assumed to have happened according to the author's standards when she created the commits for the first time. While the empty log message can always be kept in place by editing and resuming the aborted rebase, a debatable alternative could be to teach git-rebase--interactive the option --allow-empty-message for disabling complaints about empty log messages even in changed commits.
The fixup case is different again because it throws away the second commit's log message and uses the first log message for the changed commit. Do not abort the rebase if that message is empty either since it is assumed to have been verified already.
The remaining to-do list command edit is handled just like pick for this matter, because git-rebase--interactive replays the named commit without changes before the rebase is interrupted and the user can make her changes to the replayed commit.
Add tests. In particular, design the squash-specific test case such that it involves interim commits and fixup steps. Interim commits should not trigger failures themselves and fixup steps should not let git-rebase--interactive forget that it is still dealing with a squash result.
--- git-rebase--interactive.sh | 10 ++++++---- t/t3404-rebase-interactive.sh | 38 ++++++++++++++++++++++++++++++++++++++ t/t3412-rebase-root.sh | 16 ++++++++++++++++ 3 files changed, 60 insertions(+), 4 deletions(-)
git-rebase--interactive.sh +6 -4
@@ -249,7 +249,7 @@ pick_one () { test -d "$rewritten" && pick_one_preserving_merges "$@" && return - output eval git cherry-pick \ + output eval git cherry-pick --allow-empty-message \ ${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} \ "$strategy_args" $empty_args $ff "$@" }
@@ -363,7 +363,7 @@ pick_one_preserving_merges () { echo "$sha1 $(git rev-parse HEAD^0)" >> "$rewritten_list" ;; *) - output eval git cherry-pick \ + output eval git cherry-pick --allow-empty-message \ ${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} \ "$strategy_args" "$@" || die_with_patch $sha1 "Could not pick $sha1"
@@ -549,7 +549,8 @@ do_next () { squash|s|fixup|f) # This is an intermediate commit; its message will only be # used in case of trouble. So use the long version: - do_with_author output git commit --amend --no-verify -F "$squash_msg" \ + do_with_author output git commit --allow-empty-message \ + --amend --no-verify -F "$squash_msg" \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest" ;;
@@ -557,7 +558,8 @@ do_next () { # This is the final command of this squash/fixup group if test -f "$fixup_msg" then - do_with_author git commit --amend --no-verify -F "$fixup_msg" \ + do_with_author git commit --allow-empty-message \ + --amend --no-verify -F "$fixup_msg" \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest" else
t/t3404-rebase-interactive.sh +38 -0
@@ -1039,4 +1039,42 @@ test_expect_success 'short SHA-1 collide' ' ) ' +test_expect_success 'setup commits with empty commit log messages' ' + git checkout -b empty-log-messages master && + test_commit no-msg-commit-1 && + git commit --amend --allow-empty-message -F - </dev/null && + test_commit no-msg-commit-2 && + git commit --amend --allow-empty-message -F - </dev/null && + test_commit no-msg-commit-3 && + git commit --amend --allow-empty-message -F - </dev/null +' + +test_expect_success 'rebase commits with empty commit log messages' ' + git checkout -b rebase-empty-log-messages empty-log-messages && + set_fake_editor && + test_expect_code 0 env FAKE_LINES="1" git rebase -i master && + test_expect_code 0 env FAKE_LINES="1" git rebase -i --no-ff master +' + +test_expect_success 'reword commits with empty commit log messages' ' + git checkout -b reword-empty-log-messages empty-log-messages && + test_when_finished reset_rebase && + set_fake_editor && + test_must_fail env FAKE_LINES="reword 1" git rebase -i master +' + +test_expect_success 'squash commits with empty commit log messages' ' + git checkout -b squash-empty-log-messages empty-log-messages && + set_fake_editor && + test_must_fail env FAKE_LINES="1 squash 2 fixup 3" git rebase -i master && + git commit --allow-empty-message --amend && + git rebase --continue +' + +test_expect_success 'fixup commits with empty commit log messages' ' + git checkout -b fixup-empty-log-messages empty-log-messages && + set_fake_editor && + env FAKE_LINES="1 fixup 2" git rebase -i master +' + test_done
t/t3412-rebase-root.sh +16 -0
@@ -278,4 +278,20 @@ test_expect_success 'rebase -i -p --root with conflict (second part)' ' test_cmp expect-conflict-p out ' +test_expect_success 'rebase --root root with empty log message' ' + git checkout --orphan empty-log-messages-root master && + test_commit no-msg-root-commit && + git commit --amend --allow-empty-message -F - </dev/null && + test_expect_code 0 git rebase --root && + test_expect_code 0 git rebase --root --no-ff +' + +test_expect_success 'rebase --root commits with empty log messages' ' + git checkout -b empty-log-messages master && + test_commit no-msg-commit && + git commit --amend --allow-empty-message -F - </dev/null && + test_expect_code 0 git rebase --root && + test_expect_code 0 git rebase --root --no-ff +' + test_done
Signature · 1 line
2.0.1
PATCH v3 2/27 rebase -i: allow squashing empty commits without complaints
In reply to Fabian RuchThe to-do list commands squash and fixup apply the changes introduced by the named commit to the tree but instead of creating a new commit on top of the current head it replaces the previous commit with a new commit that records the updated tree. If the result is an empty commit git-rebase stops with the error message
You asked to amend the most recent commit, but doing so would make it empty. You can repeat your command with --allow-empty, or you can remove the commit entirely with "git reset HEAD^".
This message is not very helpful because neither does git-rebase support an option --allow-empty nor does the messages say how to resume the rebase. Firstly, change the error message to
The squash result is empty and --keep-empty was not specified.
You can remove the squash commit now with
git reset HEAD^
Once you are done, run
git rebase --continue
If the user wishes to squash a sequence of commits into one commit, f. i.
pick A
squash Revert "A"
squash A'
, it does not matter for the end result that the first squash result, or any sub-sequence in general, is going to be empty. The squash message is not affected at all by which commits are created and only the commit created by the last line in the sequence will end up in the final history. Secondly, print the error message only if the whole squash sequence produced an empty commit.
Lastly, since an empty squash commit is not a failure to rewrite the history as planned, issue the message above as a mere warning and interrupt the rebase with the return value zero. The interruption should be considered as a notification with the chance to undo it on the spot. Specifying the --keep-empty option tells git-rebase to keep empty squash commits in the rebased history without notification.
Add tests.
--- git-rebase--interactive.sh | 20 +++++++++++--- t/t3404-rebase-interactive.sh | 62 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 3 deletions(-)
git-rebase--interactive.sh +17 -3
@@ -549,7 +549,7 @@ do_next () { squash|s|fixup|f) # This is an intermediate commit; its message will only be # used in case of trouble. So use the long version: - do_with_author output git commit --allow-empty-message \ + do_with_author output git commit --allow-empty-message --allow-empty \ --amend --no-verify -F "$squash_msg" \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest"
@@ -558,18 +558,32 @@ do_next () { # This is the final command of this squash/fixup group if test -f "$fixup_msg" then - do_with_author git commit --allow-empty-message \ + do_with_author git commit --allow-empty-message --allow-empty \ --amend --no-verify -F "$fixup_msg" \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest" else cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit rm -f "$GIT_DIR"/MERGE_MSG - do_with_author git commit --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \ + do_with_author git commit --allow-empty --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest" fi rm -f "$squash_msg" "$fixup_msg" + if test -z "$keep_empty" && is_empty_commit HEAD + then + echo "$sha1" >"$state_dir"/stopped-sha + warn "The squash result is empty and --keep-empty was not specified." + warn + warn "You can remove the squash commit now with" + warn + warn " git reset HEAD^" + warn + warn "Once you are done, run" + warn + warn " git rebase --continue" + exit 0 + fi ;; esac record_in_rewritten $sha1
t/t3404-rebase-interactive.sh +62 -0
68 lines not on this page · raw message
Signature · 1 line
2.0.1
PATCH v3 3/27 rebase -i: allow rewording empty commits without complaints
In reply to Fabian RuchThe to-do list command reword replays a commit like pick but lets the user also edit the commit's log message. If --keep-empty is passed as option to git-rebase--interactive, empty commits ought to be replayed without complaints. However, if the users chooses to reword an empty commit by changing the respective to-do list entry from
pick fa1afe1 Empty commit
to
reword fa1afe1 Empty commit
, then git-rebase--interactive suddenly fails to replay the empty commit. This is especially counterintuitive because reword is thought of as a pick that alters the log message in some way but nothing more and the unchanged to-do list entry would not fail.
Handle reword by cherry-picking the named commit and editing the log message using
git commit --allow-empty --amend
instead of
git commit --amend.
Add test.
--- git-rebase--interactive.sh | 2 +- t/t3404-rebase-interactive.sh | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-)
git-rebase--interactive.sh +1 -1
@@ -504,7 +504,7 @@ do_next () { mark_action_done do_pick $sha1 "$rest" - git commit --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} || { + git commit --allow-empty --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} || { warn "Could not amend commit after successfully picking $sha1... $rest" warn "This is most likely due to an empty commit message, or the pre-commit hook" warn "failed. If the pre-commit hook failed, you may need to resolve the issue before"
t/t3404-rebase-interactive.sh +33 -0
@@ -75,6 +75,39 @@ test_expect_success 'rebase --keep-empty' ' test_line_count = 6 actual ' +test_expect_success 'rebase --keep-empty (reword)' ' + git checkout -b reword-emptybranch emptybranch && + set_fake_editor && + FAKE_LINES="1 reword 2" git rebase --keep-empty -i HEAD~2 && + git log --oneline >actual && + test_line_count = 6 actual +' + +test_expect_success 'rebase --keep-empty (edit)' ' + git checkout -b edit-emptybranch emptybranch && + set_fake_editor && + FAKE_LINES="1 edit 2" git rebase --keep-empty -i HEAD~2 && + git rebase --continue && + git log --oneline >actual && + test_line_count = 6 actual +' + +test_expect_success 'rebase --keep-empty (fixup)' ' + git checkout -b fixup-emptybranch emptybranch && + set_fake_editor && + FAKE_LINES="1 fixup 2" git rebase --keep-empty -i HEAD~2 && + git log --oneline >actual && + test_line_count = 5 actual +' + +test_expect_success 'rebase --keep-empty (squash)' ' + git checkout -b squash-emptybranch emptybranch && + set_fake_editor && + FAKE_LINES="1 squash 2" git rebase --keep-empty -i HEAD~2 && + git log --oneline >actual && + test_line_count = 5 actual +' + test_expect_success 'rebase -i with the exec command' ' git checkout master && (
Signature · 1 line
2.0.1
PATCH v3 4/27 fake_editor: leave standard output unchanged
In reply to Fabian RuchThe helper function set_fake_editor installs a script as GIT_EDITOR that applies pre-defined editing rules to the files it is called with, which is a quite powerful tool when writing test cases for git-rebase--interactive. To aid in debugging the changes it makes, the installed script dumps the file contents to stdout before and after editing. That interferes with the output from git-rebase--interactive, however, and the debug information has to be removed from stdout in a error-prone way. Print the editor contents on stderr instead.
When a test case wants to analyse stderr, we need to come up with a different solution. The less convenient possibility that always remains is to store the debug output in a file in the "trash" directory or even keeping copies of the edited files before and after editing.
--- t/lib-rebase.sh | 8 ++++---- t/t3404-rebase-interactive.sh | 18 ++++++------------ 2 files changed, 10 insertions(+), 16 deletions(-)
t/lib-rebase.sh +4 -4
@@ -41,8 +41,8 @@ set_fake_editor () { test -z "$FAKE_LINES" && exit grep -v '^#' < "$1" > "$1".tmp rm -f "$1" - echo 'rebase -i script before editing:' - cat "$1".tmp + echo 'rebase -i script before editing:' >&2 + cat "$1".tmp >&2 action=pick for line in $FAKE_LINES; do case $line in
@@ -59,8 +59,8 @@ set_fake_editor () { action=pick;; esac done - echo 'rebase -i script after editing:' - cat "$1" + echo 'rebase -i script after editing:' >&2 + cat "$1" >&2 EOF test_set_editor "$(pwd)/fake-editor.sh"
t/t3404-rebase-interactive.sh +6 -12
@@ -882,9 +882,8 @@ test_expect_success 'running "git rebase -i --exec git show HEAD"' ' ( FAKE_LINES="1 exec_git_show_HEAD 2 exec_git_show_HEAD" && export FAKE_LINES && - git rebase -i HEAD~2 >expect + git rebase -i HEAD~2 >expected ) && - sed -e "1,9d" expect >expected && test_cmp expected actual '
@@ -896,9 +895,8 @@ test_expect_success 'running "git rebase --exec git show HEAD -i"' ' ( FAKE_LINES="1 exec_git_show_HEAD 2 exec_git_show_HEAD" && export FAKE_LINES && - git rebase -i HEAD~2 >expect + git rebase -i HEAD~2 >expected ) && - sed -e "1,9d" expect >expected && test_cmp expected actual '
@@ -910,9 +908,8 @@ test_expect_success 'running "git rebase -ix git show HEAD"' ' ( FAKE_LINES="1 exec_git_show_HEAD 2 exec_git_show_HEAD" && export FAKE_LINES && - git rebase -i HEAD~2 >expect + git rebase -i HEAD~2 >expected ) && - sed -e "1,9d" expect >expected && test_cmp expected actual '
@@ -924,9 +921,8 @@ test_expect_success 'rebase -ix with several <CMD>' ' ( FAKE_LINES="1 exec_git_show_HEAD;_pwd 2 exec_git_show_HEAD;_pwd" && export FAKE_LINES && - git rebase -i HEAD~2 >expect + git rebase -i HEAD~2 >expected ) && - sed -e "1,9d" expect >expected && test_cmp expected actual '
@@ -939,9 +935,8 @@ test_expect_success 'rebase -ix with several instances of --exec' ' FAKE_LINES="1 exec_git_show_HEAD exec_pwd 2 exec_git_show_HEAD exec_pwd" && export FAKE_LINES && - git rebase -i HEAD~2 >expect + git rebase -i HEAD~2 >expected ) && - sed -e "1,11d" expect >expected && test_cmp expected actual '
@@ -965,9 +960,8 @@ test_expect_success 'rebase -ix with --autosquash' ' git checkout -b autosquash_expected && FAKE_LINES="1 fixup 3 fixup 4 exec_git_show_HEAD 2 exec_git_show_HEAD" && export FAKE_LINES && - git rebase -i HEAD~4 >expect + git rebase -i HEAD~4 >expected ) && - sed -e "1,13d" expect >expected && test_cmp expected actual '
Signature · 1 line
2.0.1
PATCH v3 5/27 rebase -i: hide interactive command messages in verbose mode
In reply to Fabian Ruchgit-rebase--interactive prints summary messages of the commits it creates in the final history only if the --verbose option is specified by the user and suppresses them otherwise. This behaviour is implemented by wrapping git-commit calls in a shell function named output which redirects stderr to stdout, captures stdout in a shell variable and ignores its contents unless the command exits with an error status.
The command lines used to implement the to-do list commands reword and squash print diagnostic messages even in non-verbose mode. The reason for this inconsistency is that both commands launch the log message editor GIT_EDITOR which usually requires a working terminal attached to stdin. Wrapping the reword and squash command lines in output would seemingly freeze the terminal (see commit 7725cb5, "rebase -i: fix reword when using a terminal editor"). Temporarily redirect the GIT_EDITOR output to a third file descriptor in order to ship it around the capture stream. Wrap the remaining git-commit command lines in the new output. At the moment, it is still no alternative to pass the --quiet option in non-verbose mode because git-merge-recursive for instance prints some messages regardless of the verbosity level.
In order to temporarily redirect the editor output, the new definition of output creates a wrapper script in the state directory to be used as GIT_EDITOR. Make sure the state directory exists before output is called for the first time.
--- git-rebase--interactive.sh | 9 +++++---- git-rebase.sh | 13 +++++++++++-- t/t3406-rebase-message.sh | 18 ++++++++++++++++++ 3 files changed, 34 insertions(+), 6 deletions(-)
git-rebase--interactive.sh +5 -4
@@ -504,7 +504,7 @@ do_next () { mark_action_done do_pick $sha1 "$rest" - git commit --allow-empty --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} || { + output git commit --allow-empty --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} || { warn "Could not amend commit after successfully picking $sha1... $rest" warn "This is most likely due to an empty commit message, or the pre-commit hook" warn "failed. If the pre-commit hook failed, you may need to resolve the issue before"
@@ -558,14 +558,14 @@ do_next () { # This is the final command of this squash/fixup group if test -f "$fixup_msg" then - do_with_author git commit --allow-empty-message --allow-empty \ + do_with_author output git commit --allow-empty-message --allow-empty \ --amend --no-verify -F "$fixup_msg" \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest" else cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit rm -f "$GIT_DIR"/MERGE_MSG - do_with_author git commit --allow-empty --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \ + do_with_author output git commit --allow-empty --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest" fi
@@ -923,6 +923,8 @@ EOF ;; esac +mkdir -p "$state_dir" || die "Could not create temporary $state_dir" + git var GIT_COMMITTER_IDENT >/dev/null || die "You need to set your committer info first"
@@ -938,7 +940,6 @@ then fi orig_head=$(git rev-parse --verify HEAD) || die "No HEAD?" -mkdir -p "$state_dir" || die "Could not create temporary $state_dir" : > "$state_dir"/interactive || die "Could not mark as interactive" write_basic_state
git-rebase.sh +11 -2
@@ -131,9 +131,18 @@ write_basic_state () { output () { case "$verbose" in '') - output=$("$@" 2>&1 ) + cat >"$state_dir"/editor.sh <<-EOF + #!/bin/sh + $(git var GIT_EDITOR) "\$@" >&3 + EOF + chmod +x "$state_dir"/editor.sh + ( + GIT_EDITOR=$(git rev-parse --sq-quote "$state_dir"/editor.sh) + export GIT_EDITOR + "$@" 3>&1 1>"$state_dir"/output 2>&1 + ) status=$? - test $status != 0 && printf "%s\n" "$output" + test $status != 0 && cat "$state_dir"/output return $status ;; *)
t/t3406-rebase-message.sh +18 -0
@@ -4,6 +4,8 @@ test_description='messages from rebase operation' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-rebase.sh + test_expect_success 'setup' ' test_commit O fileO && test_commit X fileX &&
@@ -84,4 +86,20 @@ test_expect_success 'rebase --onto outputs the invalid ref' ' test_i18ngrep "invalid-ref" err ' +test_expect_success 'commit summary is suppressed in non-verbose mode' ' + git checkout --detach Y && + cat >expected.out <<-EOF && + Rebasing (1/5)\r + Rebasing (2/5)\r + Rebasing (3/5)\r + Rebasing (4/5)\r + Rebasing (5/5)\r + EOF + set_fake_editor && + FAKE_LINES="reword 1 fixup 2 fixup 3 4 squash 5" \ + git rebase -i --root >actual.out.tmp && + sed -e "s/\r/\\\\r\n/g" <actual.out.tmp >actual.out && + test_cmp expected.out actual.out +' + test_done
Signature · 1 line
2.0.1
PATCH v3 6/27 rebase -i: discard redundant message when rewording fails
In reply to Fabian RuchThe to-do list command reword replays a commit like pick but lets the user also edit the commit's log message. If the edited log message is empty or is found ill-formatted by one of the commit hooks, git-rebase--interactive prints three error messages to the console.
1. The git-commit output, which contains all the output from hook scripts.
2. A rebase diagnosis saying at which task on the to-do list it got stuck.
3. Generic presumptions about what could have triggered the error.
The third message contains redundant information and does not add any enlightenment either, which makes the output unnecessarily longish and different from the other commands' output. For instance, this is what the output looks like if the log message is empty (contains duplicate Signed-off-by lines).
(1.) Aborting commit due to empty commit message. (Duplicate Signed-off-by lines.) (2.) Could not amend commit after successfully picking fa1afe1... Some change (3.) This is most likely due to an empty commit message, or the pre-commit hook
failed. If the pre-commit hook failed, you may need to resolve the issue before you are able to reword the commit.
Discard the third message.
It is true that a failed hook script might not output any diagnosis but then the generic message is not of much help either. Since this lack of information affects the built-in git commands for commit, merge and cherry-pick first, the solution would be to keep track of the failed hooks in their output so that the user knows which of her hooks require improvement.
--- git-rebase--interactive.sh | 3 --- 1 file changed, 3 deletions(-)
git-rebase--interactive.sh +0 -3
@@ -506,9 +506,6 @@ do_next () { do_pick $sha1 "$rest" output git commit --allow-empty --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} || { warn "Could not amend commit after successfully picking $sha1... $rest" - warn "This is most likely due to an empty commit message, or the pre-commit hook" - warn "failed. If the pre-commit hook failed, you may need to resolve the issue before" - warn "you are able to reword the commit." exit_with_patch $sha1 1 } record_in_rewritten $sha1
Signature · 1 line
2.0.1
PATCH v3 7/27 commit: allow disabling pre-commit and commit-msg separately
In reply to Fabian RuchIntroduce the git-commit command line options --no-pre-commit and --no-commit-msg to disable the pre-commit and commit-msg hooks, respectively. Make --no-verify a synonym for specifying both at the same time.
This change is motivated by an internal usage of git-commit in git-rebase--interactive to disable pre-commit while keeping commit-msg enabled when rewording a commit.
Make test_commit forward unknown options to git-commit instead of teaching it all possible options. In order to support leading double dashes in <message>, stop interpreting test_commit arguments following a -- argument as options. This wasn't a problem before because the first unknown option would be used as <message>.
Allow disabling tag creation to avoid name clashes when using test_commit with the same arguments several times from the same test suite. By default, test_commit tags successful commits using git-tag for easy reference. The --notag option skips this step.
Add tests.
--- Documentation/git-commit.txt | 8 ++++- builtin/commit.c | 32 ++++++++++++++--- t/t7503-pre-commit-hook.sh | 65 ++++++++++++++++++++++++++++----- t/t7504-commit-msg-hook.sh | 85 ++++++++++++++++++++++++++++++++++---------- t/test-lib-functions.sh | 23 ++++++++---- 5 files changed, 176 insertions(+), 37 deletions(-)
Documentation/git-commit.txt +7 -1
@@ -158,7 +158,7 @@ OPTIONS -n:: --no-verify:: - This option bypasses the pre-commit and commit-msg hooks. + A synonym for `--no-pre-commit --no-commit-msg`. See also linkgit:githooks[5]. --allow-empty::
@@ -238,6 +238,12 @@ You should understand the implications of rewriting history if you amend a commit that has already been published. (See the "RECOVERING FROM UPSTREAM REBASE" section in linkgit:git-rebase[1].) +--no-pre-commit:: + This option bypasses the pre-commit hook. + +--no-commit-msg:: + This option bypasses the commit-msg hook. + --no-post-rewrite:: Bypass the post-rewrite hook.
builtin/commit.c +28 -4
@@ -98,12 +98,27 @@ static char *edit_message, *use_message;static char *fixup_message, *squash_message;static int all, also, interactive, patch_interactive, only, amend, signoff;static int edit_flag =-1 ;/* unspecified */ -static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; +static int quiet, verbose, allow_empty, dry_run, renew_authorship;static int no_post_rewrite, allow_empty_message;static char *untracked_files_arg, *force_date, *ignore_submodule_arg;static char *sign_commit;/* +* The verify variable is interpreted as a bitmap of enabled commit +* verification hooks according to the legend below. +* +* By default, the pre-commit and commit-msg hooks are enabled. This +* is represented by both the PRE_COMMIT and COMMIT_MSG bits being +* set. +* +* The bitmap is changed through the command line options +* --no-verify, --no-pre-commit and --no-commit-msg. +*/ +#define PRE_COMMIT (1<<0) +#define COMMIT_MSG (1<<1) +static int verify =PRE_COMMIT |COMMIT_MSG ; + +/* * The default commit message cleanup mode will remove the lines * beginning with # (shell comments) and leading and trailing * whitespaces (empty lines or containing only whitespaces)
@@ -661,7 +676,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix,/* This checks and barfs if author is badly specified */ determine_author_info (author_ident); -if (!no_verify &&run_commit_hook (use_editor, index_file,"pre-commit" ,NULL )) +if (verify &PRE_COMMIT && +run_commit_hook (use_editor, index_file,"pre-commit" ,NULL ))return 0 ;if (squash_message) {
@@ -962,7 +978,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, } } -if (!no_verify && +if (verify &COMMIT_MSG &&run_commit_hook (use_editor, index_file,"commit-msg" ,git_path (commit_editmsg),NULL )) {return 0 ; }
@@ -1590,7 +1606,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix)OPT_BOOL (0 ,"interactive" , &interactive,N_ ("interactively add files" )),OPT_BOOL ('p' ,"patch" , &patch_interactive,N_ ("interactively add changes" )),OPT_BOOL ('o' ,"only" , &only,N_ ("commit only specified files" )), -OPT_BOOL ('n' ,"no-verify" , &no_verify,N_ ("bypass pre-commit hook" )), +OPT_NEGBIT ('n' ,"no-verify" , &verify, +N_ ("synonym for --no-pre-commit --no-commit-msg" ), +PRE_COMMIT |COMMIT_MSG ),OPT_BOOL (0 ,"dry-run" , &dry_run,N_ ("show what would be committed" )),OPT_SET_INT (0 ,"short" , &status_format,N_ ("show status concisely" ), STATUS_FORMAT_SHORT),
@@ -1603,6 +1621,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix)OPT_BOOL ('z' ,"null" , &s.null_termination,N_ ("terminate entries with NUL" )),OPT_BOOL (0 ,"amend" , &amend,N_ ("amend previous commit" )), +OPT_NEGBIT (0 ,"no-pre-commit" , &verify, +N_ ("bypass pre-commit hook" ), +PRE_COMMIT ), +OPT_NEGBIT (0 ,"no-commit-msg" , &verify, +N_ ("bypass commit-msg hook" ), +COMMIT_MSG ),OPT_BOOL (0 ,"no-post-rewrite" , &no_post_rewrite,N_ ("bypass post-rewrite hook" )), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, N_("mode"), N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, /* end commit contents options */
t/t7503-pre-commit-hook.sh +57 -8
105 lines not on this page · raw message
t/t7504-commit-msg-hook.sh +67 -18
164 lines not on this page · raw message
t/test-lib-functions.sh +17 -6
@@ -154,20 +154,28 @@ test_pause () { test_commit () { notick= && - signoff= && + notag= && + commit_opts= && while test $# != 0 do case "$1" in --notick) notick=yes ;; - --signoff) - signoff="$1" + --notag) + notag=yes + ;; + --) + shift && + break + ;; + -*) + commit_opts="$commit_opts $1" ;; *) break ;; - esac + esac && shift done && file=${2:-"$1.t"} &&
@@ -177,8 +185,11 @@ test_commit () { then test_tick fi && - git commit $signoff -m "$1" && - git tag "${4:-$1}" + git commit $commit_opts -m "$1" && + if test -z "$notag" + then + git tag "${4:-$1}" + fi } # Call test_merge with the arguments "<message> <commit>", where <commit>
Signature · 1 line
2.0.1
PATCH v3 8/27 rebase -i: verify squash messages using commit-msg
In reply to Fabian RuchUsing the to-do list command squash the user can specify two or more commits and git-rebase creates one commit that introduces all their changes combined. The authorship for the created commit is taken from the first commit specified and the user can edit the log message. There is a variant of squash available named fixup which also takes the first log message without asking for user input.
While it is reasonable to not verify replayed changes twice or rejecting some other author's changes in her name, it is insufficient to not verify the user input used as log message in the case of squash. Specify the git-commit option --no-pre-commit instead of --no-verify when committing the squash result, but not before, to let the commit-msg hook verify the final squash message. For the same reasons the pre-commit hook is disabled in all replay modes, the commit-msg hook is disabled in fixup mode.
Add tests. In addition to the existing test checking that the pre-commit hook is disabled when simply picking a commit, provide a test checking that the commit-msg hook is disabled as well.
--- git-rebase--interactive.sh | 2 +- t/t3404-rebase-interactive.sh | 80 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-)
git-rebase--interactive.sh +1 -1
@@ -562,7 +562,7 @@ do_next () { else cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit rm -f "$GIT_DIR"/MERGE_MSG - do_with_author output git commit --allow-empty --amend --no-verify -F "$GIT_DIR"/SQUASH_MSG -e \ + do_with_author output git commit --allow-empty --amend --no-pre-commit -F "$GIT_DIR"/SQUASH_MSG -e \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest" fi
t/t3404-rebase-interactive.sh +80 -0
86 lines not on this page · raw message
Signature · 1 line
2.0.1
PATCH v3 9/27 rebase -i: do not verify reworded patches using pre-commit
In reply to Fabian RuchThe to-do list command reword replays a commit like pick but lets the user also edit the commit's log message. This happens in two steps. Firstly, the named commit is cherry-picked. Secondly, the commit created in the first step is amended using an unchanged index to edit the log message. The pre-commit hook is meant to verify the changes introduced by a commit (for instance, catching inserted trailing white space). Since the committed tree is not changed between the two steps and we do not verify rebased patches, do not execute the pre-commit hook in the second step.
Specify the git-commit option --no-pre-commit to disable the pre-commit hook when editing the log message. The commit-msg hook will still be executed to verify the edited commit log message. As before, if the hook finds the new log message ill-formatted, the rebase will be interrupted with the unchanged commit replayed and the new log message in $GIT_DIR/COMMIT_EDITMSG.
Add tests.
--- git-rebase--interactive.sh | 2 +- t/t3404-rebase-interactive.sh | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-)
git-rebase--interactive.sh +1 -1
@@ -504,7 +504,7 @@ do_next () { mark_action_done do_pick $sha1 "$rest" - output git commit --allow-empty --amend --no-post-rewrite ${gpg_sign_opt:+"$gpg_sign_opt"} || { + output git commit --allow-empty --amend --no-post-rewrite --no-pre-commit ${gpg_sign_opt:+"$gpg_sign_opt"} || { warn "Could not amend commit after successfully picking $sha1... $rest" exit_with_patch $sha1 1 }
t/t3404-rebase-interactive.sh +14 -0
@@ -683,6 +683,13 @@ test_expect_success 'setup failing pre-commit' ' test_commit --no-verify pre-commit-violated-3 ' +test_expect_success 'reword a commit violating pre-commit' ' + git checkout -b reword-violating-pre-commit violating-pre-commit && + test_when_finished reset_rebase && + set_fake_editor && + env FAKE_LINES="reword 1" git rebase -i master +' + test_expect_success 'squash commits violating pre-commit' ' git checkout -b squash-violating-pre-commit violating-pre-commit && test_when_finished reset_rebase &&
@@ -726,6 +733,13 @@ test_expect_success 'rebase a commit violating commit-msg' ' FAKE_LINES="1" git rebase -i master ' +test_expect_success 'reword a commit violating commit-msg' ' + git checkout -b reword-violating-commit-msg violating-commit-msg && + test_when_finished reset_rebase && + set_fake_editor && + test_must_fail env FAKE_LINES="reword 1" git rebase -i master +' + test_expect_success 'squash commits violating commit-msg' ' git checkout -b squash-violating-commit-msg violating-commit-msg && set_fake_editor &&
Signature · 1 line
2.0.1
PATCH v3 10/27 rebase -i: teach do_pick the option --edit
In reply to Fabian Ruchdo_pick is the git-cherry-pick wrapper in git-rebase--interactive that is used to implement the to-do list command pick. To cater for the different pick behaviours (like reword), do_pick accepts several options not only from the git-cherry-pick but also the git-commit interface. Add the common option --edit to let the user edit the log message of the named commit.
Loop over $@ to parse the do_pick arguments. Assign the local variable edit if one of the options is --edit so that the remainder of do_pick can easily check whether the client code asked to edit the commit message. If one of the options is unknown, mention it on the console and die. Break the loop on the first non-option and do some sanity checking to ensure that there exactly two non-options, which are interpreted by the remainder as <commit> and <title> like before.
do_pick ought to act as a wrapper around cherry-pick. Unfortunately, it cannot just forward --edit to the cherry-pick command line. The assembled command line is executed within a command substitution for controlling the verbosity of rebase--interactive. Passing --edit would either hang the terminal or clutter the substituted command output with control sequences. Execute the reword code from do_next instead if the option --edit is specified.
--- git-rebase--interactive.sh | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+)
git-rebase--interactive.sh +43 -0
@@ -461,7 +461,42 @@ record_in_rewritten() { esac } +# Apply the changes introduced by the given commit to the current head. +# +# do_pick [--edit] <commit> <title> +# +# Wrapper around git-cherry-pick. +# +# -e, --edit +# After picking <commit>, open an editor and let the user edit the +# commit message. The editor contents becomes the commit message of +# the new head. This creates a fresh commit. +# +# <commit> +# The commit to cherry-pick. +# +# <title> +# The commit message title of <commit>. Used for information +# purposes only. do_pick () { + edit= + while test $# -gt 0 + do + case "$1" in + -e|--edit) + edit=y + ;; + -*) + die "do_pick: unrecognized option -- $1" + ;; + *) + break + ;; + esac + shift + done + test $# -ne 2 && die "do_pick: wrong number of arguments" + if test "$(git rev-parse HEAD)" = "$squash_onto" then # Set the correct commit message and author info on the
@@ -483,6 +518,14 @@ do_pick () { pick_one $1 || die_with_patch $1 "Could not apply $1... $2" fi + + if test -n "$edit" + then + output git commit --allow-empty --amend --no-post-rewrite --no-pre-commit ${gpg_sign_opt:+"$gpg_sign_opt"} || { + warn "Could not amend commit after successfully picking $1... $2" + exit_with_patch $1 1 + } + fi } do_next () {
Signature · 1 line
2.0.1
PATCH v3 11/27 rebase -i: implement reword in terms of do_pick
In reply to Fabian RuchThe to-do list command reword replays a commit like pick but lets the user also edit the commit's log message. If one thinks of pick entries as scheduled cherry-pick command lines, then reword becomes an alias for the command line cherry-pick --edit. The porcelain rebase--interactive defines a function do_pick for processing the pick entries on to-do lists. Reimplement reword in terms of do_pick --edit.
If the user picks a commit using the to-do list line
reword fa1afe1 Some change
execute the command do_pick --edit fa1afe1 "Some change" which carries out exactly the same steps as the case arm for reword in do_next so far.
--- git-rebase--interactive.sh | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-)
git-rebase--interactive.sh +1 -5
@@ -546,11 +546,7 @@ do_next () { comment_for_reflog reword mark_action_done - do_pick $sha1 "$rest" - output git commit --allow-empty --amend --no-post-rewrite --no-pre-commit ${gpg_sign_opt:+"$gpg_sign_opt"} || { - warn "Could not amend commit after successfully picking $sha1... $rest" - exit_with_patch $sha1 1 - } + do_pick --edit $sha1 "$rest" record_in_rewritten $sha1 ;; edit|e)
Signature · 1 line
2.0.1
PATCH v3 12/27 rebase -i: log the replay of root commits
In reply to Fabian RuchThe command line used to recreate root commits specifies the option -q which suppresses the commit summary message. However, git-rebase--interactive tends to tell the user about the commits it creates in the final history, if she wishes (cf. command line option --verbose). The code parts handling non-root commits and squash commits all output commit summary messages. Do not make the replay of root commits an exception. Remove the option to make the report of the rebased history complete. Do not forget to wrap the git-commit command line in output so that the summary is shown if git-rebase is called with the --verbose option but suppressed otherwise.
It is OK that the commit summary is still suppressed when git-commit is used to initialize the authorship of the sentinel commit because this additional commit is an implementation detail hidden from the final history. The removed -q option was probably introduced as a copy-and-paste error stemming from that part of the root commit handling code.
--- git-rebase--interactive.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
git-rebase--interactive.sh +2 -2
@@ -510,8 +510,8 @@ do_pick () { git commit --allow-empty --allow-empty-message --amend \ --no-post-rewrite -n -q -C $1 && pick_one -n $1 && - git commit --allow-empty --allow-empty-message \ - --amend --no-post-rewrite -n -q -C $1 \ + output git commit --allow-empty --allow-empty-message \ + --amend --no-post-rewrite -n -C $1 \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_with_patch $1 "Could not apply $1... $2" else
Signature · 1 line
2.0.1
PATCH v3 13/27 rebase -i: do not use -C when --no-edit is sufficient
In reply to Fabian RuchThe command line used to recreate root commits uses -C to suppress the log message editor. This is unnecessarily confusing, though, because that suppression is a secondary effect of the option. The main purpose of -C is to pull the metadata from another commit, but here we know that this is a no-op, since we are amending a commit just created from the same data.
At the time -C was introduced, git-commit did not yet have a documented --no-edit, and this was a reasonable way to get the desired behavior. Switch it to use --no-edit to make the intended effect more obvious.
--- git-rebase--interactive.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
git-rebase--interactive.sh +1 -1
@@ -511,7 +511,7 @@ do_pick () { --no-post-rewrite -n -q -C $1 && pick_one -n $1 && output git commit --allow-empty --allow-empty-message \ - --amend --no-post-rewrite -n -C $1 \ + --amend --no-post-rewrite -n --no-edit \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_with_patch $1 "Could not apply $1... $2" else
Signature · 1 line
2.0.1
PATCH v3 14/27 rebase -i: commit only once when rewriting picks
In reply to Fabian RuchThe options passed to do_pick determine whether the picked commit will be rewritten or not. If the commit gets rewritten, because the user requested to edit the commit message for instance, let pick_one merely apply the changes introduced by the commit and do not commit the resulting tree yet. If the commit is replayed as is, leave it to pick_one to recreate the commit (possibly by fast-forwarding the head). This makes it easier to combine git-commit options like --edit and --amend in do_pick because git-cherry-pick does not support --amend.
In the case of --edit, do not exit_with_patch but assign rewrite to pick the changes with -n. If the pick conflicts, no commit is created which we would have to amend when continuing the rebase. To complete the pick after the conflicts are resolved the user just resumes with git rebase --continue.
git-commit lets the user edit the commit log message by default. We do not want that for the rewriting git-commit command line because the default behaviour of git-rebase is exactly the opposite. Pass --no-edit when rewriting a picked commit. An explicit --edit passed to do_pick (for instance, when reword is executed) enables the editor launch again. Similarly, pass --allow-empty-message unless the log message is edited.
If rebase--interactive is used to rebase a complete branch onto some head, rebase creates a sentinel commit that requires special treatment by do_pick. Do not finalize the pick here either because its commit message can be altered as for any other pick. Since the orphaned root commit gets a temporary parent, it is always rewritten. Safely use the rewrite infrastructure of do_pick to create the final commit.
--- git-rebase--interactive.sh | 41 ++++++++++++++++++++++++++--------------- t/t3404-rebase-interactive.sh | 10 ++++++++++ 2 files changed, 36 insertions(+), 15 deletions(-)
git-rebase--interactive.sh +26 -15
@@ -63,9 +63,10 @@ msgnum="$state_dir"/msgnum author_script="$state_dir"/author-script # When an "edit" rebase command is being processed, the SHA1 of the -# commit to be edited is recorded in this file. When "git rebase -# --continue" is executed, if there are any staged changes then they -# will be amended to the HEAD commit, but only provided the HEAD +# commit to be edited is recorded in this file. The same happens when +# rewriting a root commit fails, for instance "reword". When "git +# rebase --continue" is executed, if there are any staged changes then +# they will be amended to the HEAD commit, but only provided the HEAD # commit is still the commit to be edited. When any other rebase # command is processed, this file is deleted. amend="$state_dir"/amend
@@ -479,12 +480,17 @@ record_in_rewritten() { # The commit message title of <commit>. Used for information # purposes only. do_pick () { - edit= + allow_empty_message=y + rewrite= + rewrite_amend= + rewrite_edit= while test $# -gt 0 do case "$1" in -e|--edit) - edit=y + rewrite=y + rewrite_edit=y + allow_empty_message= ;; -*) die "do_pick: unrecognized option -- $1"
@@ -499,6 +505,10 @@ do_pick () { if test "$(git rev-parse HEAD)" = "$squash_onto" then + rewrite=y + rewrite_amend=y + git rev-parse --verify HEAD >"$amend" + # Set the correct commit message and author info on the # sentinel root before cherry-picking the original changes # without committing (-n). Finally, update the sentinel again
@@ -509,22 +519,23 @@ do_pick () { # rebase --continue. git commit --allow-empty --allow-empty-message --amend \ --no-post-rewrite -n -q -C $1 && - pick_one -n $1 && - output git commit --allow-empty --allow-empty-message \ - --amend --no-post-rewrite -n --no-edit \ - ${gpg_sign_opt:+"$gpg_sign_opt"} || + pick_one -n $1 || die_with_patch $1 "Could not apply $1... $2" else - pick_one $1 || + pick_one ${rewrite:+-n} $1 || die_with_patch $1 "Could not apply $1... $2" fi - if test -n "$edit" + if test -n "$rewrite" then - output git commit --allow-empty --amend --no-post-rewrite --no-pre-commit ${gpg_sign_opt:+"$gpg_sign_opt"} || { - warn "Could not amend commit after successfully picking $1... $2" - exit_with_patch $1 1 - } + eval $(get_author_ident_from_commit $1) + do_with_author output git commit \ + --allow-empty --no-post-rewrite -n --no-edit \ + ${allow_empty_message:+--allow-empty-message} \ + ${rewrite_amend:+--amend} \ + ${rewrite_edit:+--edit --commit-msg} \ + ${gpg_sign_opt:+"$gpg_sign_opt"} || + die_with_patch $1 "Could not rewrite commit after successfully picking $1... $2" fi }
t/t3404-rebase-interactive.sh +10 -0
@@ -270,6 +270,16 @@ test_expect_success 'retain authorship' ' git show HEAD | grep "^Author: Twerp Snog" ' +test_expect_success 'retain authorship (reword)' ' + echo A > file8 && + git add file8 && + test_tick && + GIT_AUTHOR_NAME="Twerp Snog" git commit -m "different author" && + set_fake_editor && + FAKE_LINES="reword 1" git rebase -i --onto master HEAD^ && + git show HEAD | grep "^Author: Twerp Snog" +' + test_expect_success 'setup squash/fixup reverted and fixed feature' ' git checkout -b reverted-feature master && test_commit feature &&
Signature · 1 line
2.0.1
PATCH v3 15/27 rebase -i: do not die in do_pick
In reply to Fabian RuchSince do_pick might be executed in a sub-shell (a modified author environment for instance), calling die in do_pick has no effect but exiting the sub-shell with a failure exit status. The git-rebase--interactive script is not terminated. Moreover, if do_pick is called while a squash or fixup is in effect, die_with_patch will discard $squash_msg as commit message. Lastly, after a die in do_pick do_next has no chance to reschedule tasks that failed before changes could be applied.
Indicate an error in do_pick using return statements and properly kill the script at the call sites. Although possible in principle, the issued error messages are no more indicating whether do_pick failed while applying or while committing the changes. This reduces code complexity at the call site and does not matter from a user's point of view because a glance at the index reveals whether there are conflicts or not and in-depth troubleshooting is still possible using the --verbose option.
Remove the commit message title argument from do_pick's interface, which has become unused.
--- git-rebase--interactive.sh | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-)
git-rebase--interactive.sh +19 -15
@@ -464,7 +464,7 @@ record_in_rewritten() { # Apply the changes introduced by the given commit to the current head. # -# do_pick [--edit] <commit> <title> +# do_pick [--edit] <commit> # # Wrapper around git-cherry-pick. #
@@ -476,9 +476,11 @@ record_in_rewritten() { # <commit> # The commit to cherry-pick. # -# <title> -# The commit message title of <commit>. Used for information -# purposes only. +# The return value is 1 if applying the changes resulted in a conflict +# and 2 if the specified arguments were incorrect. If the changes could +# be applied successfully but creating the commit failed, a value +# greater than 2 is returned. No commit is created in either case and +# the index is left with the (conflicting) changes in place. do_pick () { allow_empty_message=y rewrite=
@@ -493,7 +495,8 @@ do_pick () { allow_empty_message= ;; -*) - die "do_pick: unrecognized option -- $1" + warn "do_pick: unrecognized option -- $1" + return 2 ;; *) break
@@ -501,7 +504,11 @@ do_pick () { esac shift done - test $# -ne 2 && die "do_pick: wrong number of arguments" + if test $# -ne 1 + then + warn "do_pick: wrong number of arguments" + return 2 + fi if test "$(git rev-parse HEAD)" = "$squash_onto" then
@@ -519,11 +526,9 @@ do_pick () { # rebase --continue. git commit --allow-empty --allow-empty-message --amend \ --no-post-rewrite -n -q -C $1 && - pick_one -n $1 || - die_with_patch $1 "Could not apply $1... $2" + pick_one -n $1 || return 1 else - pick_one ${rewrite:+-n} $1 || - die_with_patch $1 "Could not apply $1... $2" + pick_one ${rewrite:+-n} $1 || return 1 fi if test -n "$rewrite"
@@ -534,8 +539,7 @@ do_pick () { ${allow_empty_message:+--allow-empty-message} \ ${rewrite_amend:+--amend} \ ${rewrite_edit:+--edit --commit-msg} \ - ${gpg_sign_opt:+"$gpg_sign_opt"} || - die_with_patch $1 "Could not rewrite commit after successfully picking $1... $2" + ${gpg_sign_opt:+"$gpg_sign_opt"} || return 3 fi }
@@ -550,21 +554,21 @@ do_next () { comment_for_reflog pick mark_action_done - do_pick $sha1 "$rest" + do_pick $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" record_in_rewritten $sha1 ;; reword|r) comment_for_reflog reword mark_action_done - do_pick --edit $sha1 "$rest" + do_pick --edit $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" record_in_rewritten $sha1 ;; edit|e) comment_for_reflog edit mark_action_done - do_pick $sha1 "$rest" + do_pick $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" warn "Stopped at $sha1... $rest" exit_with_patch $sha1 0 ;;
Signature · 1 line
2.0.1
PATCH v3 16/27 rebase -i: teach do_pick the option --amend
In reply to Fabian Ruchdo_pick is the git-cherry-pick wrapper in git-rebase--interactive that is used to implement the to-do list commands pick, reword and edit. To cater for the different pick behaviours (like squash), do_pick accepts several options not only from the git-cherry-pick but also the git-commit interface.
Add the option --amend from the git-commit interface to the options pool of do_pick. It creates a new commit for the changes introduced by the picked commit and the previous one. The previous commit is then replaced with the new commit. If no other options are specified, the log message of the previous commit is used.
Be careful when --amend is used to pick a root commit because HEAD might point to the sentinel commit but there is still nothing to amend. Be sure to initialize amend so that commits are squashed even when git-rebase--interactive is interrupted for resolving conflicts. It is not a mistake to do the initialization regardless of any conflicts because amend is always cleared before the next to-do item is processed.
--- git-rebase--interactive.sh | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-)
git-rebase--interactive.sh +17 -1
@@ -464,10 +464,16 @@ record_in_rewritten() { # Apply the changes introduced by the given commit to the current head. # -# do_pick [--edit] <commit> +# do_pick [--amend] [--edit] <commit> # # Wrapper around git-cherry-pick. # +# --amend +# After picking <commit>, replace the current head commit with a new +# commit that also introduces the changes of <commit>. +# +# _This is not a git-cherry-pick option._ +# # -e, --edit # After picking <commit>, open an editor and let the user edit the # commit message. The editor contents becomes the commit message of
@@ -489,6 +495,16 @@ do_pick () { while test $# -gt 0 do case "$1" in + --amend) + if test "$(git rev-parse HEAD)" = "$squash_onto" || ! git rev-parse -q --verify HEAD >/dev/null + then + warn "do_pick: nothing to amend" + return 2 + fi + rewrite=y + rewrite_amend=y + git rev-parse --verify HEAD >"$amend" + ;; -e|--edit) rewrite=y rewrite_edit=y
Signature · 1 line
2.0.1
PATCH v3 17/27 rebase -i: teach do_pick the option --file
In reply to Fabian Ruchdo_pick is the git-cherry-pick wrapper in git-rebase--interactive that is used to implement the to-do list command pick, reword and edit. To cater for the different pick behaviours (like squash), do_pick accepts several options not only from the git-cherry-pick but also the git-commit interface.
Add the option --file from the git-commit interface to the options pool of do_pick. It expects an argument itself which is interpreted as a file path and takes the commit message from the given file. If --file is passed to do_pick, assign the given file path to the local variable rewrite_message and relay the option
--file "$rewrite_message"
to the git-commit command line which creates the commit.
--- git-rebase--interactive.sh | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-)
git-rebase--interactive.sh +19 -1
@@ -464,7 +464,7 @@ record_in_rewritten() { # Apply the changes introduced by the given commit to the current head. # -# do_pick [--amend] [--edit] <commit> +# do_pick [--amend] [--file <file>] [--edit] <commit> # # Wrapper around git-cherry-pick. #
@@ -474,6 +474,12 @@ record_in_rewritten() { # # _This is not a git-cherry-pick option._ # +# -F <file>, --file <file> +# Take the commit message from the given file. This creates a fresh +# commit. +# +# _This is not a git-cherry-pick option._ +# # -e, --edit # After picking <commit>, open an editor and let the user edit the # commit message. The editor contents becomes the commit message of
@@ -492,6 +498,7 @@ do_pick () { rewrite= rewrite_amend= rewrite_edit= + rewrite_message= while test $# -gt 0 do case "$1" in
@@ -505,6 +512,16 @@ do_pick () { rewrite_amend=y git rev-parse --verify HEAD >"$amend" ;; + -F|--file) + if test $# -eq 0 + then + warn "do_pick: option --file specified but no <file> given" + return 2 + fi + rewrite=y + rewrite_message=$2 + shift + ;; -e|--edit) rewrite=y rewrite_edit=y
@@ -555,6 +572,7 @@ do_pick () { ${allow_empty_message:+--allow-empty-message} \ ${rewrite_amend:+--amend} \ ${rewrite_edit:+--edit --commit-msg} \ + ${rewrite_message:+--file "$rewrite_message"} \ ${gpg_sign_opt:+"$gpg_sign_opt"} || return 3 fi }
Signature · 1 line
2.0.1
PATCH v3 18/27 rebase -i: remove no-op do_with_author git commit --amend
In reply to Fabian RuchThe author script is a file in the state directory that contains assignments of the environment variables
GIT_AUTHOR_NAME
GIT_AUTHOR_EMAIL
GIT_AUTHOR_DATE
to be evaluated by the shell. It is used to store author information and has two applications in git-rebase--interactive.sh. Firstly, the authorship of squash commits is read from it while the squash commit is being amended step by step. Secondly, after conflict resolution git rebase --continue restores the author information of the original commit by sourcing the author script. For the assignments of the git environment variables to take effect, git-rebase--interactive executes the respective git-commit commands wrapped in do_with_author. That shell function executes the named command in a subshell that exports the git environment variables.
Since
git commit --amend
has been used instead of
git reset --soft HEAD^
git committo amend squash commits, wrapping git-commit in do_with_author has become a no-op because, unless the --reset-author option is specified, git commit --amend ignores the user environment and reuses the authorship of the commit it amends. To make the code clearer and until we decide to use other authorships for squash commits than the one of the first commit, unwrap git commit --amend.
--- git-rebase--interactive.sh | 17 +++++++---------- t/t3404-rebase-interactive.sh | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 10 deletions(-)
git-rebase--interactive.sh +7 -10
@@ -622,9 +622,6 @@ do_next () { mark_action_done update_squash_messages $squash_style $sha1 - author_script_content=$(get_author_ident_from_commit HEAD) - echo "$author_script_content" > "$author_script" - eval "$author_script_content" if ! pick_one -n $sha1 then git rev-parse --verify HEAD >"$amend"
@@ -634,7 +631,7 @@ do_next () { squash|s|fixup|f) # This is an intermediate commit; its message will only be # used in case of trouble. So use the long version: - do_with_author output git commit --allow-empty-message --allow-empty \ + output git commit --allow-empty-message --allow-empty \ --amend --no-verify -F "$squash_msg" \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest"
@@ -643,14 +640,14 @@ do_next () { # This is the final command of this squash/fixup group if test -f "$fixup_msg" then - do_with_author output git commit --allow-empty-message --allow-empty \ + output git commit --allow-empty-message --allow-empty \ --amend --no-verify -F "$fixup_msg" \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest" else cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit rm -f "$GIT_DIR"/MERGE_MSG - do_with_author output git commit --allow-empty --amend --no-pre-commit -F "$GIT_DIR"/SQUASH_MSG -e \ + output git commit --allow-empty --amend --no-pre-commit -F "$GIT_DIR"/SQUASH_MSG -e \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest" fi
@@ -939,7 +936,7 @@ continue) then : Nothing to commit -- skip this else - if ! test -f "$author_script" + if ! test -f "$author_script" && ! test -f "$amend" then gpg_sign_opt_quoted=${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} die "You have staged changes in your working tree. If these changes are meant to be
@@ -956,8 +953,6 @@ In both case, once you're done, continue with: git rebase --continue " fi - . "$author_script" || - die "Error trying to find the author identity to amend commit" if test -f "$amend" then current_head=$(git rev-parse --verify HEAD)
@@ -965,10 +960,12 @@ In both case, once you're done, continue with: die "\ You have uncommitted changes in your working tree. Please, commit them first and then run 'git rebase --continue' again." - do_with_author git commit --amend --no-verify -F "$msg" -e \ + git commit --amend --no-verify -F "$msg" -e \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die "Could not commit staged changes." else + . "$author_script" || + die "Error trying to find the author identity to amend commit" do_with_author git commit --no-verify -F "$msg" -e \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die "Could not commit staged changes."
t/t3404-rebase-interactive.sh +23 -0
@@ -1270,4 +1270,27 @@ test_expect_success 'fixup commits with empty commit log messages' ' env FAKE_LINES="1 fixup 2" git rebase -i master ' +test_expect_success 'squash commits have authorship of the first commit' ' + git checkout -b squash-authorship master && + git cat-file commit HEAD~3 | sed -n -e "/^$/q" -e "/^author /p" >expected.author && + test_tick && + set_fake_editor && + FAKE_LINES="1 squash 2 squash 3 fixup 4" git rebase -i HEAD~4 && + git cat-file commit HEAD | sed -n -e "/^$/q" -e "/^author /p" >actual.author && + test_cmp expected.author actual.author +' + +test_expect_success 'squash commits have authorship of the first commit after conflict' ' + git checkout -b squash-authorship-conflict conflict-branch && + git cat-file commit HEAD~3 | sed -n -e "/^$/q" -e "/^author /p" >expected.author && + test_tick && + set_fake_editor && + test_must_fail env FAKE_LINES="1 squash 2 squash 4 fixup 3" git rebase -i HEAD~4 && + git checkout --ours conflict && + git add conflict && + git rebase --continue && + git cat-file commit HEAD | sed -n -e "/^$/q" -e "/^author /p" >actual.author && + test_cmp expected.author actual.author +' + test_done
Signature · 1 line
2.0.1
PATCH v3 19/27 rebase -i: prepare for squash in terms of do_pick --amend
In reply to Fabian RuchRewrite squash and fixup handling in do_next using the sequence
pick_one
commit
in order to test the correctness of a single do_squash or parameterised do_pick and make the subsequent patch reimplementing squash in terms of such a single function more readable.
Do not call rm -f "$GIT_DIR"/MERGE_MSG since it has no effect on the state after git-rebase--interactive terminates. The option -F makes git-commit ignore MERGE_MSG for the log message. If git-commit succeeds, MERGE_MSG is removed, and if it fails, MERGE_MSG is overwritten by the error sequence die_failed_squash. In summary, removing MERGE_MSG neither influences the squash commit message nor the file state after git-commit returns.
Moreover, pick_one ignores $GIT_DIR/SQUASH_MSG and does not touch $squash_msg so that it is correct to execute pick_one immediately before git-commit.
Might be squashed into the subsequent commit.
--- git-rebase--interactive.sh | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-)
git-rebase--interactive.sh +15 -6
@@ -622,15 +622,15 @@ do_next () { mark_action_done update_squash_messages $squash_style $sha1 - if ! pick_one -n $sha1 - then - git rev-parse --verify HEAD >"$amend" - die_failed_squash $sha1 "$rest" - fi case "$(peek_next_command)" in squash|s|fixup|f) # This is an intermediate commit; its message will only be # used in case of trouble. So use the long version: + if ! pick_one -n $sha1 + then + git rev-parse --verify HEAD >"$amend" + die_failed_squash $sha1 "Could not apply $sha1... $rest" + fi output git commit --allow-empty-message --allow-empty \ --amend --no-verify -F "$squash_msg" \ ${gpg_sign_opt:+"$gpg_sign_opt"} ||
@@ -640,13 +640,22 @@ do_next () { # This is the final command of this squash/fixup group if test -f "$fixup_msg" then + if ! pick_one -n $sha1 + then + git rev-parse --verify HEAD >"$amend" + die_failed_squash $sha1 "Could not apply $sha1... $rest" + fi output git commit --allow-empty-message --allow-empty \ --amend --no-verify -F "$fixup_msg" \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest" else cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit - rm -f "$GIT_DIR"/MERGE_MSG + if ! pick_one -n $sha1 + then + git rev-parse --verify HEAD >"$amend" + die_failed_squash $sha1 "Could not apply $sha1... $rest" + fi output git commit --allow-empty --amend --no-pre-commit -F "$GIT_DIR"/SQUASH_MSG -e \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die_failed_squash $sha1 "$rest"
Signature · 1 line
2.0.1
PATCH v3 20/27 rebase -i: implement squash in terms of do_pick
In reply to Fabian RuchThe to-do list command squash and its close relative fixup replay the changes of a commit like pick but do not recreate the commit. Instead they replace the previous commit with a new commit that also introduces the changes of the squashed commit. This is roughly like cherry-picking without committing and using git-commit to amend the previous commit.
The to-do list
pick a Some changes
squash b Some more changes
gets translated into the sequence of git commands
git cherry-pick a
git cherry-pick -n b
git commit --amend
and if git-cherry-pick supported --amend this would look even more like the to-do list it is based on
git cherry-pick a
git cherry-pick --amend b.
Since do_pick takes care of pick entries and the above suggests squash as an alias for pick --amend, reimplement squash in terms of do_pick --amend. Introduce $squash_msg as the commit message via the --file option. When the last commit of a squash series is processed, the user is asked to review the log message. Pass --edit as additional option in this case. The only difference in the options passed to git-commit and do_pick is the omitted --no-verify. However, do_pick does not execute the verification hooks anyway because it solely replays commits and assumes that they have been verified before.
--- git-rebase--interactive.sh | 32 ++++++-------------------------- 1 file changed, 6 insertions(+), 26 deletions(-)
git-rebase--interactive.sh +6 -26
@@ -626,39 +626,19 @@ do_next () { squash|s|fixup|f) # This is an intermediate commit; its message will only be # used in case of trouble. So use the long version: - if ! pick_one -n $sha1 - then - git rev-parse --verify HEAD >"$amend" - die_failed_squash $sha1 "Could not apply $sha1... $rest" - fi - output git commit --allow-empty-message --allow-empty \ - --amend --no-verify -F "$squash_msg" \ - ${gpg_sign_opt:+"$gpg_sign_opt"} || - die_failed_squash $sha1 "$rest" + do_pick --amend -F "$squash_msg" $sha1 \ + || die_failed_squash $sha1 "$rest" ;; *) # This is the final command of this squash/fixup group if test -f "$fixup_msg" then - if ! pick_one -n $sha1 - then - git rev-parse --verify HEAD >"$amend" - die_failed_squash $sha1 "Could not apply $sha1... $rest" - fi - output git commit --allow-empty-message --allow-empty \ - --amend --no-verify -F "$fixup_msg" \ - ${gpg_sign_opt:+"$gpg_sign_opt"} || - die_failed_squash $sha1 "$rest" + do_pick --amend -F "$fixup_msg" $sha1 \ + || die_failed_squash $sha1 "$rest" else cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit - if ! pick_one -n $sha1 - then - git rev-parse --verify HEAD >"$amend" - die_failed_squash $sha1 "Could not apply $sha1... $rest" - fi - output git commit --allow-empty --amend --no-pre-commit -F "$GIT_DIR"/SQUASH_MSG -e \ - ${gpg_sign_opt:+"$gpg_sign_opt"} || - die_failed_squash $sha1 "$rest" + do_pick --amend -F "$GIT_DIR"/SQUASH_MSG -e $sha1 \ + || die_failed_squash $sha1 "$rest" fi rm -f "$squash_msg" "$fixup_msg" if test -z "$keep_empty" && is_empty_commit HEAD
Signature · 1 line
2.0.1
PATCH v3 21/27 rebase -i: explicitly distinguish replay commands and exec tasks
In reply to Fabian RuchThere are two kinds of to-do list commands available. One kind replays a commit (pick, reword, edit, squash and fixup that is) and the other executes a shell command (exec). We will call the first kind replay commands.
The two kinds of tasks are scheduled using different line formats. Replay commands expect a commit hash argument following the command name and exec concatenates all arguments to assemble a command line.
Adhere to the distinction of formats by not trying to parse the sha1 field unless we are dealing with a replay command. Move the replay command handling code to a new function do_replay which assumes the first argument to be a commit hash and make no more such assumptions in do_next.
--- git-rebase--interactive.sh | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-)
git-rebase--interactive.sh +28 -14
@@ -577,13 +577,12 @@ do_pick () { fi } -do_next () { - rm -f "$msg" "$author_script" "$amend" || exit - read -r command sha1 rest < "$todo" +do_replay () { + command=$1 + sha1=$2 + rest=$3 + case "$command" in - "$comment_char"*|''|noop) - mark_action_done - ;; pick|p) comment_for_reflog pick
@@ -659,6 +658,28 @@ do_next () { esac record_in_rewritten $sha1 ;; + *) + read -r command <"$todo" + warn "Unknown command: $command" + fixtodo="Please fix this using 'git rebase --edit-todo'." + if git rev-parse --verify -q "$sha1" >/dev/null + then + die_with_patch $sha1 "$fixtodo" + else + die "$fixtodo" + fi + ;; + esac +} + +do_next () { + rm -f "$msg" "$author_script" "$amend" || exit + read -r command sha1 rest <"$todo" + + case "$command" in + "$comment_char"*|''|noop) + mark_action_done + ;; x|"exec") read -r command rest < "$todo" mark_action_done
@@ -698,14 +719,7 @@ do_next () { fi ;; *) - warn "Unknown command: $command $sha1 $rest" - fixtodo="Please fix this using 'git rebase --edit-todo'." - if git rev-parse --verify -q "$sha1" >/dev/null - then - die_with_patch $sha1 "$fixtodo" - else - die "$fixtodo" - fi + do_replay $command $sha1 "$rest" ;; esac test -s "$todo" && return
Signature · 1 line
2.0.1
PATCH v3 22/27 rebase -i: parse to-do list command line options
In reply to Fabian RuchRead in to-do list lines as
command args
instead of
command sha1 rest
so that to-do list command lines can specify additional arguments apart from the commit hash and the log message title, which become the non-options in args. Loop over args, put all options (an argument beginning with a dash) in opts, stop the loop on the first non-option and assign it to sha1. For instance, the to-do list
reword --signoff fa1afe1 Some change
is parsed as command=reword, opts= '--signoff' (including the single quotes and the space for evaluation and concatenation of options), sha1=fa1afel and rest=Some change. The loop does not know the options it parses so that options that take an argument themselves are not supported at the moment. Neither are options that contain spaces because the shell expansion of args in do_next interprets white space characters as argument separator.
Print an error message for unknown or unsupported command line options, which means an error for all specified options at the moment. Cleanly break the do_next loop by assigning a reason to the local variable malformed, which triggers the unknown command code in do_replay. Move the error code to the beginning of do_replay so that unknown commands are reported before bad options are as only the first error we come across is reported. For instance, the to-do list from above produces the error
Unknown 'reword' option: --signoff Please fix this using 'git rebase --edit-todo'.
The to-do list is also parsed when the commit hashes are translated between long and short format before and after the to-do list is edited. Apply the same procedure as in do_replay with the exception that we only care about where the options stop and the commit hash begins. Do not reject any options when transforming the commit hashes.
Enable the specification of to-do list command line options in FAKE_LINES the same way this is accomplished for command lines passed to exec. Define a new fake_editor.sh that edits a static to-do list instead of the one passed as argument when invoked by git-rebase. Add a test case that checks that unknown options are refused and can be corrected using --edit-todo.
--- git-rebase--interactive.sh | 95 +++++++++++++++++++++++++++++++----------- t/lib-rebase.sh | 20 ++++++++- t/t3427-rebase-line-options.sh | 26 ++++++++++++ 3 files changed, 114 insertions(+), 27 deletions(-) create mode 100755 t/t3427-rebase-line-options.sh
git-rebase--interactive.sh +70 -25
@@ -578,30 +578,71 @@ do_pick () { } do_replay () { + malformed= command=$1 - sha1=$2 - rest=$3 + shift + case "$command" in + pick|reword|edit|squash|fixup) + ;; + *) + read -r command <"$todo" + malformed="Unknown command: $command" + ;; + esac + + opts= + while test $# -gt 0 && test -z "$malformed" + do + case "$1" in + -*) + malformed="Unknown '$command' option: $1" + ;; + *) + break + ;; + esac + opts="$opts $1" + shift + done + sha1=$1 + shift + rest=$* + + if test -n "$malformed" + then + warn "$malformed" + fixtodo="Please fix this using 'git rebase --edit-todo'." + if git rev-parse --verify -q "$sha1" >/dev/null + then + die_with_patch $sha1 "$fixtodo" + else + die "$fixtodo" + fi + fi case "$command" in pick|p) comment_for_reflog pick mark_action_done - do_pick $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" + do_pick $opts $sha1 \ + || die_with_patch $sha1 "Could not apply $sha1... $rest" record_in_rewritten $sha1 ;; reword|r) comment_for_reflog reword mark_action_done - do_pick --edit $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" + do_pick --edit $opts $sha1 \ + || die_with_patch $sha1 "Could not apply $sha1... $rest" record_in_rewritten $sha1 ;; edit|e) comment_for_reflog edit mark_action_done - do_pick $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" + do_pick $opts $sha1 \ + || die_with_patch $sha1 "Could not apply $sha1... $rest" warn "Stopped at $sha1... $rest" exit_with_patch $sha1 0 ;;
@@ -625,18 +666,18 @@ do_replay () { squash|s|fixup|f) # This is an intermediate commit; its message will only be # used in case of trouble. So use the long version: - do_pick --amend -F "$squash_msg" $sha1 \ + do_pick --amend -F "$squash_msg" $opts $sha1 \ || die_failed_squash $sha1 "$rest" ;; *) # This is the final command of this squash/fixup group if test -f "$fixup_msg" then - do_pick --amend -F "$fixup_msg" $sha1 \ + do_pick --amend -F "$fixup_msg" $opts $sha1 \ || die_failed_squash $sha1 "$rest" else cp "$squash_msg" "$GIT_DIR"/SQUASH_MSG || exit - do_pick --amend -F "$GIT_DIR"/SQUASH_MSG -e $sha1 \ + do_pick --amend -F "$GIT_DIR"/SQUASH_MSG -e $opts $sha1 \ || die_failed_squash $sha1 "$rest" fi rm -f "$squash_msg" "$fixup_msg"
@@ -658,23 +699,12 @@ do_replay () { esac record_in_rewritten $sha1 ;; - *) - read -r command <"$todo" - warn "Unknown command: $command" - fixtodo="Please fix this using 'git rebase --edit-todo'." - if git rev-parse --verify -q "$sha1" >/dev/null - then - die_with_patch $sha1 "$fixtodo" - else - die "$fixtodo" - fi - ;; esac } do_next () { rm -f "$msg" "$author_script" "$amend" || exit - read -r command sha1 rest <"$todo" + read -r command args <"$todo" case "$command" in "$comment_char"*|''|noop)
@@ -719,7 +749,7 @@ do_next () { fi ;; *) - do_replay $command $sha1 "$rest" + do_replay $command $args ;; esac test -s "$todo" && return
@@ -799,19 +829,34 @@ skip_unnecessary_picks () { } transform_todo_ids () { - while read -r command rest + while read -r command args do case "$command" in "$comment_char"* | exec) # Be careful for oddball commands like 'exec' # that do not have a SHA-1 at the beginning of $rest. + newargs=\ $args ;; *) - sha1=$(git rev-parse --verify --quiet "$@" ${rest%% *}) && - rest="$sha1 ${rest#* }" + newargs= + sha1= + for arg in $args + do + case "$arg" in + -*) + newargs="$newargs $arg" + ;; + *) + test -z "$sha1" && + sha1=$(git rev-parse --verify --quiet "$@" $arg) && + arg=$sha1 + newargs="$newargs $arg" + ;; + esac + done ;; esac - printf '%s\n' "$command${rest:+ }$rest" + printf '%s\n' "$command$newargs" done <"$todo" >"$todo.new" && mv -f "$todo.new" "$todo" }
t/lib-rebase.sh +18 -2
@@ -46,8 +46,8 @@ set_fake_editor () { action=pick for line in $FAKE_LINES; do case $line in - squash|fixup|edit|reword) - action="$line";; + pick*|squash*|fixup*|edit*|reword*) + action=$(echo "$line" | sed 's/_/ /g');; exec*) echo "$line" | sed 's/_/ /g' >> "$1";; "#")
@@ -80,6 +80,22 @@ set_cat_todo_editor () { test_set_editor "$(pwd)/fake-editor.sh" } +# set_fixed_todo_editor takes a file path as argument and installs an +# editor script that, firstly, overwrites the file path argument with +# the one specified during installation and, secondly, calls +# fake-editor.sh for changing the contents as usual. This comes in +# handy if it is easier to change some fixed file instead of the one +# that will be passed when the editor is being invoked. + +set_fixed_todo_editor () { + set_fake_editor + write_script fake-editor-wrapper.sh <<-EOF + cp "$1" "\$1" + "$(pwd)"/fake-editor.sh "\$1" + EOF + test_set_editor "$(pwd)/fake-editor-wrapper.sh" +} + # checks that the revisions in "$2" represent a linear range with the # subjects in "$1" test_linear_range () {
t/t3427-rebase-line-options.sh new file +26 -0
@@ -0,0 +1,26 @@ +#!/bin/sh + +test_description='git rebase -i with line options' + +. ./test-lib.sh + +. "$TEST_DIRECTORY"/lib-rebase.sh + +test_expect_success 'Set up repository' ' + test_commit Initial && + test_commit Commit1 && + test_commit Commit2 +' + +test_expect_success 'Unknown option' ' + git checkout -b unknown-option master && + set_cat_todo_editor && + test_must_fail git rebase -i HEAD^ >todo && + set_fake_editor && + test_must_fail env FAKE_LINES="1 pick_--unknown-option 2" git rebase -i HEAD~2 && + set_fixed_todo_editor "$(pwd)"/todo && + git rebase --edit-todo && + git rebase --continue +' + +test_done
Signature · 1 line
2.0.1
PATCH v3 23/27 rebase -i: teach do_pick the option --reset-author
In reply to Fabian Ruchdo_pick is the git-cherry-pick wrapper in git-rebase--interactive that is used to implement many of the to-do list commands. Eventually, the complete do_pick interface will be exposed to the user in some form or another and those commands will become simple aliases for the do_pick options now used to implement them.
Add the git-commit option --reset-author to the options pool of do_pick. It rewrites the author date and name of the picked commit to match the committer date and name.
If --reset-author is passed to do_pick, set the rewrite flag and relay the option to the git-commit command line which creates the final commit. If --amend is not passed as well, the fresh authorship effect is achieved by the mere fact that we are creating a new commit. Do not even source the ident information in that case because the user shell might have already exported the respective environment variables.
--- git-rebase--interactive.sh | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-)
git-rebase--interactive.sh +22 -4
@@ -464,10 +464,18 @@ record_in_rewritten() { # Apply the changes introduced by the given commit to the current head. # -# do_pick [--amend] [--file <file>] [--edit] <commit> +# do_pick [--reset-author] [--amend] [--file <file>] [--edit] <commit> # # Wrapper around git-cherry-pick. # +# --reset-author +# Pretend the changes were made for the first time. Declare that the +# authorship of the resulting commit now belongs to the committer. +# This also renews the author timestamp. This creates a fresh +# commit. +# +# _This is not a git-cherry-pick option._ +# # --amend # After picking <commit>, replace the current head commit with a new # commit that also introduces the changes of <commit>.
@@ -496,12 +504,17 @@ record_in_rewritten() { do_pick () { allow_empty_message=y rewrite= + rewrite_reset_author= rewrite_amend= rewrite_edit= rewrite_message= while test $# -gt 0 do case "$1" in + --reset-author) + rewrite=y + rewrite_reset_author=y + ;; --amend) if test "$(git rev-parse HEAD)" = "$squash_onto" || ! git rev-parse -q --verify HEAD >/dev/null then
@@ -566,11 +579,16 @@ do_pick () { if test -n "$rewrite" then - eval $(get_author_ident_from_commit $1) - do_with_author output git commit \ + do_with_author= + if test -z "$rewrite_reset_author" && test -z "$rewrite_amend" + then + eval $(get_author_ident_from_commit $1) + do_with_author=do_with_author + fi + $do_with_author output git commit \ --allow-empty --no-post-rewrite -n --no-edit \ ${allow_empty_message:+--allow-empty-message} \ - ${rewrite_amend:+--amend} \ + ${rewrite_amend:+--amend ${rewrite_reset_author:+--reset-author}} \ ${rewrite_edit:+--edit --commit-msg} \ ${rewrite_message:+--file "$rewrite_message"} \ ${gpg_sign_opt:+"$gpg_sign_opt"} || return 3
Signature · 1 line
2.0.1
PATCH v3 24/27 rebase -i: teach do_pick the option --signoff
In reply to Fabian Ruchdo_pick is the git-cherry-pick wrapper in git-rebase--interactive that is currently used to implement most of the to-do list commands and offers additional options that will eventually find their way onto to-do lists.
To extend the repertoire of available options, add the git-commit and git-cherry-pick option --signoff to the do_pick interface. It appends a Signed-off-by: line using the committer identity to the log message of the picked commit.
--- git-rebase--interactive.sh | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-)
git-rebase--interactive.sh +12 -1
@@ -464,10 +464,15 @@ record_in_rewritten() { # Apply the changes introduced by the given commit to the current head. # -# do_pick [--reset-author] [--amend] [--file <file>] [--edit] <commit> +# do_pick [--signoff] [--reset-author] [--amend] [--file <file>] +# [--edit] <commit> # # Wrapper around git-cherry-pick. # +# -s, --signoff +# Insert a Signed-off-by: line using the committer identity at the +# end of the commit log message. This creates a fresh commit. +# # --reset-author # Pretend the changes were made for the first time. Declare that the # authorship of the resulting commit now belongs to the committer.
@@ -504,6 +509,7 @@ record_in_rewritten() { do_pick () { allow_empty_message=y rewrite= + rewrite_signoff= rewrite_reset_author= rewrite_amend= rewrite_edit=
@@ -511,6 +517,10 @@ do_pick () { while test $# -gt 0 do case "$1" in + -s|--signoff) + rewrite=y + rewrite_signoff=y + ;; --reset-author) rewrite=y rewrite_reset_author=y
@@ -588,6 +598,7 @@ do_pick () { $do_with_author output git commit \ --allow-empty --no-post-rewrite -n --no-edit \ ${allow_empty_message:+--allow-empty-message} \ + ${rewrite_signoff:+--signoff} \ ${rewrite_amend:+--amend ${rewrite_reset_author:+--reset-author}} \ ${rewrite_edit:+--edit --commit-msg} \ ${rewrite_message:+--file "$rewrite_message"} \
Signature · 1 line
2.0.1
PATCH v3 25/27 rebase -i: do not overwrite user author information
In reply to Fabian RuchThe shell function get_author_ident_from_commit defined by git-sh-setup retrieves the author information from the named commit and returns assignments of the environment variables
GIT_AUTHOR_NAME
GIT_AUTHOR_EMAIL
GIT_AUTHOR_DATE
ready for evaluation by the shell.
This interface is used in conjunction with the so-called author script which is a git-rebase--interactive state file that contains the get_author_ident_from_commit output. It is sourced when git rebase --continue is executed after conflict resolution to retain the original commit authorship.
The variable assignments are only exported by the subshell that executes the git-commit command line that commits the resolved conflicts. That is taken care of by wrapping the git-commit call in do_with_author. However, this is not enough protection from modifying the git environment variables unintentionally because the user running git-rebase could have already exported those herself. And therefore, a bare git-commit could result in an authorship that is neither intended by the user nor by git-rebase--interactive.
While it is not an issue now (either do_with_author, git-cherry-pick or --amend are used to create commits), the unnecessary loss of the author name and e-mail copied from the user environment, and the unneeded fixing of the author date might become a problem when we decide to support something similar to --reset-author or `--ignore-date in interactive git-rebase.
Do not assign the git environment variables until in the do_with_author subshell.
--- git-rebase--interactive.sh | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-)
git-rebase--interactive.sh +10 -9
@@ -166,7 +166,7 @@ make_patch () { test -f "$msg" || commit_message "$1" > "$msg" test -f "$author_script" || - get_author_ident_from_commit "$1" > "$author_script" + echo "$1" > "$author_script" } die_with_patch () {
@@ -215,9 +215,13 @@ is_merge_commit() } # Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and -# GIT_AUTHOR_DATE exported from the current environment. +# GIT_AUTHOR_DATE assigned the author information extracted from the +# named commit and exported. do_with_author () { ( + sha1=$1 + shift + eval $(get_author_ident_from_commit $sha1) export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE "$@" )
@@ -348,13 +352,11 @@ pick_one_preserving_merges () { test "a$1" = a-n && die "Refusing to squash a merge: $sha1" # redo merge - author_script_content=$(get_author_ident_from_commit $sha1) - eval "$author_script_content" msg_content="$(commit_message $sha1)" # No point in merging the first parent, that's HEAD new_parents=${new_parents# $first_parent} merge_args="--no-log --no-ff" - if ! do_with_author output eval \ + if ! do_with_author $sha1 output eval \ 'git merge ${gpg_sign_opt:+"$gpg_sign_opt"} \ $merge_args $strategy_args -m "$msg_content" $new_parents' then
@@ -592,8 +594,7 @@ do_pick () { do_with_author= if test -z "$rewrite_reset_author" && test -z "$rewrite_amend" then - eval $(get_author_ident_from_commit $1) - do_with_author=do_with_author + do_with_author="do_with_author $1" fi $do_with_author output git commit \ --allow-empty --no-post-rewrite -n --no-edit \
@@ -1041,9 +1042,9 @@ first and then run 'git rebase --continue' again." ${gpg_sign_opt:+"$gpg_sign_opt"} || die "Could not commit staged changes." else - . "$author_script" || + test -r "$author_script" || die "Error trying to find the author identity to amend commit" - do_with_author git commit --no-verify -F "$msg" -e \ + do_with_author $(cat "$author_script") git commit --no-verify -F "$msg" -e \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die "Could not commit staged changes." fi
Signature · 1 line
2.0.1
PATCH v3 26/27 rebase -i: refuse to commit when resuming with updated head
In reply to Fabian RuchIf git-rebase--interactive fails to apply the changes introduced by a commit due to conflicts, it interrupts the rebase process and gives the user a shell to resolve the conflicts manually. The process is resumed when the user executes git rebase --continue. If the index has changes, the script assumes that those are to be committed under the authorship and with the log message of the commit it tried to replay last. However, that assumption is most likely incorrect if the user has already committed the resolved changes herself. To prevent committing unrelated changes under the wrong authorship and with the wrong log message, at least check that HEAD is still at the same commit by tracking the hash of the last replayed commit. A similar check already happens before git rebase --continue amends the previous commit after an edit or a conflicted squash command.
--- git-rebase--interactive.sh | 35 +++++++++++++++++++++-------------- t/t3404-rebase-interactive.sh | 12 ++++++++++++ 2 files changed, 33 insertions(+), 14 deletions(-)
git-rebase--interactive.sh +21 -14
@@ -62,13 +62,18 @@ msgnum="$state_dir"/msgnum # being rebased. author_script="$state_dir"/author-script -# When an "edit" rebase command is being processed, the SHA1 of the -# commit to be edited is recorded in this file. The same happens when -# rewriting a root commit fails, for instance "reword". When "git -# rebase --continue" is executed, if there are any staged changes then -# they will be amended to the HEAD commit, but only provided the HEAD -# commit is still the commit to be edited. When any other rebase -# command is processed, this file is deleted. +# This file keeps track of the SHA1 of the last replayed commit, the +# new parent of the next commit being replayed. It is used to make +# sure that "git rebase --continue" only commits resolved conflicts +# or "edit" changes automatically. +last_head="$state_dir"/last_head + +# When an "edit" or a "squash" rebase command is being processed, the +# file 'amend' is created. When "git rebase --continue" is executed, +# if there are any staged changes then they will be amended to the +# HEAD commit, but only provided the HEAD commit is still the commit +# to be edited or the squash commit. When any other rebase command is +# processed, these files are deleted. amend="$state_dir"/amend # For the post-rewrite hook, we make a list of rewritten commits and
@@ -179,7 +184,8 @@ die_with_patch () { exit_with_patch () { echo "$1" > "$state_dir"/stopped-sha make_patch $1 - git rev-parse --verify HEAD > "$amend" + >"$amend" + git rev-parse --verify HEAD >"$last_head" gpg_sign_opt_quoted=${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} warn "You can amend the commit now, with" warn
@@ -535,7 +541,7 @@ do_pick () { fi rewrite=y rewrite_amend=y - git rev-parse --verify HEAD >"$amend" + >"$amend" ;; -F|--file) if test $# -eq 0
@@ -572,7 +578,7 @@ do_pick () { then rewrite=y rewrite_amend=y - git rev-parse --verify HEAD >"$amend" + >"$amend" # Set the correct commit message and author info on the # sentinel root before cherry-picking the original changes
@@ -734,6 +740,7 @@ do_replay () { do_next () { rm -f "$msg" "$author_script" "$amend" || exit + git rev-parse --verify HEAD >"$last_head" || exit read -r command args <"$todo" case "$command" in
@@ -1031,13 +1038,13 @@ In both case, once you're done, continue with: git rebase --continue " fi - if test -f "$amend" - then - current_head=$(git rev-parse --verify HEAD) - test "$current_head" = $(cat "$amend") || + current_head=$(git rev-parse --verify HEAD) + test "$current_head" = $(cat "$last_head") || die "\ You have uncommitted changes in your working tree. Please, commit them first and then run 'git rebase --continue' again." + if test -f "$amend" + then git commit --amend --no-verify -F "$msg" -e \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die "Could not commit staged changes."
t/t3404-rebase-interactive.sh +12 -0
@@ -444,6 +444,18 @@ test_expect_success '--continue tries to commit' ' git show HEAD | grep chouette ' +test_expect_success '--continue does not commit after head is moved' ' + git reset --hard to-be-rebased@{1} && + test_must_fail git rebase -i --onto new-branch1 HEAD^ && + echo resolved >file1 && + git add file1 && + git commit && + echo dirty >file1 && + test_must_fail git rebase --continue && + git checkout file1 && + git rebase --continue +' + test_expect_success 'verbose flag is heeded, even after --continue' ' git reset --hard master@{1} && test_tick &&
Signature · 1 line
2.0.1
PATCH v3 27/27 rebase -i: enable --signoff, --reset-author for pick, reword, edit
In reply to Fabian RuchLift the general unknown option blockade for the pick, reword and edit commands. If do_cmd comes across one of the options --signoff and --reset-author while parsing a to-do entry and the scheduled command is either pick or reword, relay the option to do_pick.
Remember to add Signed-off-by: and to reset the authorship even when the rebase is interrupted for conflict resolution and git rebase --continue creates the commit. Employ the same mechanism that is used to remember amending an interim squash commit after conflict resolution or a commit after editing. If a line option was specified, create the files signoff and resetauthor respectively in the state directory. While signoff is handled by simply specifying the --signoff option when creating the commit, the resetauthor case is somewhat more involved. The author script contains the author information of the replayed commit. Renewing the authorship means using the user environment for the authorship so that we need to skip the author script if resetauthor exists and we are not amending. If we are amending, --reset-author must be passed to git-commit because otherwise the authorship of HEAD would be used.
do_pick options like --gpg-sign and --file are not yet supported because do_cmd cannot handle option arguments and options with spaces at the moment. squash and fixup still do not accept user options as the interplay of --reset-author and the author script is yet to be determined.
Document the new options by listing them in the to-do help and giving a usage example in the "INTERACTIVE MODE" section of the git-rebase man page.
Add tests.
--- Documentation/git-rebase.txt | 13 +++ git-rebase--interactive.sh | 38 +++++++- t/t3427-rebase-line-options.sh | 192 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 240 insertions(+), 3 deletions(-)
Documentation/git-rebase.txt +13 -0
@@ -508,6 +508,19 @@ rebasing. If you just want to edit the commit message for a commit, replace the command "pick" with the command "reword". +The commands "pick", "reword" and "edit" understand some well-known +options. To add a Signed-off-by line at the end of the commit +message, pass the `--signoff` option. The authorship can be renewed +by specifying the `--reset-author` option. For instance, before you +decide to publish a heavily edited commit you might want to reset the +authorship and add your signature. You can do so on a per line basis: + +------------------------------------------- +pick deadbee The oneline of this commit +pick --reset-author --signoff fa1afe1 The oneline of the next commit +... +------------------------------------------- + If you want to fold two or more commits into one, replace the command "pick" for the second and subsequent commits with "squash" or "fixup". If the commits had different authors, the folded commit will be
git-rebase--interactive.sh +36 -2
@@ -72,9 +72,15 @@ last_head="$state_dir"/last_head # file 'amend' is created. When "git rebase --continue" is executed, # if there are any staged changes then they will be amended to the # HEAD commit, but only provided the HEAD commit is still the commit -# to be edited or the squash commit. When any other rebase command is +# to be edited or the squash commit. Similarly, when a Signed-off-by: +# should be added to a log message or the authorship should be +# renewed, the files 'signoff' and 'resetauthor' are created +# respectively, and "git rebase --continue" carries out the changes +# after conflict resolution. When any other rebase command is # processed, these files are deleted. amend="$state_dir"/amend +signoff="$state_dir"/signoff +resetauthor="$state_dir"/resetauthor # For the post-rewrite hook, we make a list of rewritten commits and # their new sha1s. The rewritten-pending list keeps the sha1s of
@@ -149,6 +155,10 @@ Commands: f, fixup = like "squash", but discard this commit's log message x, exec = run command (the rest of the line) using shell +Options: + [pick | reword | edit] --signoff = add a Signed-off-by line + [pick | reword | edit] --reset-author = renew authorship + These lines can be re-ordered; they are executed from top to bottom. If you remove a line here THAT COMMIT WILL BE LOST.
@@ -528,10 +538,12 @@ do_pick () { -s|--signoff) rewrite=y rewrite_signoff=y + >"$signoff" ;; --reset-author) rewrite=y rewrite_reset_author=y + >"$resetauthor" ;; --amend) if test "$(git rev-parse HEAD)" = "$squash_onto" || ! git rev-parse -q --verify HEAD >/dev/null
@@ -630,6 +642,15 @@ do_replay () { while test $# -gt 0 && test -z "$malformed" do case "$1" in + --signoff|--reset-author) + case "$command" in + pick|reword|edit) + ;; + *) + malformed="Unsupported '$command' option: $1" + ;; + esac + ;; -*) malformed="Unknown '$command' option: $1" ;;
@@ -739,7 +760,7 @@ do_replay () { } do_next () { - rm -f "$msg" "$author_script" "$amend" || exit + rm -f "$msg" "$author_script" "$amend" "$signoff" "$resetauthor" || exit git rev-parse --verify HEAD >"$last_head" || exit read -r command args <"$todo"
@@ -1043,15 +1064,28 @@ In both case, once you're done, continue with: die "\ You have uncommitted changes in your working tree. Please, commit them first and then run 'git rebase --continue' again." + rewrite_reset_author= + rewrite_signoff= + test -f "$resetauthor" && rewrite_reset_author=y + test -f "$signoff" && rewrite_signoff=y if test -f "$amend" then git commit --amend --no-verify -F "$msg" -e \ + ${rewrite_signoff:+--signoff} \ + ${rewrite_reset_author:+--reset-author} \ + ${gpg_sign_opt:+"$gpg_sign_opt"} || + die "Could not commit staged changes." + elif test -n "$rewrite_reset_author" + then + git commit --no-verify -F "$msg" -e \ + ${rewrite_signoff:+--signoff} \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die "Could not commit staged changes." else test -r "$author_script" || die "Error trying to find the author identity to amend commit" do_with_author $(cat "$author_script") git commit --no-verify -F "$msg" -e \ + ${rewrite_signoff:+--signoff} \ ${gpg_sign_opt:+"$gpg_sign_opt"} || die "Could not commit staged changes." fi
t/t3427-rebase-line-options.sh +191 -1
205 lines not on this page · raw message
Signature · 1 line
2.0.1