diff options
-rw-r--r-- | builtin/commit.c | 40 | ||||
-rw-r--r-- | sequencer.c | 52 | ||||
-rw-r--r-- | sequencer.h | 3 | ||||
-rwxr-xr-x | t/t3403-rebase-skip.sh | 79 | ||||
-rwxr-xr-x | t/t3404-rebase-interactive.sh | 64 | ||||
-rwxr-xr-x | t/t3507-cherry-pick-conflict.sh | 23 | ||||
-rwxr-xr-x | t/t3510-cherry-pick-sequence.sh | 3 | ||||
-rw-r--r-- | wt-status.h | 15 |
8 files changed, 233 insertions, 46 deletions
diff --git a/builtin/commit.c b/builtin/commit.c index 7ba33a3bec..5f379e4807 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -59,6 +59,9 @@ N_("The previous cherry-pick is now empty, possibly due to conflict resolution.\ " git commit --allow-empty\n" "\n"); +static const char empty_rebase_pick_advice[] = +N_("Otherwise, please use 'git rebase --skip'\n"); + static const char empty_cherry_pick_advice_single[] = N_("Otherwise, please use 'git cherry-pick --skip'\n"); @@ -122,7 +125,6 @@ static enum commit_msg_cleanup_mode cleanup_mode; static const char *cleanup_arg; static enum commit_whence whence; -static int sequencer_in_use; static int use_editor = 1, include_status = 1; static int have_option_m; static struct strbuf message = STRBUF_INIT; @@ -179,12 +181,7 @@ static void determine_whence(struct wt_status *s) { if (file_exists(git_path_merge_head(the_repository))) whence = FROM_MERGE; - else if (file_exists(git_path_cherry_pick_head(the_repository))) { - whence = FROM_CHERRY_PICK; - if (file_exists(git_path_seq_dir())) - sequencer_in_use = 1; - } - else + else if (!sequencer_determine_whence(the_repository, &whence)) whence = FROM_COMMIT; if (s) s->whence = whence; @@ -477,8 +474,10 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix if (whence != FROM_COMMIT) { if (whence == FROM_MERGE) die(_("cannot do a partial commit during a merge.")); - else if (whence == FROM_CHERRY_PICK) + else if (is_from_cherry_pick(whence)) die(_("cannot do a partial commit during a cherry-pick.")); + else if (is_from_rebase(whence)) + die(_("cannot do a partial commit during a rebase.")); } if (list_paths(&partial, !current_head ? NULL : "HEAD", &pathspec)) @@ -795,7 +794,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, */ else if (whence == FROM_MERGE) hook_arg1 = "merge"; - else if (whence == FROM_CHERRY_PICK) { + else if (is_from_cherry_pick(whence) || whence == FROM_REBASE_PICK) { hook_arg1 = "commit"; hook_arg2 = "CHERRY_PICK_HEAD"; } @@ -973,12 +972,15 @@ static int prepare_to_commit(const char *index_file, const char *prefix, run_status(stdout, index_file, prefix, 0, s); if (amend) fputs(_(empty_amend_advice), stderr); - else if (whence == FROM_CHERRY_PICK) { + else if (is_from_cherry_pick(whence) || + whence == FROM_REBASE_PICK) { fputs(_(empty_cherry_pick_advice), stderr); - if (!sequencer_in_use) + if (whence == FROM_CHERRY_PICK_SINGLE) fputs(_(empty_cherry_pick_advice_single), stderr); - else + else if (whence == FROM_CHERRY_PICK_MULTI) fputs(_(empty_cherry_pick_advice_multi), stderr); + else + fputs(_(empty_rebase_pick_advice), stderr); } return 0; } @@ -1181,8 +1183,10 @@ static int parse_and_validate_options(int argc, const char *argv[], if (amend && whence != FROM_COMMIT) { if (whence == FROM_MERGE) die(_("You are in the middle of a merge -- cannot amend.")); - else if (whence == FROM_CHERRY_PICK) + else if (is_from_cherry_pick(whence)) die(_("You are in the middle of a cherry-pick -- cannot amend.")); + else if (whence == FROM_REBASE_PICK) + die(_("You are in the middle of a rebase -- cannot amend.")); } if (fixup_message && squash_message) die(_("Options --squash and --fixup cannot be used together")); @@ -1204,7 +1208,8 @@ static int parse_and_validate_options(int argc, const char *argv[], use_message = edit_message; if (amend && !use_message && !fixup_message) use_message = "HEAD"; - if (!use_message && whence != FROM_CHERRY_PICK && renew_authorship) + if (!use_message && !is_from_cherry_pick(whence) && + !is_from_rebase(whence) && renew_authorship) die(_("--reset-author can be used only with -C, -c or --amend.")); if (use_message) { use_message_buffer = read_commit_message(use_message); @@ -1213,7 +1218,8 @@ static int parse_and_validate_options(int argc, const char *argv[], author_message_buffer = use_message_buffer; } } - if (whence == FROM_CHERRY_PICK && !renew_authorship) { + if ((is_from_cherry_pick(whence) || whence == FROM_REBASE_PICK) && + !renew_authorship) { author_message = "CHERRY_PICK_HEAD"; author_message_buffer = read_commit_message(author_message); } @@ -1631,8 +1637,10 @@ int cmd_commit(int argc, const char **argv, const char *prefix) reduce_heads_replace(&parents); } else { if (!reflog_msg) - reflog_msg = (whence == FROM_CHERRY_PICK) + reflog_msg = is_from_cherry_pick(whence) ? "commit (cherry-pick)" + : is_from_rebase(whence) + ? "commit (rebase)" : "commit"; commit_list_insert(current_head, &parents); } diff --git a/sequencer.c b/sequencer.c index e528225e78..55b9e047ef 100644 --- a/sequencer.c +++ b/sequencer.c @@ -40,7 +40,7 @@ static const char cherry_picked_prefix[] = "(cherry picked from commit "; GIT_PATH_FUNC(git_path_commit_editmsg, "COMMIT_EDITMSG") -GIT_PATH_FUNC(git_path_seq_dir, "sequencer") +static GIT_PATH_FUNC(git_path_seq_dir, "sequencer") static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo") static GIT_PATH_FUNC(git_path_opts_file, "sequencer/opts") @@ -1433,9 +1433,19 @@ out: return res; } +static int write_rebase_head(struct object_id *oid) +{ + if (update_ref("rebase", "REBASE_HEAD", oid, + NULL, REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) + return error(_("could not update %s"), "REBASE_HEAD"); + + return 0; +} + static int do_commit(struct repository *r, const char *msg_file, const char *author, - struct replay_opts *opts, unsigned int flags) + struct replay_opts *opts, unsigned int flags, + struct object_id *oid) { int res = 1; @@ -1460,8 +1470,12 @@ static int do_commit(struct repository *r, return res; } } - if (res == 1) + if (res == 1) { + if (is_rebase_i(opts) && oid) + if (write_rebase_head(oid)) + return -1; return run_git_commit(r, msg_file, opts, flags); + } return res; } @@ -1929,7 +1943,9 @@ static int do_pick_commit(struct repository *r, * However, if the merge did not even start, then we don't want to * write it at all. */ - if (command == TODO_PICK && !opts->no_commit && (res == 0 || res == 1) && + if ((command == TODO_PICK || command == TODO_REWORD || + command == TODO_EDIT) && !opts->no_commit && + (res == 0 || res == 1) && update_ref(NULL, "CHERRY_PICK_HEAD", &commit->object.oid, NULL, REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) res = -1; @@ -1965,7 +1981,8 @@ static int do_pick_commit(struct repository *r, } /* else allow == 0 and there's nothing special to do */ if (!opts->no_commit && !drop_commit) { if (author || command == TODO_REVERT || (flags & AMEND_MSG)) - res = do_commit(r, msg_file, author, opts, flags); + res = do_commit(r, msg_file, author, opts, flags, + commit? &commit->object.oid : NULL); else res = error(_("unable to parse commit author")); *check_todo = !!(flags & EDIT_MSG); @@ -3000,9 +3017,7 @@ static int make_patch(struct repository *r, p = short_commit_name(commit); if (write_message(p, strlen(p), rebase_path_stopped_sha(), 1) < 0) return -1; - if (update_ref("rebase", "REBASE_HEAD", &commit->object.oid, - NULL, REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) - res |= error(_("could not update %s"), "REBASE_HEAD"); + res |= write_rebase_head(&commit->object.oid); strbuf_addf(&buf, "%s/patch", get_dir(opts)); memset(&log_tree_opt, 0, sizeof(log_tree_opt)); @@ -5315,3 +5330,24 @@ int todo_list_rearrange_squash(struct todo_list *todo_list) return 0; } + +int sequencer_determine_whence(struct repository *r, enum commit_whence *whence) +{ + if (file_exists(git_path_cherry_pick_head(r))) { + struct object_id cherry_pick_head, rebase_head; + + if (file_exists(git_path_seq_dir())) + *whence = FROM_CHERRY_PICK_MULTI; + if (file_exists(rebase_path()) && + !get_oid("REBASE_HEAD", &rebase_head) && + !get_oid("CHERRY_PICK_HEAD", &cherry_pick_head) && + oideq(&rebase_head, &cherry_pick_head)) + *whence = FROM_REBASE_PICK; + else + *whence = FROM_CHERRY_PICK_SINGLE; + + return 1; + } + + return 0; +} diff --git a/sequencer.h b/sequencer.h index 718a07426d..0bee85093e 100644 --- a/sequencer.h +++ b/sequencer.h @@ -3,12 +3,12 @@ #include "cache.h" #include "strbuf.h" +#include "wt-status.h" struct commit; struct repository; const char *git_path_commit_editmsg(void); -const char *git_path_seq_dir(void); const char *rebase_path_todo(void); const char *rebase_path_todo_backup(void); const char *rebase_path_dropped(void); @@ -206,4 +206,5 @@ int write_basic_state(struct replay_opts *opts, const char *head_name, void sequencer_post_commit_cleanup(struct repository *r, int verbose); int sequencer_get_last_command(struct repository* r, enum replay_action *action); +int sequencer_determine_whence(struct repository *r, enum commit_whence *whence); #endif /* SEQUENCER_H */ diff --git a/t/t3403-rebase-skip.sh b/t/t3403-rebase-skip.sh index ee8a8dba52..a927774910 100755 --- a/t/t3403-rebase-skip.sh +++ b/t/t3403-rebase-skip.sh @@ -29,6 +29,13 @@ test_expect_success setup ' test_tick && git commit -m reverted-goodbye && git tag reverted-goodbye && + git checkout goodbye && + test_tick && + GIT_AUTHOR_NAME="Another Author" \ + GIT_AUTHOR_EMAIL="another.author@example.com" \ + git commit --amend --no-edit -m amended-goodbye && + test_tick && + git tag amended-goodbye && git checkout -f skip-reference && echo moo > hello && @@ -85,6 +92,78 @@ test_expect_success 'moved back to branch correctly' ' test_debug 'gitk --all & sleep 1' +test_expect_success 'correct advice upon picking empty commit' ' + test_when_finished "git rebase --abort" && + test_must_fail git rebase -i --onto goodbye \ + amended-goodbye^ amended-goodbye 2>err && + test_i18ngrep "previous cherry-pick is now empty" err && + test_i18ngrep "git rebase --skip" err && + test_must_fail git commit && + test_i18ngrep "git rebase --skip" err +' + +test_expect_success 'correct authorship when committing empty pick' ' + test_when_finished "git rebase --abort" && + test_must_fail git rebase -i --onto goodbye \ + amended-goodbye^ amended-goodbye && + git commit --allow-empty && + git log --pretty=format:"%an <%ae>%n%ad%B" -1 amended-goodbye >expect && + git log --pretty=format:"%an <%ae>%n%ad%B" -1 HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'correct advice upon rewording empty commit' ' + test_when_finished "git rebase --abort" && + ( + set_fake_editor && + test_must_fail env FAKE_LINES="reword 1" git rebase -i \ + --onto goodbye amended-goodbye^ amended-goodbye 2>err + ) && + test_i18ngrep "previous cherry-pick is now empty" err && + test_i18ngrep "git rebase --skip" err && + test_must_fail git commit && + test_i18ngrep "git rebase --skip" err +' + +test_expect_success 'correct advice upon editing empty commit' ' + test_when_finished "git rebase --abort" && + ( + set_fake_editor && + test_must_fail env FAKE_LINES="edit 1" git rebase -i \ + --onto goodbye amended-goodbye^ amended-goodbye 2>err + ) && + test_i18ngrep "previous cherry-pick is now empty" err && + test_i18ngrep "git rebase --skip" err && + test_must_fail git commit && + test_i18ngrep "git rebase --skip" err +' + +test_expect_success 'correct advice upon cherry-picking an empty commit during a rebase' ' + test_when_finished "git rebase --abort" && + ( + set_fake_editor && + test_must_fail env FAKE_LINES="1 exec_git_cherry-pick_amended-goodbye" \ + git rebase -i goodbye^ goodbye 2>err + ) && + test_i18ngrep "previous cherry-pick is now empty" err && + test_i18ngrep "git cherry-pick --skip" err && + test_must_fail git commit 2>err && + test_i18ngrep "git cherry-pick --skip" err +' + +test_expect_success 'correct advice upon multi cherry-pick picking an empty commit during a rebase' ' + test_when_finished "git rebase --abort" && + ( + set_fake_editor && + test_must_fail env FAKE_LINES="1 exec_git_cherry-pick_goodbye_amended-goodbye" \ + git rebase -i goodbye^^ goodbye 2>err + ) && + test_i18ngrep "previous cherry-pick is now empty" err && + test_i18ngrep "git cherry-pick --skip" err && + test_must_fail git commit 2>err && + test_i18ngrep "git cherry-pick --skip" err +' + test_expect_success 'fixup that empties commit fails' ' test_when_finished "git rebase --abort" && ( diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index c5ce3ab760..4a7d21f898 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -187,7 +187,7 @@ test_expect_success 'no changes are a nop' ' git checkout branch2 && git rebase -i F && test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch2" && - test $(git rev-parse I) = $(git rev-parse HEAD) + test_cmp_rev I HEAD ' test_expect_success 'test the [branch] option' ' @@ -196,16 +196,16 @@ test_expect_success 'test the [branch] option' ' git commit -m "stop here" && git rebase -i F branch2 && test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch2" && - test $(git rev-parse I) = $(git rev-parse branch2) && - test $(git rev-parse I) = $(git rev-parse HEAD) + test_cmp_rev I branch2 && + test_cmp_rev I HEAD ' test_expect_success 'test --onto <branch>' ' git checkout -b test-onto branch2 && git rebase -i --onto branch1 F && test "$(git symbolic-ref -q HEAD)" = "refs/heads/test-onto" && - test $(git rev-parse HEAD^) = $(git rev-parse branch1) && - test $(git rev-parse I) = $(git rev-parse branch2) + test_cmp_rev HEAD^ branch1 && + test_cmp_rev I branch2 ' test_expect_success 'rebase on top of a non-conflicting commit' ' @@ -214,12 +214,12 @@ test_expect_success 'rebase on top of a non-conflicting commit' ' git rebase -i branch2 && test file6 = $(git diff --name-only original-branch1) && test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch1" && - test $(git rev-parse I) = $(git rev-parse branch2) && - test $(git rev-parse I) = $(git rev-parse HEAD~2) + test_cmp_rev I branch2 && + test_cmp_rev I HEAD~2 ' test_expect_success 'reflog for the branch shows state before rebase' ' - test $(git rev-parse branch1@{1}) = $(git rev-parse original-branch1) + test_cmp_rev branch1@{1} original-branch1 ' test_expect_success 'reflog for the branch shows correct finish message' ' @@ -279,7 +279,7 @@ test_expect_success 'show conflicted patch' ' test_expect_success 'abort' ' git rebase --abort && - test $(git rev-parse new-branch1) = $(git rev-parse HEAD) && + test_cmp_rev new-branch1 HEAD && test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch1" && test_path_is_missing .git/rebase-merge ' @@ -322,7 +322,7 @@ test_expect_success 'retain authorship w/ conflicts' ' echo resolved >conflict && git add conflict && git rebase --continue && - test $(git rev-parse conflict-a^0) = $(git rev-parse HEAD^) && + test_cmp_rev conflict-a^0 HEAD^ && git show >out && grep AttributeMe out ' @@ -339,7 +339,7 @@ test_expect_success 'squash' ' git rebase -i --onto master HEAD~2 ) && test B = $(cat file7) && - test $(git rev-parse HEAD^) = $(git rev-parse master) + test_cmp_rev HEAD^ master ' test_expect_success 'retain authorship when squashing' ' @@ -398,9 +398,9 @@ test_expect_success REBASE_P 'preserve merges with -p' ' git update-index --refresh && git diff-files --quiet && git diff-index --quiet --cached HEAD -- && - test $(git rev-parse HEAD~6) = $(git rev-parse branch1) && - test $(git rev-parse HEAD~4^2) = $(git rev-parse to-be-preserved) && - test $(git rev-parse HEAD^^2^) = $(git rev-parse HEAD^^^) && + test_cmp_rev HEAD~6 branch1 && + test_cmp_rev HEAD~4^2 to-be-preserved && + test_cmp_rev HEAD^^2^ HEAD^^^ && test $(git show HEAD~5:file1) = B && test $(git show HEAD~3:file1) = C && test $(git show HEAD:file1) = E && @@ -432,7 +432,7 @@ test_expect_success '--continue tries to commit' ' git add file1 && FAKE_COMMIT_MESSAGE="chouette!" git rebase --continue ) && - test $(git rev-parse HEAD^) = $(git rev-parse new-branch1) && + test_cmp_rev HEAD^ new-branch1 && git show HEAD | grep chouette ' @@ -739,7 +739,7 @@ test_expect_success 'do "noop" when there is nothing to cherry-pick' ' --author="Somebody else <somebody@else.com>" && test $(git rev-parse branch3) != $(git rev-parse branch4) && git rebase -i branch3 && - test $(git rev-parse branch3) = $(git rev-parse branch4) + test_cmp_rev branch3 branch4 ' @@ -798,7 +798,7 @@ test_expect_success 'rebase -i continue with unstaged submodule' ' test_must_fail git rebase -i submodule-base && git reset && git rebase --continue && - test $(git rev-parse submodule-base) = $(git rev-parse HEAD) + test_cmp_rev submodule-base HEAD ' test_expect_success 'avoid unnecessary reset' ' @@ -821,7 +821,7 @@ test_expect_success 'reword' ' git rebase -i A && git show HEAD | grep "E changed" && test $(git rev-parse master) != $(git rev-parse HEAD) && - test $(git rev-parse master^) = $(git rev-parse HEAD^) && + test_cmp_rev master^ HEAD^ && FAKE_LINES="1 2 reword 3 4" FAKE_COMMIT_MESSAGE="D changed" \ git rebase -i A && git show HEAD^ | grep "D changed" && @@ -885,7 +885,7 @@ test_expect_success 'always cherry-pick with --no-ff' ' git diff HEAD~$p original-no-ff-branch~$p > out && test_must_be_empty out done && - test $(git rev-parse HEAD~3) = $(git rev-parse original-no-ff-branch~3) && + test_cmp_rev HEAD~3 original-no-ff-branch~3 && git diff HEAD~3 original-no-ff-branch~3 > out && test_must_be_empty out ' @@ -1734,6 +1734,32 @@ test_expect_success 'post-commit hook is called' ' test_cmp expect actual ' +test_expect_success 'correct error message for partial commit after empty pick' ' + test_when_finished "git rebase --abort" && + ( + set_fake_editor && + FAKE_LINES="2 1 1" && + export FAKE_LINES && + test_must_fail git rebase -i A D + ) && + echo x >file1 && + test_must_fail git commit file1 2>err && + test_i18ngrep "cannot do a partial commit during a rebase." err +' + +test_expect_success 'correct error message for commit --amend after empty pick' ' + test_when_finished "git rebase --abort" && + ( + set_fake_editor && + FAKE_LINES="1 1" && + export FAKE_LINES && + test_must_fail git rebase -i A D + ) && + echo x>file1 && + test_must_fail git commit -a --amend 2>err && + test_i18ngrep "middle of a rebase -- cannot amend." err +' + # This must be the last test in this file test_expect_success '$EDITOR and friends are unchanged' ' test_editor_unchanged diff --git a/t/t3507-cherry-pick-conflict.sh b/t/t3507-cherry-pick-conflict.sh index 9bd482ce3b..752bc43487 100755 --- a/t/t3507-cherry-pick-conflict.sh +++ b/t/t3507-cherry-pick-conflict.sh @@ -161,6 +161,29 @@ test_expect_success 'successful commit clears CHERRY_PICK_HEAD' ' test_must_fail git rev-parse --verify CHERRY_PICK_HEAD ' + +test_expect_success 'partial commit of cherry-pick fails' ' + pristine_detach initial && + + test_must_fail git cherry-pick picked && + echo resolved >foo && + git add foo && + test_must_fail git commit foo 2>err && + + test_i18ngrep "cannot do a partial commit during a cherry-pick." err +' + +test_expect_success 'commit --amend of cherry-pick fails' ' + pristine_detach initial && + + test_must_fail git cherry-pick picked && + echo resolved >foo && + git add foo && + test_must_fail git commit --amend 2>err && + + test_i18ngrep "in the middle of a cherry-pick -- cannot amend." err +' + test_expect_success 'successful final commit clears cherry-pick state' ' pristine_detach initial && diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh index 793bcc7fe3..5b94fdaa67 100755 --- a/t/t3510-cherry-pick-sequence.sh +++ b/t/t3510-cherry-pick-sequence.sh @@ -123,7 +123,8 @@ test_expect_success 'revert --skip to skip commit' ' test_expect_success 'skip "empty" commit' ' pristine_detach picked && test_commit dummy foo d && - test_must_fail git cherry-pick anotherpick && + test_must_fail git cherry-pick anotherpick 2>err && + test_i18ngrep "git cherry-pick --skip" err && git cherry-pick --skip && test_cmp_rev dummy HEAD ' diff --git a/wt-status.h b/wt-status.h index 71c3f25f43..73ab5d4da1 100644 --- a/wt-status.h +++ b/wt-status.h @@ -38,9 +38,22 @@ enum show_ignored_type { enum commit_whence { FROM_COMMIT, /* normal */ FROM_MERGE, /* commit came from merge */ - FROM_CHERRY_PICK /* commit came from cherry-pick */ + FROM_CHERRY_PICK_SINGLE, /* commit came from cherry-pick */ + FROM_CHERRY_PICK_MULTI, /* commit came from a sequence of cherry-picks */ + FROM_REBASE_PICK /* commit came from a pick/reword/edit */ }; +static inline int is_from_cherry_pick(enum commit_whence whence) +{ + return whence == FROM_CHERRY_PICK_SINGLE || + whence == FROM_CHERRY_PICK_MULTI; +} + +static inline int is_from_rebase(enum commit_whence whence) +{ + return whence == FROM_REBASE_PICK; +} + struct wt_status_change_data { int worktree_status; int index_status; |