summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunio C Hamano <gitster@pobox.com>2020-02-21 02:06:02 (GMT)
committerJunio C Hamano <gitster@pobox.com>2020-02-21 02:06:02 (GMT)
commitc8286da1bd6734f4da7443ab5210611aca0296e5 (patch)
tree00668486449be297810387b98a020f0d8eef79f4
parent5f2f705c27be7b9c495f608916810a57e176c1c4 (diff)
parent283d1f2255e96c6e03fdbd9bd4a89c2627bc9c7a (diff)
downloadgit-c8286da1bd6734f4da7443ab5210611aca0296e5.zip
git-c8286da1bd6734f4da7443ab5210611aca0296e5.tar.gz
git-c8286da1bd6734f4da7443ab5210611aca0296e5.tar.bz2
Merge branch 'pw/advise-rebase-skip' into pu
The mechanism to prevent "git commit" from making an empty commit or amending during an interrupted cherry-pick was broken during the rewrite of "git rebase" in C, which has been corrected. What's the status of this one? The tip two are still RFC. * pw/advise-rebase-skip: rebase -i: leave CHERRY_PICK_HEAD when there are conflicts rebase: fix advice when a fixup creates an empty commit commit: give correct advice for empty commit during a rebase commit: encapsulate determine_whence() for sequencer commit: use enum value for multiple cherry-picks sequencer: write CHERRY_PICK_HEAD for reword and edit cherry-pick: check commit error messages cherry-pick: add test for `--skip` advice in `git commit` t3404: use test_cmp_rev
-rw-r--r--builtin/commit.c68
-rw-r--r--sequencer.c93
-rw-r--r--sequencer.h4
-rwxr-xr-xt/t3403-rebase-skip.sh97
-rwxr-xr-xt/t3404-rebase-interactive.sh166
-rwxr-xr-xt/t3507-cherry-pick-conflict.sh23
-rwxr-xr-xt/t3510-cherry-pick-sequence.sh3
-rwxr-xr-xt/t7512-status-help.sh2
-rw-r--r--wt-status.h16
9 files changed, 386 insertions, 86 deletions
diff --git a/builtin/commit.c b/builtin/commit.c
index 7ba33a3..c55699c 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -52,6 +52,20 @@ N_("You asked to amend the most recent commit, but doing so would make\n"
"it empty. You can repeat your command with --allow-empty, or you can\n"
"remove the commit entirely with \"git reset HEAD^\".\n");
+static const char empty_rebase_fixup_advice[] =
+N_("The fixup would make the commit empty\n"
+"If you wish to commit it anyway use:\n"
+"\n"
+" git commit --amend --allow-empty\n"
+" git rebase --continue\n"
+"\n"
+"To remove the commit entirely use:\n"
+"\n"
+" git reset HEAD^\n"
+" git rebase --continue\n"
+"\n"
+"Otherwise, please use 'git rebase --skip' to skip it\n");
+
static const char empty_cherry_pick_advice[] =
N_("The previous cherry-pick is now empty, possibly due to conflict resolution.\n"
"If you wish to commit it anyway, use:\n"
@@ -59,6 +73,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 +139,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,13 +195,14 @@ 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 {
+ int res = sequencer_determine_whence(the_repository, &whence,
+ amend);
+ if (res < 0)
+ die(_("could not read sequencer state"));
+ else if (!res)
+ whence = FROM_COMMIT;
}
- else
- whence = FROM_COMMIT;
if (s)
s->whence = whence;
}
@@ -195,7 +212,6 @@ static void status_init_config(struct wt_status *s, config_fn_t fn)
wt_status_prepare(the_repository, s);
init_diff_ui_defaults();
git_config(fn, s);
- determine_whence(s);
s->hints = advice_status_hints; /* must come after git_config() */
}
@@ -477,8 +493,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 +813,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";
}
@@ -969,16 +987,22 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
if (!committable && whence != FROM_MERGE && !allow_empty &&
!(amend && is_a_merge(current_head))) {
s->hints = advice_status_hints;
+ fprintf(stderr, "\nwhence = %d\n", whence);
s->display_comment_prefix = old_display_comment_prefix;
run_status(stdout, index_file, prefix, 0, s);
- if (amend)
+ if (whence == FROM_REBASE_FIXUP)
+ fputs(_(empty_rebase_fixup_advice), stderr);
+ else 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;
}
@@ -1150,6 +1174,8 @@ static void finalize_deferred_config(struct wt_status *s)
if (s->ahead_behind_flags == AHEAD_BEHIND_UNSPECIFIED)
s->ahead_behind_flags = AHEAD_BEHIND_FULL;
+
+ determine_whence(s);
}
static int parse_and_validate_options(int argc, const char *argv[],
@@ -1181,8 +1207,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 +1232,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 +1242,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 +1661,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 7477b15..0e4fd13 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")
@@ -376,11 +376,15 @@ static void print_advice(struct repository *r, int show_hint,
if (msg) {
fprintf(stderr, "%s\n", msg);
/*
- * A conflict has occurred but the porcelain
- * (typically rebase --interactive) wants to take care
- * of the commit itself so remove CHERRY_PICK_HEAD
+ * A conflict has occurred but the porcelain wants to take care
+ * of the commit itself so remove CHERRY_PICK_HEAD. Note that we
+ * do not do this for interactive rebases anymore in order to
+ * preserve the author identity when the user runs 'git commit'
+ * to commit the conflict resolution rather than relying on
+ * 'rebase --continue' to do it for them.
*/
- unlink(git_path_cherry_pick_head(r));
+ if (!is_rebase_i(opts))
+ unlink(git_path_cherry_pick_head(r));
return;
}
@@ -1433,9 +1437,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 +1474,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 +1947,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;
@@ -1963,7 +1983,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);
@@ -2998,9 +3019,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));
@@ -5313,3 +5332,53 @@ int todo_list_rearrange_squash(struct todo_list *todo_list)
return 0;
}
+
+int sequencer_determine_whence(struct repository *r, enum commit_whence *whence,
+ int amending)
+{
+ 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;
+ } else if (amending && file_exists(rebase_path_current_fixups()) &&
+ (file_exists(git_path_squash_msg(r)) ||
+ file_exists(git_path_merge_msg(r)))) {
+ /*
+ * If rebase_path_amend() exists the user is running `git
+ * commit`, if not we're committing a fixup/squash directly from
+ * the sequencer
+ */
+ if (file_exists(rebase_path_amend())) {
+ struct strbuf rev = STRBUF_INIT;
+ struct object_id to_amend, head;
+
+ if (get_oid("HEAD", &head))
+ return error(_("amending invalid head")); /* let commit deal with error */
+ if (!read_oneliner(&rev, rebase_path_amend(), 0))
+ return error(_("invalid file: '%s'"),
+ rebase_path_amend());
+ if (get_oid_hex(rev.buf, &to_amend))
+ return error(_("invalid contents: '%s'"),
+ rebase_path_amend());
+ if (oideq(&head, &to_amend)) {
+ *whence = FROM_REBASE_FIXUP;
+ return 1;
+ }
+ } else {
+ *whence = FROM_REBASE_FIXUP;
+ return 1;
+ }
+ }
+
+ return 0;
+}
diff --git a/sequencer.h b/sequencer.h
index 718a074..031929c 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,6 @@ 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,
+ int amending);
#endif /* SEQUENCER_H */
diff --git a/t/t3403-rebase-skip.sh b/t/t3403-rebase-skip.sh
index ee8a8db..bc110b6 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,22 +92,100 @@ test_expect_success 'moved back to branch correctly' '
test_debug 'gitk --all & sleep 1'
-test_expect_success 'fixup that empties commit fails' '
+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 'correct advice when fixup empties commit' '
test_when_finished "git rebase --abort" &&
(
set_fake_editor &&
test_must_fail env FAKE_LINES="1 fixup 2" git rebase -i \
- goodbye^ reverted-goodbye
- )
+ goodbye^ reverted-goodbye 2>err
+ ) &&
+ test_i18ngrep "git rebase --skip" err &&
+ test_must_fail git commit --amend --no-edit 2>err &&
+ test_i18ngrep "git rebase --skip" err
'
-test_expect_success 'squash that empties commit fails' '
+test_expect_success 'correct advice when squash empties commit' '
test_when_finished "git rebase --abort" &&
(
set_fake_editor &&
test_must_fail env FAKE_LINES="1 squash 2" git rebase -i \
- goodbye^ reverted-goodbye
- )
+ goodbye^ reverted-goodbye 2>err
+ ) &&
+ test_i18ngrep "git rebase --skip" err &&
+ test_must_fail git commit --amend --no-edit 2>err &&
+ test_i18ngrep "git rebase --skip" err
'
# Must be the last test in this file
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index c5ce3ab..43b06b6 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -30,31 +30,35 @@ Initial setup:
. "$TEST_DIRECTORY"/lib-rebase.sh
test_expect_success 'setup' '
- test_commit A file1 &&
- test_commit B file1 &&
- test_commit C file2 &&
- test_commit D file1 &&
- test_commit E file3 &&
- git checkout -b branch1 A &&
- test_commit F file4 &&
- test_commit G file1 &&
- test_commit H file5 &&
- git checkout -b branch2 F &&
- test_commit I file6 &&
- git checkout -b conflict-branch A &&
- test_commit one conflict &&
- test_commit two conflict &&
- test_commit three conflict &&
- test_commit four conflict &&
- git checkout -b no-conflict-branch A &&
- test_commit J fileJ &&
- test_commit K fileK &&
- test_commit L fileL &&
- test_commit M fileM &&
- git checkout -b no-ff-branch A &&
- test_commit N fileN &&
- test_commit O fileO &&
- test_commit P fileP
+ (
+ GIT_AUTHOR_NAME="Original Author" &&
+ GIT_AUTHOR_EMAIL="original.author@example.com" &&
+ test_commit A file1 &&
+ test_commit B file1 &&
+ test_commit C file2 &&
+ test_commit D file1 &&
+ test_commit E file3 &&
+ git checkout -b branch1 A &&
+ test_commit F file4 &&
+ test_commit G file1 &&
+ test_commit H file5 &&
+ git checkout -b branch2 F &&
+ test_commit I file6 &&
+ git checkout -b conflict-branch A &&
+ test_commit one conflict &&
+ test_commit two conflict &&
+ test_commit three conflict &&
+ test_commit four conflict &&
+ git checkout -b no-conflict-branch A &&
+ test_commit J fileJ &&
+ test_commit K fileK &&
+ test_commit L fileL &&
+ test_commit M fileM &&
+ git checkout -b no-ff-branch A &&
+ test_commit N fileN &&
+ test_commit O fileO &&
+ test_commit P fileP
+ )
'
# "exec" commands are run with the user shell by default, but this may
@@ -187,7 +191,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 +200,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 +218,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' '
@@ -256,7 +260,7 @@ test_expect_success 'stop on conflicting pick' '
D
=======
G
- >>>>>>> $commit... G
+ >>>>>>> $(git rev-parse --short HEAD)... G
EOF
git tag new-branch1 &&
test_must_fail git rebase -i master &&
@@ -279,7 +283,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 +326,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 +343,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 +402,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 +436,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 +743,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 +802,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 +825,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 +889,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 +1738,78 @@ 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
+'
+
+test_expect_success 'correct error message for partial commit after confilct' '
+ test_when_finished "git rebase --abort" &&
+ git checkout D &&
+ (
+ set_fake_editor &&
+ FAKE_LINES="2 3" &&
+ export FAKE_LINES &&
+ test_must_fail git rebase -i A
+ ) &&
+ echo x >file1 &&
+ echo y >file2 &&
+ git add file1 file2 &&
+ 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 conflict' '
+ test_when_finished "git rebase --abort" &&
+ git checkout D &&
+ (
+ set_fake_editor &&
+ FAKE_LINES=3 &&
+ export FAKE_LINES &&
+ test_must_fail git rebase -i A
+ ) &&
+ echo x>file1 &&
+ test_must_fail git commit -a --amend 2>err &&
+ test_i18ngrep "middle of a rebase -- cannot amend." err
+'
+
+test_expect_success 'correct authorship and message after conflict' '
+ git checkout D &&
+ (
+ set_fake_editor &&
+ FAKE_LINES=3 &&
+ export FAKE_LINES &&
+ test_must_fail git rebase -i A
+ ) &&
+ echo x >file1 &&
+ git commit -a &&
+ git log --pretty=format:"%an <%ae>%n%ad%n%B" -1 D >expect &&
+ git log --pretty=format:"%an <%ae>%n%ad%n%B" -1 HEAD >actual &&
+ test_cmp expect actual &&
+ git rebase --continue
+'
+
# 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 9bd482c..752bc43 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 793bcc7..5b94fda 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/t/t7512-status-help.sh b/t/t7512-status-help.sh
index 29518e0..fca37ed 100755
--- a/t/t7512-status-help.sh
+++ b/t/t7512-status-help.sh
@@ -144,7 +144,6 @@ You are currently rebasing branch '\''rebase_i_conflicts_second'\'' on '\''$ONTO
(use "git rebase --abort" to check out the original branch)
Unmerged paths:
- (use "git restore --staged <file>..." to unstage)
(use "git add <file>..." to mark resolution)
both modified: main.txt
@@ -171,7 +170,6 @@ You are currently rebasing branch '\''rebase_i_conflicts_second'\'' on '\''$ONTO
(all conflicts fixed: run "git rebase --continue")
Changes to be committed:
- (use "git restore --staged <file>..." to unstage)
modified: main.txt
Untracked files not listed (use -u option to show untracked files)
diff --git a/wt-status.h b/wt-status.h
index 71c3f25..1584df3 100644
--- a/wt-status.h
+++ b/wt-status.h
@@ -38,9 +38,23 @@ 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 */
+ FROM_REBASE_FIXUP /* commit came from fixup/squash */
};
+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 || whence == FROM_REBASE_FIXUP;
+}
+
struct wt_status_change_data {
int worktree_status;
int index_status;